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 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 */ 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 */ 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 */ 152 void release() { 153 mMediaSession.release(); 154 } 155 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 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 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 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 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 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 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