PlaybackControlsRow.java revision 60bb6af2e336072921f5d3c3861e86b3cc6241b3
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.support.v17.leanback.R;
17import android.util.TypedValue;
18import android.content.Context;
19import android.content.res.TypedArray;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Paint;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffColorFilter;
27import android.graphics.drawable.BitmapDrawable;
28import android.graphics.drawable.Drawable;
29import android.view.KeyEvent;
30
31/**
32 * A row of playback controls to be displayed by a {@link PlaybackControlsRowPresenter}.
33 *
34 * This row consists of some optional item detail, a series of primary actions,
35 * and an optional series of secondary actions.
36 *
37 * Controls are specified via an {@link ObjectAdapter} containing one or more
38 * {@link Action}s.
39 *
40 * Adapters should have their {@link PresenterSelector} set to an instance of
41 * {@link ControlButtonPresenterSelector}.
42 *
43 */
44public class PlaybackControlsRow extends Row {
45
46    /**
47     * Base class for an action comprised of a series of icons.
48     */
49    public static abstract class MultiAction extends Action {
50        private int mIndex;
51        private Drawable[] mDrawables;
52        private String[] mLabels;
53        private String[] mLabels2;
54
55        /**
56         * Constructor
57         * @param id The id of the Action.
58         */
59        public MultiAction(int id) {
60            super(id);
61        }
62
63        /**
64         * Sets the array of drawables.  The size of the array defines the range
65         * of valid indices for this action.
66         */
67        public void setDrawables(Drawable[] drawables) {
68            mDrawables = drawables;
69            setIndex(0);
70        }
71
72        /**
73         * Sets the array of strings used as labels.  The size of the array defines the range
74         * of valid indices for this action.  The labels are used to define the accessibility
75         * content description unless secondary labels are provided.
76         */
77        public void setLabels(String[] labels) {
78            mLabels = labels;
79            setIndex(0);
80        }
81
82        /**
83         * Sets the array of strings used as secondary labels.  These labels are used
84         * in place of the primary labels for accessibility content description only.
85         */
86        public void setSecondaryLabels(String[] labels) {
87            mLabels2 = labels;
88            setIndex(0);
89        }
90
91        /**
92         * Returns the number of actions.
93         */
94        public int getActionCount() {
95            if (mDrawables != null) {
96                return mDrawables.length;
97            }
98            if (mLabels != null) {
99                return mLabels.length;
100            }
101            return 0;
102        }
103
104        /**
105         * Returns the drawable at the given index.
106         */
107        public Drawable getDrawable(int index) {
108            return mDrawables == null ? null : mDrawables[index];
109        }
110
111        /**
112         * Returns the label at the given index.
113         */
114        public String getLabel(int index) {
115            return mLabels == null ? null : mLabels[index];
116        }
117
118        /**
119         * Returns the secondary label at the given index.
120         */
121        public String getSecondaryLabel(int index) {
122            return mLabels2 == null ? null : mLabels2[index];
123        }
124
125        /**
126         * Increments the index, wrapping to zero once the end is reached.
127         */
128        public void nextIndex() {
129            setIndex(mIndex < getActionCount() - 1 ? mIndex + 1 : 0);
130        }
131
132        /**
133         * Sets the current index.
134         */
135        public void setIndex(int index) {
136            mIndex = index;
137            if (mDrawables != null) {
138                setIcon(mDrawables[mIndex]);
139            }
140            if (mLabels != null) {
141                setLabel1(mLabels[mIndex]);
142            }
143            if (mLabels2 != null) {
144                setLabel2(mLabels2[mIndex]);
145            }
146        }
147
148        /**
149         * Gets the current index.
150         */
151        public int getIndex() {
152            return mIndex;
153        }
154    }
155
156    /**
157     * An action displaying icons for play and pause.
158     */
159    public static class PlayPauseAction extends MultiAction {
160        /**
161         * Action index for the play icon.
162         */
163        public static int PLAY = 0;
164
165        /**
166         * Action index for the pause icon.
167         */
168        public static int PAUSE = 1;
169
170        /**
171         * Constructor
172         * @param context Context used for loading resources.
173         */
174        public PlayPauseAction(Context context) {
175            super(R.id.lb_control_play_pause);
176            Drawable[] drawables = new Drawable[2];
177            drawables[PLAY] = getStyledDrawable(context,
178                    R.styleable.lbPlaybackControlsActionIcons_play);
179            drawables[PAUSE] = getStyledDrawable(context,
180                    R.styleable.lbPlaybackControlsActionIcons_pause);
181            setDrawables(drawables);
182
183            String[] labels = new String[drawables.length];
184            labels[PLAY] = context.getString(R.string.lb_playback_controls_play);
185            labels[PAUSE] = context.getString(R.string.lb_playback_controls_pause);
186            setLabels(labels);
187            addKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
188            addKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
189            addKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
190        }
191    }
192
193    /**
194     * An action displaying an icon for fast forward.
195     */
196    public static class FastForwardAction extends MultiAction {
197        /**
198         * Constructor
199         * @param context Context used for loading resources.
200         */
201        public FastForwardAction(Context context) {
202            this(context, 1);
203        }
204
205        /**
206         * Constructor
207         * @param context Context used for loading resources.
208         * @param numSpeeds Number of supported fast forward speeds.
209         */
210        public FastForwardAction(Context context, int numSpeeds) {
211            super(R.id.lb_control_fast_forward);
212
213            if (numSpeeds < 1) {
214                throw new IllegalArgumentException("numSpeeds must be > 0");
215            }
216            Drawable[] drawables = new Drawable[numSpeeds];
217            drawables[0] = getStyledDrawable(context,
218                    R.styleable.lbPlaybackControlsActionIcons_fast_forward);
219            setDrawables(drawables);
220
221            String[] labels = new String[getActionCount()];
222            labels[0] = context.getString(R.string.lb_playback_controls_fast_forward);
223
224            String[] labels2 = new String[getActionCount()];
225            labels2[0] = labels[0];
226
227            for (int i = 1; i < numSpeeds; i++) {
228                int multiplier = i + 1;
229                labels[i] = context.getResources().getString(
230                        R.string.lb_control_display_fast_forward_multiplier, multiplier);
231                labels2[i] = context.getResources().getString(
232                        R.string.lb_playback_controls_fast_forward_multiplier, multiplier);
233            }
234            setLabels(labels);
235            setSecondaryLabels(labels2);
236            addKeyCode(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
237        }
238    }
239
240    /**
241     * An action displaying an icon for rewind.
242     */
243    public static class RewindAction extends MultiAction {
244        /**
245         * Constructor
246         * @param context Context used for loading resources.
247         */
248        public RewindAction(Context context) {
249            this(context, 1);
250        }
251
252        /**
253         * Constructor
254         * @param context Context used for loading resources.
255         * @param numSpeeds Number of supported fast forward speeds.
256         */
257        public RewindAction(Context context, int numSpeeds) {
258            super(R.id.lb_control_fast_rewind);
259
260            if (numSpeeds < 1) {
261                throw new IllegalArgumentException("numSpeeds must be > 0");
262            }
263            Drawable[] drawables = new Drawable[numSpeeds];
264            drawables[0] = getStyledDrawable(context,
265                    R.styleable.lbPlaybackControlsActionIcons_rewind);
266            setDrawables(drawables);
267
268            String[] labels = new String[getActionCount()];
269            labels[0] = context.getString(R.string.lb_playback_controls_rewind);
270
271            String[] labels2 = new String[getActionCount()];
272            labels2[0] = labels[0];
273
274            for (int i = 1; i < numSpeeds; i++) {
275                int multiplier = i + 1;
276                labels[i] = labels[i] = context.getResources().getString(
277                        R.string.lb_control_display_rewind_multiplier, multiplier);
278                labels2[i] = context.getResources().getString(
279                        R.string.lb_playback_controls_rewind_multiplier, multiplier);
280            }
281            setLabels(labels);
282            setSecondaryLabels(labels2);
283            addKeyCode(KeyEvent.KEYCODE_MEDIA_REWIND);
284        }
285    }
286
287    /**
288     * An action displaying an icon for skip next.
289     */
290    public static class SkipNextAction extends Action {
291        /**
292         * Constructor
293         * @param context Context used for loading resources.
294         */
295        public SkipNextAction(Context context) {
296            super(R.id.lb_control_skip_next);
297            setIcon(getStyledDrawable(context,
298                    R.styleable.lbPlaybackControlsActionIcons_skip_next));
299            setLabel1(context.getString(R.string.lb_playback_controls_skip_next));
300            addKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
301        }
302    }
303
304    /**
305     * An action displaying an icon for skip previous.
306     */
307    public static class SkipPreviousAction extends Action {
308        /**
309         * Constructor
310         * @param context Context used for loading resources.
311         */
312        public SkipPreviousAction(Context context) {
313            super(R.id.lb_control_skip_previous);
314            setIcon(getStyledDrawable(context,
315                    R.styleable.lbPlaybackControlsActionIcons_skip_previous));
316            setLabel1(context.getString(R.string.lb_playback_controls_skip_previous));
317            addKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
318        }
319    }
320
321    /**
322     * An action displaying an icon for "more actions".
323     */
324    public static class MoreActions extends Action {
325        /**
326         * Constructor
327         * @param context Context used for loading resources.
328         */
329        public MoreActions(Context context) {
330            super(R.id.lb_control_more_actions);
331            setIcon(context.getResources().getDrawable(R.drawable.lb_ic_more));
332            setLabel1(context.getString(R.string.lb_playback_controls_more_actions));
333        }
334    }
335
336    /**
337     * A base class for displaying a thumbs action.
338     */
339    public static abstract class ThumbsAction extends MultiAction {
340        /**
341         * Action index for the solid thumb icon.
342         */
343        public static int SOLID = 0;
344
345        /**
346         * Action index for the outline thumb icon.
347         */
348        public static int OUTLINE = 1;
349
350        /**
351         * Constructor
352         * @param context Context used for loading resources.
353         */
354        public ThumbsAction(int id, Context context, int solidIconIndex, int outlineIconIndex) {
355            super(id);
356            Drawable[] drawables = new Drawable[2];
357            drawables[SOLID] = getStyledDrawable(context, solidIconIndex);
358            drawables[OUTLINE] = getStyledDrawable(context, outlineIconIndex);
359            setDrawables(drawables);
360        }
361    }
362
363    /**
364     * An action displaying an icon for thumbs up.
365     */
366    public static class ThumbsUpAction extends ThumbsAction {
367        public ThumbsUpAction(Context context) {
368            super(R.id.lb_control_thumbs_up, context,
369                    R.styleable.lbPlaybackControlsActionIcons_thumb_up,
370                    R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
371            String[] labels = new String[getActionCount()];
372            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
373            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_up_outline);
374            setLabels(labels);
375        }
376    }
377
378    /**
379     * An action displaying an icon for thumbs down.
380     */
381    public static class ThumbsDownAction extends ThumbsAction {
382        public ThumbsDownAction(Context context) {
383            super(R.id.lb_control_thumbs_down, context,
384                    R.styleable.lbPlaybackControlsActionIcons_thumb_down,
385                    R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
386            String[] labels = new String[getActionCount()];
387            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
388            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_down_outline);
389            setLabels(labels);
390        }
391    }
392
393    /**
394     * An action for displaying three repeat states: none, one, or all.
395     */
396    public static class RepeatAction extends MultiAction {
397        /**
398         * Action index for the repeat-none icon.
399         */
400        public static int NONE = 0;
401
402        /**
403         * Action index for the repeat-all icon.
404         */
405        public static int ALL = 1;
406
407        /**
408         * Action index for the repeat-one icon.
409         */
410        public static int ONE = 2;
411
412        /**
413         * Constructor
414         * @param context Context used for loading resources.
415         */
416        public RepeatAction(Context context) {
417            this(context, getColorFromTheme(context,
418                    R.attr.playbackControlsIconHighlightColor));
419        }
420
421        /**
422         * Constructor
423         * @param context Context used for loading resources
424         * @param highlightColor Color to display the repeat-all and repeat0one icons.
425         */
426        public RepeatAction(Context context, int highlightColor) {
427            this(context, highlightColor, highlightColor);
428        }
429
430        /**
431         * Constructor
432         * @param context Context used for loading resources
433         * @param repeatAllColor Color to display the repeat-all icon.
434         * @param repeatOneColor Color to display the repeat-one icon.
435         */
436        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
437            super(R.id.lb_control_repeat);
438            Drawable[] drawables = new Drawable[3];
439            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
440                    R.styleable.lbPlaybackControlsActionIcons_repeat);
441            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
442                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
443            drawables[NONE] = repeatDrawable;
444            drawables[ALL] = new BitmapDrawable(context.getResources(),
445                    createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
446            drawables[ONE] = new BitmapDrawable(context.getResources(),
447                    createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
448            setDrawables(drawables);
449
450            String[] labels = new String[drawables.length];
451            // Note, labels denote the action taken when clicked
452            labels[NONE] = context.getString(R.string.lb_playback_controls_repeat_all);
453            labels[ALL] = context.getString(R.string.lb_playback_controls_repeat_one);
454            labels[ONE] = context.getString(R.string.lb_playback_controls_repeat_none);
455            setLabels(labels);
456        }
457    }
458
459    /**
460     * An action for displaying a shuffle icon.
461     */
462    public static class ShuffleAction extends MultiAction {
463        public static int OFF = 0;
464        public static int ON = 1;
465
466        /**
467         * Constructor
468         * @param context Context used for loading resources.
469         */
470        public ShuffleAction(Context context) {
471            this(context, getColorFromTheme(context,
472                    R.attr.playbackControlsIconHighlightColor));
473        }
474
475        /**
476         * Constructor
477         * @param context Context used for loading resources.
478         * @param highlightColor Color for the highlighted icon state.
479         */
480        public ShuffleAction(Context context, int highlightColor) {
481            super(R.id.lb_control_shuffle);
482            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
483                    R.styleable.lbPlaybackControlsActionIcons_shuffle);
484            Drawable[] drawables = new Drawable[2];
485            drawables[OFF] = uncoloredDrawable;
486            drawables[ON] = new BitmapDrawable(context.getResources(),
487                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
488            setDrawables(drawables);
489
490            String[] labels = new String[drawables.length];
491            labels[OFF] = context.getString(R.string.lb_playback_controls_shuffle_enable);
492            labels[ON] = context.getString(R.string.lb_playback_controls_shuffle_disable);
493            setLabels(labels);
494        }
495    }
496
497    /**
498     * An action for displaying a HQ (High Quality) icon.
499     */
500    public static class HighQualityAction extends MultiAction {
501        public static int OFF = 0;
502        public static int ON = 1;
503
504        /**
505         * Constructor
506         * @param context Context used for loading resources.
507         */
508        public HighQualityAction(Context context) {
509            this(context, getColorFromTheme(context,
510                    R.attr.playbackControlsIconHighlightColor));
511        }
512
513        /**
514         * Constructor
515         * @param context Context used for loading resources.
516         * @param highlightColor Color for the highlighted icon state.
517         */
518        public HighQualityAction(Context context, int highlightColor) {
519            super(R.id.lb_control_high_quality);
520            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
521                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
522            Drawable[] drawables = new Drawable[2];
523            drawables[OFF] = uncoloredDrawable;
524            drawables[ON] = new BitmapDrawable(context.getResources(),
525                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
526            setDrawables(drawables);
527
528            String[] labels = new String[drawables.length];
529            labels[OFF] = context.getString(R.string.lb_playback_controls_high_quality_enable);
530            labels[ON] = context.getString(R.string.lb_playback_controls_high_quality_disable);
531            setLabels(labels);
532        }
533    }
534
535    /**
536     * An action for displaying a CC (Closed Captioning) icon.
537     */
538    public static class ClosedCaptioningAction extends MultiAction {
539        public static int OFF = 0;
540        public static int ON = 1;
541
542        /**
543         * Constructor
544         * @param context Context used for loading resources.
545         */
546        public ClosedCaptioningAction(Context context) {
547            this(context, getColorFromTheme(context,
548                    R.attr.playbackControlsIconHighlightColor));
549        }
550
551        /**
552         * Constructor
553         * @param context Context used for loading resources.
554         * @param highlightColor Color for the highlighted icon state.
555         */
556        public ClosedCaptioningAction(Context context, int highlightColor) {
557            super(R.id.lb_control_high_quality);
558            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
559                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
560            Drawable[] drawables = new Drawable[2];
561            drawables[OFF] = uncoloredDrawable;
562            drawables[ON] = new BitmapDrawable(context.getResources(),
563                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
564            setDrawables(drawables);
565
566            String[] labels = new String[drawables.length];
567            labels[OFF] = context.getString(R.string.lb_playback_controls_closed_captioning_enable);
568            labels[ON] = context.getString(R.string.lb_playback_controls_closed_captioning_disable);
569            setLabels(labels);
570        }
571    }
572
573    private static Bitmap createBitmap(Bitmap bitmap, int color) {
574        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
575        Canvas canvas = new Canvas(dst);
576        Paint paint = new Paint();
577        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
578        canvas.drawBitmap(bitmap, 0, 0, paint);
579        return dst;
580    }
581
582    private static int getColorFromTheme(Context context, int attributeResId) {
583        TypedValue outValue = new TypedValue();
584        context.getTheme().resolveAttribute(attributeResId, outValue, true);
585        return outValue.data;
586    }
587
588    private static Drawable getStyledDrawable(Context context, int index) {
589        TypedValue outValue = new TypedValue();
590        context.getTheme().resolveAttribute(
591                R.attr.playbackControlsActionIcons, outValue, false);
592        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
593                R.styleable.lbPlaybackControlsActionIcons);
594        Drawable drawable = array.getDrawable(index);
595        array.recycle();
596        return drawable;
597    }
598
599    private Object mItem;
600    private Drawable mImageDrawable;
601    private ObjectAdapter mPrimaryActionsAdapter;
602    private ObjectAdapter mSecondaryActionsAdapter;
603    private int mTotalTimeMs;
604    private int mCurrentTimeMs;
605    private int mBufferedProgressMs;
606    private OnPlaybackStateChangedListener mListener;
607
608    /**
609     * Constructor for a PlaybackControlsRow that displays some details from
610     * the given item.
611     *
612     * @param item The main item for the row.
613     */
614    public PlaybackControlsRow(Object item) {
615        mItem = item;
616    }
617
618    /**
619     * Constructor for a PlaybackControlsRow that has no item details.
620     */
621    public PlaybackControlsRow() {
622    }
623
624    /**
625     * Gets the main item for the details page.
626     */
627    public final Object getItem() {
628        return mItem;
629    }
630
631    /**
632     * Sets a {link @Drawable} image for this row.
633     *
634     * @param drawable The drawable to set.
635     */
636    public final void setImageDrawable(Drawable drawable) {
637        mImageDrawable = drawable;
638    }
639
640    /**
641     * Sets a {@link Bitmap} for this row.
642     *
643     * @param context The context to retrieve display metrics from.
644     * @param bm The bitmap to set.
645     */
646    public final void setImageBitmap(Context context, Bitmap bm) {
647        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
648    }
649
650    /**
651     * Gets the image {@link Drawable} of this row.
652     *
653     * @return The overview's image drawable, or null if no drawable has been
654     *         assigned.
655     */
656    public final Drawable getImageDrawable() {
657        return mImageDrawable;
658    }
659
660    /**
661     * Sets the primary actions {@link ObjectAdapter}.
662     */
663    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
664        mPrimaryActionsAdapter = adapter;
665    }
666
667    /**
668     * Sets the secondary actions {@link ObjectAdapter}.
669     */
670    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
671        mSecondaryActionsAdapter = adapter;
672    }
673
674    /**
675     * Returns the primary actions {@link ObjectAdapter}.
676     */
677    public final ObjectAdapter getPrimaryActionsAdapter() {
678        return mPrimaryActionsAdapter;
679    }
680
681    /**
682     * Returns the secondary actions {@link ObjectAdapter}.
683     */
684    public final ObjectAdapter getSecondaryActionsAdapter() {
685        return mSecondaryActionsAdapter;
686    }
687
688    /**
689     * Sets the total time in milliseconds for the playback controls row.
690     */
691    public void setTotalTime(int ms) {
692        mTotalTimeMs = ms;
693    }
694
695    /**
696     * Returns the total time in milliseconds for the playback controls row.
697     */
698    public int getTotalTime() {
699        return mTotalTimeMs;
700    }
701
702    /**
703     * Sets the current time in milliseconds for the playback controls row.
704     * If this row is bound to a view, the view will automatically
705     * be updated to reflect the new value.
706     */
707    public void setCurrentTime(int ms) {
708        if (mCurrentTimeMs != ms) {
709            mCurrentTimeMs = ms;
710            currentTimeChanged();
711        }
712    }
713
714    /**
715     * Returns the current time in milliseconds for the playback controls row.
716     */
717    public int getCurrentTime() {
718        return mCurrentTimeMs;
719    }
720
721    /**
722     * Sets the buffered progress for the playback controls row.
723     * If this row is bound to a view, the view will automatically
724     * be updated to reflect the new value.
725     */
726    public void setBufferedProgress(int ms) {
727        if (mBufferedProgressMs != ms) {
728            mBufferedProgressMs = ms;
729            bufferedProgressChanged();
730        }
731    }
732
733    /**
734     * Returns the buffered progress for the playback controls row.
735     */
736    public int getBufferedProgress() {
737        return mBufferedProgressMs;
738    }
739
740    /**
741     * Returns the Action associated with the given keycode, or null if no associated action exists.
742     * Searches the primary adapter first, then the secondary adapter.
743     */
744    public Action getActionForKeyCode(int keyCode) {
745        Action action = getActionForKeyCode(getPrimaryActionsAdapter(), keyCode);
746        if (action != null) {
747            return action;
748        }
749        return getActionForKeyCode(getSecondaryActionsAdapter(), keyCode);
750    }
751
752    /**
753     * Returns the Action associated with the given keycode, or null if no associated action exists.
754     */
755    public Action getActionForKeyCode(ObjectAdapter adapter, int keyCode) {
756        if (adapter != mPrimaryActionsAdapter && adapter != mSecondaryActionsAdapter) {
757            throw new IllegalArgumentException("Invalid adapter");
758        }
759        for (int i = 0; i < adapter.size(); i++) {
760            Action action = (Action) adapter.get(i);
761            if (action.respondsToKeyCode(keyCode)) {
762                return action;
763            }
764        }
765        return null;
766    }
767
768    interface OnPlaybackStateChangedListener {
769        public void onCurrentTimeChanged(int currentTimeMs);
770        public void onBufferedProgressChanged(int bufferedProgressMs);
771    }
772
773    /**
774     * Sets a listener to be called when the playback state changes.
775     */
776    void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
777        mListener = listener;
778    }
779
780    /**
781     * Returns the playback state listener.
782     */
783    OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
784        return mListener;
785    }
786
787    private void currentTimeChanged() {
788        if (mListener != null) {
789            mListener.onCurrentTimeChanged(mCurrentTimeMs);
790        }
791    }
792
793    private void bufferedProgressChanged() {
794        if (mListener != null) {
795            mListener.onBufferedProgressChanged(mBufferedProgressMs);
796        }
797    }
798}
799