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