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 androidx.leanback.media;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.View;
26 
27 import androidx.annotation.RestrictTo;
28 import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
29 import androidx.leanback.widget.Action;
30 import androidx.leanback.widget.ArrayObjectAdapter;
31 import androidx.leanback.widget.ControlButtonPresenterSelector;
32 import androidx.leanback.widget.OnActionClickedListener;
33 import androidx.leanback.widget.PlaybackControlsRow;
34 import androidx.leanback.widget.PlaybackControlsRowPresenter;
35 import androidx.leanback.widget.PlaybackRowPresenter;
36 import androidx.leanback.widget.PresenterSelector;
37 import androidx.leanback.widget.RowPresenter;
38 import androidx.leanback.widget.SparseArrayObjectAdapter;
39 
40 import java.lang.ref.WeakReference;
41 import java.util.List;
42 
43 /**
44  * A helper class for managing a {@link PlaybackControlsRow}
45  * and {@link PlaybackGlueHost} that implements a
46  * recommended approach to handling standard playback control actions such as play/pause,
47  * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
48  * is a glue layer in that manages the configuration of and interaction between the
49  * leanback UI components by defining a functional interface to the media player.
50  *
51  * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
52  * subclass this abstract helper.  To create a subclass you must implement all of the
53  * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
54  * {@link #onStateChanged()} appropriately.
55  * </p>
56  *
57  * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
58  * inform the glue what speed levels are supported for fast forward/rewind.
59  * </p>
60  *
61  * <p>You may override {@link #onCreateControlsRowAndPresenter()} which will create a
62  * {@link PlaybackControlsRow} and a {@link PlaybackControlsRowPresenter}. You may call
63  * {@link #setControlsRow(PlaybackControlsRow)} and
64  * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to customize your own row and presenter.
65  * </p>
66  *
67  * <p>The helper sets a {@link SparseArrayObjectAdapter}
68  * on the controls row as the primary actions adapter, and adds actions to it. You can provide
69  * additional actions by overriding {@link #onCreatePrimaryActions}. This helper does not
70  * deal in secondary actions so those you may add separately.
71  * </p>
72  *
73  * <p>Provide a click listener on your fragment and if an action is clicked, call
74  * {@link #onActionClicked}.
75  * </p>
76  *
77  * <p>This helper implements a key event handler. If you pass a
78  * {@link PlaybackGlueHost}, it will configure its
79  * fragment to intercept all key events.  Otherwise, you should set the glue object as key event
80  * handler to the ViewHolder when bound by your row presenter; see
81  * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
82  * </p>
83  *
84  * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
85  * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
86  * {@link #getUpdatePeriod()} provides a recommended update period.
87  * </p>
88  *
89  */
90 public abstract class PlaybackControlGlue extends PlaybackGlue
91         implements OnActionClickedListener, View.OnKeyListener {
92     /**
93      * The adapter key for the first custom control on the left side
94      * of the predefined primary controls.
95      */
96     public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
97 
98     /**
99      * The adapter key for the skip to previous control.
100      */
101     public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
102 
103     /**
104      * The adapter key for the rewind control.
105      */
106     public static final int ACTION_REWIND = 0x20;
107 
108     /**
109      * The adapter key for the play/pause control.
110      */
111     public static final int ACTION_PLAY_PAUSE = 0x40;
112 
113     /**
114      * The adapter key for the fast forward control.
115      */
116     public static final int ACTION_FAST_FORWARD = 0x80;
117 
118     /**
119      * The adapter key for the skip to next control.
120      */
121     public static final int ACTION_SKIP_TO_NEXT = 0x100;
122 
123     /**
124      * The adapter key for the first custom control on the right side
125      * of the predefined primary controls.
126      */
127     public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
128 
129     /**
130      * Invalid playback speed.
131      */
132     public static final int PLAYBACK_SPEED_INVALID = -1;
133 
134     /**
135      * Speed representing playback state that is paused.
136      */
137     public static final int PLAYBACK_SPEED_PAUSED = 0;
138 
139     /**
140      * Speed representing playback state that is playing normally.
141      */
142     public static final int PLAYBACK_SPEED_NORMAL = 1;
143 
144     /**
145      * The initial (level 0) fast forward playback speed.
146      * The negative of this value is for rewind at the same speed.
147      */
148     public static final int PLAYBACK_SPEED_FAST_L0 = 10;
149 
150     /**
151      * The level 1 fast forward playback speed.
152      * The negative of this value is for rewind at the same speed.
153      */
154     public static final int PLAYBACK_SPEED_FAST_L1 = 11;
155 
156     /**
157      * The level 2 fast forward playback speed.
158      * The negative of this value is for rewind at the same speed.
159      */
160     public static final int PLAYBACK_SPEED_FAST_L2 = 12;
161 
162     /**
163      * The level 3 fast forward playback speed.
164      * The negative of this value is for rewind at the same speed.
165      */
166     public static final int PLAYBACK_SPEED_FAST_L3 = 13;
167 
168     /**
169      * The level 4 fast forward playback speed.
170      * The negative of this value is for rewind at the same speed.
171      */
172     public static final int PLAYBACK_SPEED_FAST_L4 = 14;
173 
174     static final String TAG = "PlaybackControlGlue";
175     static final boolean DEBUG = false;
176 
177     static final int MSG_UPDATE_PLAYBACK_STATE = 100;
178     private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
179     private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
180             - PLAYBACK_SPEED_FAST_L0 + 1;
181 
182     private final int[] mFastForwardSpeeds;
183     private final int[] mRewindSpeeds;
184     private PlaybackControlsRow mControlsRow;
185     private PlaybackRowPresenter mControlsRowPresenter;
186     private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
187     private PlaybackControlsRow.SkipNextAction mSkipNextAction;
188     private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
189     private PlaybackControlsRow.FastForwardAction mFastForwardAction;
190     private PlaybackControlsRow.RewindAction mRewindAction;
191     private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
192     private boolean mFadeWhenPlaying = true;
193 
194     static class UpdatePlaybackStateHandler extends Handler {
195         @Override
handleMessage(Message msg)196         public void handleMessage(Message msg) {
197             if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
198                 PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
199                 if (glue != null) {
200                     glue.updatePlaybackState();
201                 }
202             }
203         }
204     }
205 
206     static final Handler sHandler = new UpdatePlaybackStateHandler();
207 
208     final WeakReference<PlaybackControlGlue> mGlueWeakReference =  new WeakReference(this);
209 
210     /**
211      * Constructor for the glue.
212      *
213      * @param context
214      * @param seekSpeeds Array of seek speeds for fast forward and rewind.
215      */
PlaybackControlGlue(Context context, int[] seekSpeeds)216     public PlaybackControlGlue(Context context, int[] seekSpeeds) {
217         this(context, seekSpeeds, seekSpeeds);
218     }
219 
220     /**
221      * Constructor for the glue.
222      *
223      * @param context
224      * @param fastForwardSpeeds Array of seek speeds for fast forward.
225      * @param rewindSpeeds Array of seek speeds for rewind.
226      */
PlaybackControlGlue(Context context, int[] fastForwardSpeeds, int[] rewindSpeeds)227     public PlaybackControlGlue(Context context,
228                                int[] fastForwardSpeeds,
229                                int[] rewindSpeeds) {
230         super(context);
231         if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
232             throw new IllegalStateException("invalid fastForwardSpeeds array size");
233         }
234         mFastForwardSpeeds = fastForwardSpeeds;
235         if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
236             throw new IllegalStateException("invalid rewindSpeeds array size");
237         }
238         mRewindSpeeds = rewindSpeeds;
239     }
240 
241     @Override
onAttachedToHost(PlaybackGlueHost host)242     protected void onAttachedToHost(PlaybackGlueHost host) {
243         super.onAttachedToHost(host);
244         host.setOnKeyInterceptListener(this);
245         host.setOnActionClickedListener(this);
246         if (getControlsRow() == null || getPlaybackRowPresenter() == null) {
247             onCreateControlsRowAndPresenter();
248         }
249         host.setPlaybackRowPresenter(getPlaybackRowPresenter());
250         host.setPlaybackRow(getControlsRow());
251     }
252 
253     @Override
onHostStart()254     protected void onHostStart() {
255         enableProgressUpdating(true);
256     }
257 
258     @Override
onHostStop()259     protected void onHostStop() {
260         enableProgressUpdating(false);
261     }
262 
263     @Override
onDetachedFromHost()264     protected void onDetachedFromHost() {
265         enableProgressUpdating(false);
266         super.onDetachedFromHost();
267     }
268 
269     /**
270      * Instantiating a {@link PlaybackControlsRow} and corresponding
271      * {@link PlaybackControlsRowPresenter}. Subclass may override.
272      */
onCreateControlsRowAndPresenter()273     protected void onCreateControlsRowAndPresenter() {
274         if (getControlsRow() == null) {
275             PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
276             setControlsRow(controlsRow);
277         }
278         if (getPlaybackRowPresenter() == null) {
279             final AbstractDetailsDescriptionPresenter detailsPresenter =
280                     new AbstractDetailsDescriptionPresenter() {
281                         @Override
282                         protected void onBindDescription(ViewHolder
283                                 viewHolder, Object object) {
284                             PlaybackControlGlue glue = (PlaybackControlGlue) object;
285                             if (glue.hasValidMedia()) {
286                                 viewHolder.getTitle().setText(glue.getMediaTitle());
287                                 viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
288                             } else {
289                                 viewHolder.getTitle().setText("");
290                                 viewHolder.getSubtitle().setText("");
291                             }
292                         }
293                     };
294 
295             setPlaybackRowPresenter(new PlaybackControlsRowPresenter(detailsPresenter) {
296                 @Override
297                 protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
298                     super.onBindRowViewHolder(vh, item);
299                     vh.setOnKeyListener(PlaybackControlGlue.this);
300                 }
301                 @Override
302                 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
303                     super.onUnbindRowViewHolder(vh);
304                     vh.setOnKeyListener(null);
305                 }
306             });
307         }
308     }
309 
310     /**
311      * Returns the fast forward speeds.
312      */
getFastForwardSpeeds()313     public int[] getFastForwardSpeeds() {
314         return mFastForwardSpeeds;
315     }
316 
317     /**
318      * Returns the rewind speeds.
319      */
getRewindSpeeds()320     public int[] getRewindSpeeds() {
321         return mRewindSpeeds;
322     }
323 
324     /**
325      * Sets the controls to fade after a timeout when media is playing.
326      */
setFadingEnabled(boolean enable)327     public void setFadingEnabled(boolean enable) {
328         mFadeWhenPlaying = enable;
329         if (!mFadeWhenPlaying && getHost() != null) {
330             getHost().setControlsOverlayAutoHideEnabled(false);
331         }
332     }
333 
334     /**
335      * Returns true if controls are set to fade when media is playing.
336      */
isFadingEnabled()337     public boolean isFadingEnabled() {
338         return mFadeWhenPlaying;
339     }
340 
341     /**
342      * Sets the controls row to be managed by the glue layer.
343      * The primary actions and playback state related aspects of the row
344      * are updated by the glue.
345      */
setControlsRow(PlaybackControlsRow controlsRow)346     public void setControlsRow(PlaybackControlsRow controlsRow) {
347         mControlsRow = controlsRow;
348         mControlsRow.setPrimaryActionsAdapter(
349                 createPrimaryActionsAdapter(new ControlButtonPresenterSelector()));
350         // Add secondary actions
351         ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
352                 new ControlButtonPresenterSelector());
353         onCreateSecondaryActions(secondaryActions);
354         getControlsRow().setSecondaryActionsAdapter(secondaryActions);
355         updateControlsRow();
356     }
357 
358     /**
359      * @hide
360      */
361     @RestrictTo(RestrictTo.Scope.LIBRARY)
createPrimaryActionsAdapter( PresenterSelector presenterSelector)362     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
363             PresenterSelector presenterSelector) {
364         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
365         onCreatePrimaryActions(adapter);
366         return adapter;
367     }
368 
369     /**
370      * Sets the controls row Presenter to be managed by the glue layer.
371      * @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
372      * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)}.
373      */
374     @Deprecated
setControlsRowPresenter(PlaybackControlsRowPresenter presenter)375     public void setControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
376         mControlsRowPresenter = presenter;
377     }
378 
379     /**
380      * Returns the playback controls row managed by the glue layer.
381      */
getControlsRow()382     public PlaybackControlsRow getControlsRow() {
383         return mControlsRow;
384     }
385 
386     /**
387      * Returns the playback controls row Presenter managed by the glue layer.
388      * @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
389      * {@link #getPlaybackRowPresenter()}.
390      */
391     @Deprecated
getControlsRowPresenter()392     public PlaybackControlsRowPresenter getControlsRowPresenter() {
393         return mControlsRowPresenter instanceof PlaybackControlsRowPresenter
394                 ? (PlaybackControlsRowPresenter) mControlsRowPresenter : null;
395     }
396 
397     /**
398      * Sets the controls row Presenter to be passed to {@link PlaybackGlueHost} in
399      * {@link #onAttachedToHost(PlaybackGlueHost)}.
400      */
setPlaybackRowPresenter(PlaybackRowPresenter presenter)401     public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
402         mControlsRowPresenter = presenter;
403     }
404 
405     /**
406      * Returns the playback row Presenter to be passed to {@link PlaybackGlueHost} in
407      * {@link #onAttachedToHost(PlaybackGlueHost)}.
408      */
getPlaybackRowPresenter()409     public PlaybackRowPresenter getPlaybackRowPresenter() {
410         return mControlsRowPresenter;
411     }
412 
413     /**
414      * Override this to start/stop a runnable to call {@link #updateProgress} at
415      * an interval such as {@link #getUpdatePeriod}.
416      */
enableProgressUpdating(boolean enable)417     public void enableProgressUpdating(boolean enable) {
418     }
419 
420     /**
421      * Returns the time period in milliseconds that should be used
422      * to update the progress.  See {@link #updateProgress()}.
423      */
getUpdatePeriod()424     public int getUpdatePeriod() {
425         // TODO: calculate a better update period based on total duration and screen size
426         return 500;
427     }
428 
429     /**
430      * Updates the progress bar based on the current media playback position.
431      */
updateProgress()432     public void updateProgress() {
433         int position = getCurrentPosition();
434         if (DEBUG) Log.v(TAG, "updateProgress " + position);
435         if (mControlsRow != null) {
436             mControlsRow.setCurrentTime(position);
437         }
438     }
439 
440     /**
441      * Handles action clicks.  A subclass may override this add support for additional actions.
442      */
443     @Override
onActionClicked(Action action)444     public void onActionClicked(Action action) {
445         dispatchAction(action, null);
446     }
447 
448     /**
449      * Handles key events and returns true if handled.  A subclass may override this to provide
450      * additional support.
451      */
452     @Override
onKey(View v, int keyCode, KeyEvent event)453     public boolean onKey(View v, int keyCode, KeyEvent event) {
454         switch (keyCode) {
455             case KeyEvent.KEYCODE_DPAD_UP:
456             case KeyEvent.KEYCODE_DPAD_DOWN:
457             case KeyEvent.KEYCODE_DPAD_RIGHT:
458             case KeyEvent.KEYCODE_DPAD_LEFT:
459             case KeyEvent.KEYCODE_BACK:
460             case KeyEvent.KEYCODE_ESCAPE:
461                 boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
462                         || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
463                 if (abortSeek) {
464                     mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
465                     play(mPlaybackSpeed);
466                     updatePlaybackStatusAfterUserAction();
467                     return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
468                 }
469                 return false;
470         }
471         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
472                 mControlsRow.getPrimaryActionsAdapter();
473         Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
474 
475         if (action != null) {
476             if (action == primaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
477                     || action == primaryActionsAdapter.lookup(ACTION_REWIND)
478                     || action == primaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
479                     || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
480                     || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
481                 if (event.getAction() == KeyEvent.ACTION_DOWN) {
482                     dispatchAction(action, event);
483                 }
484                 return true;
485             }
486         }
487         return false;
488     }
489 
490     /**
491      * Called when the given action is invoked, either by click or keyevent.
492      */
dispatchAction(Action action, KeyEvent keyEvent)493     boolean dispatchAction(Action action, KeyEvent keyEvent) {
494         boolean handled = false;
495         if (action == mPlayPauseAction) {
496             boolean canPlay = keyEvent == null
497                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
498                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
499             boolean canPause = keyEvent == null
500                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
501                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
502             //            PLAY_PAUSE    PLAY      PAUSE
503             // playing    paused                  paused
504             // paused     playing       playing
505             // ff/rw      playing       playing   paused
506             if (canPause
507                     && (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
508                         mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
509                 mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
510                 pause();
511             } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
512                 mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
513                 play(mPlaybackSpeed);
514             }
515             updatePlaybackStatusAfterUserAction();
516             handled = true;
517         } else if (action == mSkipNextAction) {
518             next();
519             handled = true;
520         } else if (action == mSkipPreviousAction) {
521             previous();
522             handled = true;
523         } else if (action == mFastForwardAction) {
524             if (mPlaybackSpeed < getMaxForwardSpeedId()) {
525                 switch (mPlaybackSpeed) {
526                     case PLAYBACK_SPEED_FAST_L0:
527                     case PLAYBACK_SPEED_FAST_L1:
528                     case PLAYBACK_SPEED_FAST_L2:
529                     case PLAYBACK_SPEED_FAST_L3:
530                         mPlaybackSpeed++;
531                         break;
532                     default:
533                         mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
534                         break;
535                 }
536                 play(mPlaybackSpeed);
537                 updatePlaybackStatusAfterUserAction();
538             }
539             handled = true;
540         } else if (action == mRewindAction) {
541             if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
542                 switch (mPlaybackSpeed) {
543                     case -PLAYBACK_SPEED_FAST_L0:
544                     case -PLAYBACK_SPEED_FAST_L1:
545                     case -PLAYBACK_SPEED_FAST_L2:
546                     case -PLAYBACK_SPEED_FAST_L3:
547                         mPlaybackSpeed--;
548                         break;
549                     default:
550                         mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
551                         break;
552                 }
553                 play(mPlaybackSpeed);
554                 updatePlaybackStatusAfterUserAction();
555             }
556             handled = true;
557         }
558         return handled;
559     }
560 
getMaxForwardSpeedId()561     private int getMaxForwardSpeedId() {
562         return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
563     }
564 
getMaxRewindSpeedId()565     private int getMaxRewindSpeedId() {
566         return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
567     }
568 
updateControlsRow()569     private void updateControlsRow() {
570         updateRowMetadata();
571         updateControlButtons();
572         sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
573         updatePlaybackState();
574     }
575 
updatePlaybackStatusAfterUserAction()576     private void updatePlaybackStatusAfterUserAction() {
577         updatePlaybackState(mPlaybackSpeed);
578         // Sync playback state after a delay
579         sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
580         sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
581                 mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
582     }
583 
584     /**
585      * Start playback at the given speed.
586      *
587      * @param speed The desired playback speed.  For normal playback this will be
588      *              {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
589      *              and negative values for rewind.
590      */
play(int speed)591     public void play(int speed) {
592     }
593 
594     @Override
play()595     public final void play() {
596         play(PLAYBACK_SPEED_NORMAL);
597     }
598 
updateRowMetadata()599     private void updateRowMetadata() {
600         if (mControlsRow == null) {
601             return;
602         }
603 
604         if (DEBUG) Log.v(TAG, "updateRowMetadata");
605 
606         if (!hasValidMedia()) {
607             mControlsRow.setImageDrawable(null);
608             mControlsRow.setTotalTime(0);
609             mControlsRow.setCurrentTime(0);
610         } else {
611             mControlsRow.setImageDrawable(getMediaArt());
612             mControlsRow.setTotalTime(getMediaDuration());
613             mControlsRow.setCurrentTime(getCurrentPosition());
614         }
615 
616         if (getHost() != null) {
617             getHost().notifyPlaybackRowChanged();
618         }
619     }
620 
updatePlaybackState()621     void updatePlaybackState() {
622         if (hasValidMedia()) {
623             mPlaybackSpeed = getCurrentSpeedId();
624             updatePlaybackState(mPlaybackSpeed);
625         }
626     }
627 
updateControlButtons()628     void updateControlButtons() {
629         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
630                 getControlsRow().getPrimaryActionsAdapter();
631         final long actions = getSupportedActions();
632         if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0 && mSkipPreviousAction == null) {
633             mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
634             primaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
635         } else if ((actions & ACTION_SKIP_TO_PREVIOUS) == 0 && mSkipPreviousAction != null) {
636             primaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
637             mSkipPreviousAction = null;
638         }
639         if ((actions & ACTION_REWIND) != 0 && mRewindAction == null) {
640             mRewindAction = new PlaybackControlsRow.RewindAction(getContext(),
641                     mRewindSpeeds.length);
642             primaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
643         } else if ((actions & ACTION_REWIND) == 0 && mRewindAction != null) {
644             primaryActionsAdapter.clear(ACTION_REWIND);
645             mRewindAction = null;
646         }
647         if ((actions & ACTION_PLAY_PAUSE) != 0 && mPlayPauseAction == null) {
648             mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
649             primaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
650         } else if ((actions & ACTION_PLAY_PAUSE) == 0 && mPlayPauseAction != null) {
651             primaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
652             mPlayPauseAction = null;
653         }
654         if ((actions & ACTION_FAST_FORWARD) != 0 && mFastForwardAction == null) {
655             mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getContext(),
656                     mFastForwardSpeeds.length);
657             primaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
658         } else if ((actions & ACTION_FAST_FORWARD) == 0 && mFastForwardAction != null) {
659             primaryActionsAdapter.clear(ACTION_FAST_FORWARD);
660             mFastForwardAction = null;
661         }
662         if ((actions & ACTION_SKIP_TO_NEXT) != 0 && mSkipNextAction == null) {
663             mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
664             primaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
665         } else if ((actions & ACTION_SKIP_TO_NEXT) == 0 && mSkipNextAction != null) {
666             primaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
667             mSkipNextAction = null;
668         }
669     }
670 
updatePlaybackState(int playbackSpeed)671     private void updatePlaybackState(int playbackSpeed) {
672         if (mControlsRow == null) {
673             return;
674         }
675 
676         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
677                 getControlsRow().getPrimaryActionsAdapter();
678 
679         if (mFastForwardAction != null) {
680             int index = 0;
681             if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
682                 index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
683             }
684             if (mFastForwardAction.getIndex() != index) {
685                 mFastForwardAction.setIndex(index);
686                 notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
687             }
688         }
689         if (mRewindAction != null) {
690             int index = 0;
691             if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
692                 index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
693             }
694             if (mRewindAction.getIndex() != index) {
695                 mRewindAction.setIndex(index);
696                 notifyItemChanged(primaryActionsAdapter, mRewindAction);
697             }
698         }
699 
700         if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
701             updateProgress();
702             enableProgressUpdating(false);
703         } else {
704             enableProgressUpdating(true);
705         }
706 
707         if (mFadeWhenPlaying && getHost() != null) {
708             getHost().setControlsOverlayAutoHideEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
709         }
710 
711         if (mPlayPauseAction != null) {
712             int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
713                     ? PlaybackControlsRow.PlayPauseAction.INDEX_PLAY
714                     : PlaybackControlsRow.PlayPauseAction.INDEX_PAUSE;
715             if (mPlayPauseAction.getIndex() != index) {
716                 mPlayPauseAction.setIndex(index);
717                 notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
718             }
719         }
720         List<PlayerCallback> callbacks = getPlayerCallbacks();
721         if (callbacks != null) {
722             for (int i = 0, size = callbacks.size(); i < size; i++) {
723                 callbacks.get(i).onPlayStateChanged(this);
724             }
725         }
726     }
727 
notifyItemChanged(SparseArrayObjectAdapter adapter, Object object)728     private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
729         int index = adapter.indexOf(object);
730         if (index >= 0) {
731             adapter.notifyArrayItemRangeChanged(index, 1);
732         }
733     }
734 
getSpeedString(int speed)735     private static String getSpeedString(int speed) {
736         switch (speed) {
737             case PLAYBACK_SPEED_INVALID:
738                 return "PLAYBACK_SPEED_INVALID";
739             case PLAYBACK_SPEED_PAUSED:
740                 return "PLAYBACK_SPEED_PAUSED";
741             case PLAYBACK_SPEED_NORMAL:
742                 return "PLAYBACK_SPEED_NORMAL";
743             case PLAYBACK_SPEED_FAST_L0:
744                 return "PLAYBACK_SPEED_FAST_L0";
745             case PLAYBACK_SPEED_FAST_L1:
746                 return "PLAYBACK_SPEED_FAST_L1";
747             case PLAYBACK_SPEED_FAST_L2:
748                 return "PLAYBACK_SPEED_FAST_L2";
749             case PLAYBACK_SPEED_FAST_L3:
750                 return "PLAYBACK_SPEED_FAST_L3";
751             case PLAYBACK_SPEED_FAST_L4:
752                 return "PLAYBACK_SPEED_FAST_L4";
753             case -PLAYBACK_SPEED_FAST_L0:
754                 return "-PLAYBACK_SPEED_FAST_L0";
755             case -PLAYBACK_SPEED_FAST_L1:
756                 return "-PLAYBACK_SPEED_FAST_L1";
757             case -PLAYBACK_SPEED_FAST_L2:
758                 return "-PLAYBACK_SPEED_FAST_L2";
759             case -PLAYBACK_SPEED_FAST_L3:
760                 return "-PLAYBACK_SPEED_FAST_L3";
761             case -PLAYBACK_SPEED_FAST_L4:
762                 return "-PLAYBACK_SPEED_FAST_L4";
763         }
764         return null;
765     }
766 
767     /**
768      * Returns true if there is a valid media item.
769      */
hasValidMedia()770     public abstract boolean hasValidMedia();
771 
772     /**
773      * Returns true if media is currently playing.
774      */
isMediaPlaying()775     public abstract boolean isMediaPlaying();
776 
777     @Override
isPlaying()778     public boolean isPlaying() {
779         return isMediaPlaying();
780     }
781 
782     /**
783      * Returns the title of the media item.
784      */
getMediaTitle()785     public abstract CharSequence getMediaTitle();
786 
787     /**
788      * Returns the subtitle of the media item.
789      */
getMediaSubtitle()790     public abstract CharSequence getMediaSubtitle();
791 
792     /**
793      * Returns the duration of the media item in milliseconds.
794      */
getMediaDuration()795     public abstract int getMediaDuration();
796 
797     /**
798      * Returns a bitmap of the art for the media item.
799      */
getMediaArt()800     public abstract Drawable getMediaArt();
801 
802     /**
803      * Returns a bitmask of actions supported by the media player.
804      */
getSupportedActions()805     public abstract long getSupportedActions();
806 
807     /**
808      * Returns the current playback speed.  When playing normally,
809      * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
810      */
getCurrentSpeedId()811     public abstract int getCurrentSpeedId();
812 
813     /**
814      * Returns the current position of the media item in milliseconds.
815      */
getCurrentPosition()816     public abstract int getCurrentPosition();
817 
818     /**
819      * May be overridden to add primary actions to the adapter.
820      *
821      * @param primaryActionsAdapter The adapter to add primary {@link Action}s.
822      */
onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter)823     protected void onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter) {
824     }
825 
826     /**
827      * May be overridden to add secondary actions to the adapter.
828      *
829      * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
830      */
onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter)831     protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
832     }
833 
834     /**
835      * Must be called appropriately by a subclass when the playback state has changed.
836      * It updates the playback state displayed on the media player.
837      */
onStateChanged()838     protected void onStateChanged() {
839         if (DEBUG) Log.v(TAG, "onStateChanged");
840         // If a pending control button update is present, delay
841         // the update until the state settles.
842         if (!hasValidMedia()) {
843             return;
844         }
845         if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
846             sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
847             if (getCurrentSpeedId() != mPlaybackSpeed) {
848                 if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
849                 sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
850                         mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
851             } else {
852                 if (DEBUG) Log.v(TAG, "Update state matches expectation");
853                 updatePlaybackState();
854             }
855         } else {
856             updatePlaybackState();
857         }
858     }
859 
860     /**
861      * Must be called appropriately by a subclass when the metadata state has changed.
862      */
onMetadataChanged()863     protected void onMetadataChanged() {
864         if (DEBUG) Log.v(TAG, "onMetadataChanged");
865         updateRowMetadata();
866     }
867 }
868