• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.media.MediaMetadata;
24 import android.media.session.MediaSession;
25 import android.media.session.PlaybackState;
26 import android.media.tv.TvContract;
27 import android.media.tv.TvInputInfo;
28 import android.os.AsyncTask;
29 import android.support.annotation.NonNull;
30 import android.support.annotation.Nullable;
31 import android.text.TextUtils;
32 
33 import com.android.tv.data.Channel;
34 import com.android.tv.data.Program;
35 import com.android.tv.util.ImageLoader;
36 import com.android.tv.util.Utils;
37 
38 /**
39  * A wrapper class for {@link MediaSession} to support common operations on media sessions for
40  * {@link MainActivity}.
41  */
42 class MediaSessionWrapper {
43     private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession";
44     private static PlaybackState MEDIA_SESSION_STATE_PLAYING = new PlaybackState.Builder()
45             .setState(PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f)
46             .build();
47     private static PlaybackState MEDIA_SESSION_STATE_STOPPED = new PlaybackState.Builder()
48             .setState(PlaybackState.STATE_STOPPED, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f)
49             .build();
50 
51     private final Context mContext;
52     private final MediaSession mMediaSession;
53     private int mNowPlayingCardWidth;
54     private int mNowPlayingCardHeight;
55 
MediaSessionWrapper(Context context)56     MediaSessionWrapper(Context context) {
57         mContext = context;
58         mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG);
59         mMediaSession.setCallback(new MediaSession.Callback() {
60             @Override
61             public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
62                 // Consume the media button event here. Should not send it to other apps.
63                 return true;
64             }
65         });
66         mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
67                 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
68         mNowPlayingCardWidth = mContext.getResources().getDimensionPixelSize(
69                 R.dimen.notif_card_img_max_width);
70         mNowPlayingCardHeight = mContext.getResources().getDimensionPixelSize(
71                 R.dimen.notif_card_img_height);
72     }
73 
74     /**
75      * Sets playback state.
76      *
77      * @param isPlaying {@code true} if TV is playing, otherwise {@code false}.
78      */
setPlaybackState(boolean isPlaying)79     void setPlaybackState(boolean isPlaying) {
80         if (isPlaying) {
81             mMediaSession.setActive(true);
82             // setPlaybackState() has to be called after calling setActive(). b/31933276
83             mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_PLAYING);
84         } else if (mMediaSession.isActive()) {
85             mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_STOPPED);
86             mMediaSession.setActive(false);
87         }
88     }
89 
90     /**
91      * Updates media session according to the current TV playback status.
92      *
93      * @param blocked {@code true} if the current channel is blocked, either by user settings or
94      *                the current program's content ratings.
95      * @param currentChannel The currently playing channel.
96      * @param currentProgram The currently playing program.
97      */
update(boolean blocked, Channel currentChannel, Program currentProgram)98     void update(boolean blocked, Channel currentChannel, Program currentProgram) {
99         if (currentChannel == null) {
100             setPlaybackState(false);
101             return;
102         }
103 
104         // If the channel is blocked, display a lock and a short text on the Now Playing Card
105         if (blocked) {
106             Bitmap art = BitmapFactory.decodeResource(mContext.getResources(),
107                     R.drawable.ic_message_lock_preview);
108             updateMediaMetadata(mContext.getResources()
109                     .getString(R.string.channel_banner_locked_channel_title), art);
110             setPlaybackState(true);
111             return;
112         }
113 
114         String cardTitleText = null;
115         String posterArtUri = null;
116         if (currentProgram != null) {
117             cardTitleText = currentProgram.getTitle();
118             posterArtUri = currentProgram.getPosterArtUri();
119         }
120         if (TextUtils.isEmpty(cardTitleText)) {
121             cardTitleText = getChannelName(currentChannel);
122         }
123         updateMediaMetadata(cardTitleText, null);
124         if (posterArtUri == null) {
125             posterArtUri = TvContract.buildChannelLogoUri(currentChannel.getId()).toString();
126         }
127         updatePosterArt(currentChannel, currentProgram, cardTitleText, null, posterArtUri);
128         setPlaybackState(true);
129     }
130 
131     /**
132      * Releases the media session.
133      *
134      * @see MediaSession#release()
135      */
release()136     void release() {
137         mMediaSession.release();
138     }
139 
getChannelName(Channel channel)140     private String getChannelName(Channel channel) {
141         if (channel.isPassthrough()) {
142             TvInputInfo input = TvApplication.getSingletons(mContext).getTvInputManagerHelper()
143                     .getTvInputInfo(channel.getInputId());
144             return Utils.loadLabel(mContext, input);
145         } else {
146             return channel.getDisplayName();
147         }
148     }
149 
updatePosterArt(Channel currentChannel, Program currentProgram, String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri)150     private void updatePosterArt(Channel currentChannel, Program currentProgram,
151             String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) {
152         if (posterArt != null) {
153             updateMediaMetadata(cardTitleText, posterArt);
154         } else if (posterArtUri != null) {
155             ImageLoader.loadBitmap(mContext, posterArtUri, mNowPlayingCardWidth,
156                     mNowPlayingCardHeight, new ProgramPosterArtCallback(this, currentChannel,
157                             currentProgram, cardTitleText));
158         } else {
159             updateMediaMetadata(cardTitleText, R.drawable.default_now_card);
160         }
161     }
162 
updateMediaMetadata(final String title, final Bitmap posterArt)163     private void updateMediaMetadata(final String title, final Bitmap posterArt) {
164         new AsyncTask<Void, Void, Void>() {
165             @Override
166             protected Void doInBackground(Void... arg0) {
167                 MediaMetadata.Builder builder = new MediaMetadata.Builder();
168                 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
169                 if (posterArt != null) {
170                     builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
171                 }
172                 mMediaSession.setMetadata(builder.build());
173                 return null;
174             }
175         }.execute();
176     }
177 
updateMediaMetadata(final String title, final int imageResId)178     private void updateMediaMetadata(final String title, final int imageResId) {
179         new AsyncTask<Void, Void, Void> () {
180             @Override
181             protected Void doInBackground(Void... arg0) {
182                 MediaMetadata.Builder builder = new MediaMetadata.Builder();
183                 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
184                 Bitmap posterArt =
185                         BitmapFactory.decodeResource(mContext.getResources(), imageResId);
186                 if (posterArt != null) {
187                     builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
188                 }
189                 mMediaSession.setMetadata(builder.build());
190                 return null;
191             }
192         }.execute();
193     }
194 
195     private static class ProgramPosterArtCallback extends
196             ImageLoader.ImageLoaderCallback<MediaSessionWrapper> {
197         private final Channel mChannel;
198         private final Program mProgram;
199         private final String mCardTitleText;
200 
ProgramPosterArtCallback(MediaSessionWrapper sessionWrapper, Channel channel, Program program, String cardTitleText)201         ProgramPosterArtCallback(MediaSessionWrapper sessionWrapper, Channel channel,
202                 Program program, String cardTitleText) {
203             super(sessionWrapper);
204             mChannel = channel;
205             mProgram = program;
206             mCardTitleText = cardTitleText;
207         }
208 
209         @Override
onBitmapLoaded(MediaSessionWrapper sessionWrapper, @Nullable Bitmap posterArt)210         public void onBitmapLoaded(MediaSessionWrapper sessionWrapper, @Nullable Bitmap posterArt) {
211             if (((MainActivity) sessionWrapper.mContext).isNowPlayingProgram(mChannel, mProgram)) {
212                 sessionWrapper.updatePosterArt(mChannel, mProgram, mCardTitleText, posterArt, null);
213             }
214         }
215     }
216 }
217