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.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 
     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      */
     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      */
     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      */
    136     void release() {
    137         mMediaSession.release();
    138     }
    139 
    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 
    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 
    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 
    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 
    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
    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