• 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.app.Activity;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.media.MediaMetadata;
26 import android.media.session.MediaController;
27 import android.media.session.MediaSession;
28 import android.media.session.PlaybackState;
29 import android.media.tv.TvContract;
30 import android.media.tv.TvInputInfo;
31 import android.os.AsyncTask;
32 import android.support.annotation.NonNull;
33 import android.support.annotation.Nullable;
34 import android.support.annotation.VisibleForTesting;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import com.android.tv.data.Program;
38 import com.android.tv.data.api.Channel;
39 import com.android.tv.util.Utils;
40 import com.android.tv.util.images.ImageLoader;
41 
42 /**
43  * A wrapper class for {@link MediaSession} to support common operations on media sessions for
44  * {@link MainActivity}.
45  */
46 class MediaSessionWrapper {
47     private static final String TAG = "MediaSessionWrapper";
48     private static final boolean DEBUG = false;
49     private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession";
50 
51     @VisibleForTesting
52     static final PlaybackState MEDIA_SESSION_STATE_PLAYING =
53             new PlaybackState.Builder()
54                     .setState(
55                             PlaybackState.STATE_PLAYING,
56                             PlaybackState.PLAYBACK_POSITION_UNKNOWN,
57                             1.0f)
58                     .build();
59 
60     @VisibleForTesting
61     static final PlaybackState MEDIA_SESSION_STATE_STOPPED =
62             new PlaybackState.Builder()
63                     .setState(
64                             PlaybackState.STATE_STOPPED,
65                             PlaybackState.PLAYBACK_POSITION_UNKNOWN,
66                             0.0f)
67                     .build();
68 
69     private final Context mContext;
70     private final MediaSession mMediaSession;
71     private final MediaController.Callback mMediaControllerCallback =
72             new MediaController.Callback() {
73                 @Override
74                 public void onPlaybackStateChanged(@Nullable PlaybackState state) {
75                     super.onPlaybackStateChanged(state);
76                     if (DEBUG) {
77                         Log.d(TAG, "onPlaybackStateChanged: " + state);
78                     }
79                     if (isMediaSessionStateStop(state)) {
80                         mMediaSession.setActive(false);
81                     }
82                 }
83             };
84     private MediaController mMediaController;
85     private int mNowPlayingCardWidth;
86     private int mNowPlayingCardHeight;
87 
MediaSessionWrapper(Context context, PendingIntent pendingIntent)88     MediaSessionWrapper(Context context, PendingIntent pendingIntent) {
89         mContext = context;
90         mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG);
91         mMediaSession.setCallback(
92                 new MediaSession.Callback() {
93                     @Override
94                     public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
95                         // Consume the media button event here. Should not send it to other apps.
96                         return true;
97                     }
98                 });
99         mMediaSession.setFlags(
100                 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
101                         | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
102         mMediaSession.setSessionActivity(pendingIntent);
103 
104         initMediaController();
105         mNowPlayingCardWidth =
106                 mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
107         mNowPlayingCardHeight =
108                 mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
109     }
110 
111     /**
112      * Sets playback state.
113      *
114      * @param isPlaying {@code true} if TV is playing, otherwise {@code false}.
115      */
setPlaybackState(boolean isPlaying)116     void setPlaybackState(boolean isPlaying) {
117         if (isPlaying) {
118             mMediaSession.setActive(true);
119             // setPlaybackState() has to be called after calling setActive(). b/31933276
120             mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_PLAYING);
121         } else if (mMediaSession.isActive()) {
122             mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_STOPPED);
123         }
124     }
125 
126     /**
127      * Updates media session according to the current TV playback status.
128      *
129      * @param blocked {@code true} if the current channel is blocked, either by user settings or the
130      *     current program's content ratings.
131      * @param currentChannel The currently playing channel.
132      * @param currentProgram The currently playing program.
133      */
update(boolean blocked, Channel currentChannel, Program currentProgram)134     void update(boolean blocked, Channel currentChannel, Program currentProgram) {
135         if (currentChannel == null) {
136             setPlaybackState(false);
137             return;
138         }
139 
140         // If the channel is blocked, display a lock and a short text on the Now Playing Card
141         if (blocked) {
142             Bitmap art =
143                     BitmapFactory.decodeResource(
144                             mContext.getResources(), R.drawable.ic_message_lock_preview);
145             updateMediaMetadata(
146                     mContext.getResources().getString(R.string.channel_banner_locked_channel_title),
147                     art);
148             setPlaybackState(true);
149             return;
150         }
151 
152         String cardTitleText = null;
153         String posterArtUri = null;
154         if (currentProgram != null) {
155             cardTitleText = currentProgram.getTitle();
156             posterArtUri = currentProgram.getPosterArtUri();
157         }
158         if (TextUtils.isEmpty(cardTitleText)) {
159             cardTitleText = getChannelName(currentChannel);
160         }
161         updateMediaMetadata(cardTitleText, null);
162         if (posterArtUri == null) {
163             posterArtUri = TvContract.buildChannelLogoUri(currentChannel.getId()).toString();
164         }
165         updatePosterArt(currentChannel, currentProgram, cardTitleText, null, posterArtUri);
166         setPlaybackState(true);
167     }
168 
169     /**
170      * Releases the media session.
171      *
172      * @see MediaSession#release()
173      */
release()174     void release() {
175         unregisterMediaControllerCallback();
176         mMediaSession.release();
177     }
178 
getChannelName(Channel channel)179     private String getChannelName(Channel channel) {
180         if (channel.isPassthrough()) {
181             TvInputInfo input =
182                     TvSingletons.getSingletons(mContext)
183                             .getTvInputManagerHelper()
184                             .getTvInputInfo(channel.getInputId());
185             return Utils.loadLabel(mContext, input);
186         } else {
187             return channel.getDisplayName();
188         }
189     }
190 
updatePosterArt( Channel currentChannel, Program currentProgram, String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri)191     private void updatePosterArt(
192             Channel currentChannel,
193             Program currentProgram,
194             String cardTitleText,
195             @Nullable Bitmap posterArt,
196             @Nullable String posterArtUri) {
197         if (posterArt != null) {
198             updateMediaMetadata(cardTitleText, posterArt);
199         } else if (posterArtUri != null) {
200             ImageLoader.loadBitmap(
201                     mContext,
202                     posterArtUri,
203                     mNowPlayingCardWidth,
204                     mNowPlayingCardHeight,
205                     new ProgramPosterArtCallback(
206                             this, currentChannel, currentProgram, cardTitleText));
207         } else {
208             updateMediaMetadata(cardTitleText, R.drawable.default_now_card);
209         }
210     }
211 
updateMediaMetadata(final String title, final Bitmap posterArt)212     private void updateMediaMetadata(final String title, final Bitmap posterArt) {
213         new AsyncTask<Void, Void, Void>() {
214             @Override
215             protected Void doInBackground(Void... arg0) {
216                 MediaMetadata.Builder builder = new MediaMetadata.Builder();
217                 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
218                 if (posterArt != null) {
219                     builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
220                 }
221                 mMediaSession.setMetadata(builder.build());
222                 return null;
223             }
224         }.execute();
225     }
226 
updateMediaMetadata(final String title, final int imageResId)227     private void updateMediaMetadata(final String title, final int imageResId) {
228         new AsyncTask<Void, Void, Void>() {
229             @Override
230             protected Void doInBackground(Void... arg0) {
231                 MediaMetadata.Builder builder = new MediaMetadata.Builder();
232                 builder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
233                 Bitmap posterArt =
234                         BitmapFactory.decodeResource(mContext.getResources(), imageResId);
235                 if (posterArt != null) {
236                     builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
237                 }
238                 mMediaSession.setMetadata(builder.build());
239                 return null;
240             }
241         }.execute();
242     }
243 
244     @VisibleForTesting
getMediaSession()245     MediaSession getMediaSession() {
246         return mMediaSession;
247     }
248 
249     @VisibleForTesting
getMediaControllerCallback()250     MediaController.Callback getMediaControllerCallback() {
251         return mMediaControllerCallback;
252     }
253 
254     @VisibleForTesting
initMediaController()255     void initMediaController() {
256         mMediaController = new MediaController(mContext, mMediaSession.getSessionToken());
257         ((Activity) mContext).setMediaController(mMediaController);
258         mMediaController.registerCallback(mMediaControllerCallback);
259     }
260 
261     @VisibleForTesting
unregisterMediaControllerCallback()262     void unregisterMediaControllerCallback() {
263         mMediaController.unregisterCallback(mMediaControllerCallback);
264     }
265 
isMediaSessionStateStop(PlaybackState state)266     private static boolean isMediaSessionStateStop(PlaybackState state) {
267         return state != null
268                 && state.getState() == MEDIA_SESSION_STATE_STOPPED.getState()
269                 && state.getPosition() == MEDIA_SESSION_STATE_STOPPED.getPosition()
270                 && state.getPlaybackSpeed() == MEDIA_SESSION_STATE_STOPPED.getPlaybackSpeed();
271     }
272 
273     private static class ProgramPosterArtCallback
274             extends ImageLoader.ImageLoaderCallback<MediaSessionWrapper> {
275         private final Channel mChannel;
276         private final Program mProgram;
277         private final String mCardTitleText;
278 
ProgramPosterArtCallback( MediaSessionWrapper sessionWrapper, Channel channel, Program program, String cardTitleText)279         ProgramPosterArtCallback(
280                 MediaSessionWrapper sessionWrapper,
281                 Channel channel,
282                 Program program,
283                 String cardTitleText) {
284             super(sessionWrapper);
285             mChannel = channel;
286             mProgram = program;
287             mCardTitleText = cardTitleText;
288         }
289 
290         @Override
onBitmapLoaded(MediaSessionWrapper sessionWrapper, @Nullable Bitmap posterArt)291         public void onBitmapLoaded(MediaSessionWrapper sessionWrapper, @Nullable Bitmap posterArt) {
292             if (((MainActivity) sessionWrapper.mContext).isNowPlayingProgram(mChannel, mProgram)) {
293                 sessionWrapper.updatePosterArt(mChannel, mProgram, mCardTitleText, posterArt, null);
294             }
295         }
296     }
297 }
298