MediaSessionWrapper.java revision 633eb826b8c97731dfc5ef12c7bf78a63734275d
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
17package com.android.tv;
18
19import android.content.Context;
20import android.content.Intent;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.media.MediaMetadata;
24import android.media.session.MediaSession;
25import android.media.session.PlaybackState;
26import android.media.tv.TvContract;
27import android.media.tv.TvInputInfo;
28import android.os.AsyncTask;
29import android.support.annotation.NonNull;
30import android.support.annotation.Nullable;
31import android.text.TextUtils;
32
33import com.android.tv.data.Channel;
34import com.android.tv.data.Program;
35import com.android.tv.util.ImageLoader;
36import 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 */
42class 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