Home | History | Annotate | Download | only in tv
      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