• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.dvr.ui;
18 
19 import android.app.Activity;
20 import android.graphics.drawable.Drawable;
21 import android.media.MediaMetadata;
22 import android.media.session.MediaController;
23 import android.media.session.MediaController.TransportControls;
24 import android.media.session.PlaybackState;
25 import android.support.v17.leanback.app.PlaybackControlGlue;
26 import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
27 import android.support.v17.leanback.widget.Action;
28 import android.support.v17.leanback.widget.OnActionClickedListener;
29 import android.support.v17.leanback.widget.PlaybackControlsRow;
30 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
31 import android.support.v17.leanback.widget.RowPresenter;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.view.KeyEvent;
35 import android.view.View;
36 
37 import com.android.tv.R;
38 import com.android.tv.util.TimeShiftUtils;
39 
40 /**
41  * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and
42  * send command to the media controller. It also helps to update playback states displayed in the
43  * fragment according to information the media session provides.
44  */
45 public class DvrPlaybackControlHelper extends PlaybackControlGlue {
46     private static final String TAG = "DvrPlaybackControlHelper";
47     private static final boolean DEBUG = false;
48 
49     /**
50      * Indicates the ID of the media under playback is unknown.
51      */
52     public static int UNKNOWN_MEDIA_ID = -1;
53 
54     private int mPlaybackState = PlaybackState.STATE_NONE;
55     private int mPlaybackSpeedLevel;
56     private int mPlaybackSpeedId;
57     private boolean mReadyToControl;
58 
59     private final MediaController mMediaController;
60     private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
61     private final TransportControls mTransportControls;
62     private final int mExtraPaddingTopForNoDescription;
63 
DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment)64     public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
65         super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
66         mMediaController = activity.getMediaController();
67         mMediaController.registerCallback(mMediaControllerCallback);
68         mTransportControls = mMediaController.getTransportControls();
69         mExtraPaddingTopForNoDescription = activity.getResources()
70                 .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
71     }
72 
73     @Override
createControlsRowAndPresenter()74     public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
75         PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
76         setControlsRow(controlsRow);
77         AbstractDetailsDescriptionPresenter detailsPresenter =
78                 new AbstractDetailsDescriptionPresenter() {
79             @Override
80             protected void onBindDescription(
81                     AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) {
82                 PlaybackControlGlue glue = (PlaybackControlGlue) object;
83                 if (glue.hasValidMedia()) {
84                     viewHolder.getTitle().setText(glue.getMediaTitle());
85                     viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
86                 } else {
87                     viewHolder.getTitle().setText("");
88                     viewHolder.getSubtitle().setText("");
89                 }
90                 if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) {
91                     viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(),
92                             mExtraPaddingTopForNoDescription,
93                             viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom());
94                 }
95             }
96         };
97         PlaybackControlsRowPresenter presenter =
98                 new PlaybackControlsRowPresenter(detailsPresenter) {
99             @Override
100             protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
101                 super.onBindRowViewHolder(vh, item);
102                 vh.setOnKeyListener(DvrPlaybackControlHelper.this);
103             }
104 
105             @Override
106             protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
107                 super.onUnbindRowViewHolder(vh);
108                 vh.setOnKeyListener(null);
109             }
110         };
111         presenter.setProgressColor(getContext().getResources()
112                 .getColor(R.color.play_controls_progress_bar_watched));
113         presenter.setBackgroundColor(getContext().getResources()
114                 .getColor(R.color.play_controls_body_background_enabled));
115         presenter.setOnActionClickedListener(new OnActionClickedListener() {
116             @Override
117             public void onActionClicked(Action action) {
118                 if (mReadyToControl) {
119                     DvrPlaybackControlHelper.super.onActionClicked(action);
120                 }
121             }
122         });
123         return presenter;
124     }
125 
126     @Override
onKey(View v, int keyCode, KeyEvent event)127     public boolean onKey(View v, int keyCode, KeyEvent event) {
128         if (mReadyToControl) {
129             if (keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE && event.getAction() == KeyEvent.ACTION_DOWN
130                     && (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
131                     || mPlaybackState == PlaybackState.STATE_REWINDING)) {
132                 // Workaround of b/31489271. Clicks play/pause button first to reset play controls
133                 // to "play" state. Then we can pass MEDIA_PAUSE to let playback be paused.
134                 onActionClicked(getControlsRow().getActionForKeyCode(keyCode));
135             }
136             return super.onKey(v, keyCode, event);
137         }
138         return false;
139     }
140 
141     @Override
hasValidMedia()142     public boolean hasValidMedia() {
143         PlaybackState playbackState = mMediaController.getPlaybackState();
144         return playbackState != null;
145     }
146 
147     @Override
isMediaPlaying()148     public boolean isMediaPlaying() {
149         PlaybackState playbackState = mMediaController.getPlaybackState();
150         if (playbackState == null) {
151             return false;
152         }
153         int state = playbackState.getState();
154         return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING
155                 && state != PlaybackState.STATE_PAUSED;
156     }
157 
158     /**
159      * Returns the ID of the media under playback.
160      */
getMediaId()161     public long getMediaId() {
162         MediaMetadata mediaMetadata = mMediaController.getMetadata();
163         return mediaMetadata == null ? UNKNOWN_MEDIA_ID
164                 : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID);
165     }
166 
167     @Override
getMediaTitle()168     public CharSequence getMediaTitle() {
169         MediaMetadata mediaMetadata = mMediaController.getMetadata();
170         return mediaMetadata == null ? ""
171                 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
172     }
173 
174     @Override
getMediaSubtitle()175     public CharSequence getMediaSubtitle() {
176         MediaMetadata mediaMetadata = mMediaController.getMetadata();
177         return mediaMetadata == null ? ""
178                 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE);
179     }
180 
181     @Override
getMediaDuration()182     public int getMediaDuration() {
183         MediaMetadata mediaMetadata = mMediaController.getMetadata();
184         return mediaMetadata == null ? 0
185                 : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
186     }
187 
188     @Override
getMediaArt()189     public Drawable getMediaArt() {
190         // Do not show the poster art on control row.
191         return null;
192     }
193 
194     @Override
getSupportedActions()195     public long getSupportedActions() {
196         return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
197     }
198 
199     @Override
getCurrentSpeedId()200     public int getCurrentSpeedId() {
201         return mPlaybackSpeedId;
202     }
203 
204     @Override
getCurrentPosition()205     public int getCurrentPosition() {
206         PlaybackState playbackState = mMediaController.getPlaybackState();
207         if (playbackState == null) {
208             return 0;
209         }
210         return (int) playbackState.getPosition();
211     }
212 
213     /**
214      * Unregister media controller's callback.
215      */
unregisterCallback()216     public void unregisterCallback() {
217         mMediaController.unregisterCallback(mMediaControllerCallback);
218     }
219 
220     @Override
startPlayback(int speedId)221     protected void startPlayback(int speedId) {
222         if (getCurrentSpeedId() == speedId) {
223             return;
224         }
225         if (speedId == PLAYBACK_SPEED_NORMAL) {
226             mTransportControls.play();
227         } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) {
228             mTransportControls.rewind();
229         } else if (speedId >= PLAYBACK_SPEED_FAST_L0){
230             mTransportControls.fastForward();
231         }
232     }
233 
234     @Override
pausePlayback()235     protected void pausePlayback() {
236         mTransportControls.pause();
237     }
238 
239     @Override
skipToNext()240     protected void skipToNext() {
241         // Do nothing.
242     }
243 
244     @Override
skipToPrevious()245     protected void skipToPrevious() {
246         // Do nothing.
247     }
248 
249     @Override
onRowChanged(PlaybackControlsRow row)250     protected void onRowChanged(PlaybackControlsRow row) {
251         // Do nothing.
252     }
253 
onStateChanged(int state, long positionMs, int speedLevel)254     private void onStateChanged(int state, long positionMs, int speedLevel) {
255         if (DEBUG) Log.d(TAG, "onStateChanged");
256         getControlsRow().setCurrentTime((int) positionMs);
257         if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) {
258             // Only position is changed, no need to update controls row
259             return;
260         }
261         // NOTICE: The below two variables should only be used in this method.
262         // The only usage of them is to confirm if the state is changed or not.
263         mPlaybackState = state;
264         mPlaybackSpeedLevel = speedLevel;
265         switch (state) {
266             case PlaybackState.STATE_PLAYING:
267                 mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL;
268                 setFadingEnabled(true);
269                 mReadyToControl = true;
270                 break;
271             case PlaybackState.STATE_PAUSED:
272                 mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED;
273                 setFadingEnabled(true);
274                 mReadyToControl = true;
275                 break;
276             case PlaybackState.STATE_FAST_FORWARDING:
277                 mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel;
278                 setFadingEnabled(false);
279                 mReadyToControl = true;
280                 break;
281             case PlaybackState.STATE_REWINDING:
282                 mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel;
283                 setFadingEnabled(false);
284                 mReadyToControl = true;
285                 break;
286             case PlaybackState.STATE_CONNECTING:
287                 setFadingEnabled(false);
288                 mReadyToControl = false;
289                 break;
290             case PlaybackState.STATE_NONE:
291                 mReadyToControl = false;
292                 break;
293             default:
294                 setFadingEnabled(true);
295                 break;
296         }
297         onStateChanged();
298     }
299 
300     private class MediaControllerCallback extends MediaController.Callback {
301         @Override
onPlaybackStateChanged(PlaybackState state)302         public void onPlaybackStateChanged(PlaybackState state) {
303             if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState());
304             onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed());
305         }
306 
307         @Override
onMetadataChanged(MediaMetadata metadata)308         public void onMetadataChanged(MediaMetadata metadata) {
309             DvrPlaybackControlHelper.this.onMetadataChanged();
310             ((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated();
311         }
312     }
313 }