PlaybackControlsRow.java revision 46443cb5b092f1d9156342645088eead9da026f6
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, getIconHighlightColor(context));
418        }
419
420        /**
421         * Constructor
422         * @param context Context used for loading resources
423         * @param highlightColor Color to display the repeat-all and repeat0one icons.
424         */
425        public RepeatAction(Context context, int highlightColor) {
426            this(context, highlightColor, highlightColor);
427        }
428
429        /**
430         * Constructor
431         * @param context Context used for loading resources
432         * @param repeatAllColor Color to display the repeat-all icon.
433         * @param repeatOneColor Color to display the repeat-one icon.
434         */
435        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
436            super(R.id.lb_control_repeat);
437            Drawable[] drawables = new Drawable[3];
438            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
439                    R.styleable.lbPlaybackControlsActionIcons_repeat);
440            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
441                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
442            drawables[NONE] = repeatDrawable;
443            drawables[ALL] = repeatDrawable == null ? null
444                    : new BitmapDrawable(context.getResources(),
445                            createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
446            drawables[ONE] = repeatOneDrawable == null ? null
447                    : new BitmapDrawable(context.getResources(),
448                            createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
449            setDrawables(drawables);
450
451            String[] labels = new String[drawables.length];
452            // Note, labels denote the action taken when clicked
453            labels[NONE] = context.getString(R.string.lb_playback_controls_repeat_all);
454            labels[ALL] = context.getString(R.string.lb_playback_controls_repeat_one);
455            labels[ONE] = context.getString(R.string.lb_playback_controls_repeat_none);
456            setLabels(labels);
457        }
458    }
459
460    /**
461     * An action for displaying a shuffle icon.
462     */
463    public static class ShuffleAction extends MultiAction {
464        public static int OFF = 0;
465        public static int ON = 1;
466
467        /**
468         * Constructor
469         * @param context Context used for loading resources.
470         */
471        public ShuffleAction(Context context) {
472            this(context, getIconHighlightColor(context));
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, getIconHighlightColor(context));
510        }
511
512        /**
513         * Constructor
514         * @param context Context used for loading resources.
515         * @param highlightColor Color for the highlighted icon state.
516         */
517        public HighQualityAction(Context context, int highlightColor) {
518            super(R.id.lb_control_high_quality);
519            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
520                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
521            Drawable[] drawables = new Drawable[2];
522            drawables[OFF] = uncoloredDrawable;
523            drawables[ON] = new BitmapDrawable(context.getResources(),
524                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
525            setDrawables(drawables);
526
527            String[] labels = new String[drawables.length];
528            labels[OFF] = context.getString(R.string.lb_playback_controls_high_quality_enable);
529            labels[ON] = context.getString(R.string.lb_playback_controls_high_quality_disable);
530            setLabels(labels);
531        }
532    }
533
534    /**
535     * An action for displaying a CC (Closed Captioning) icon.
536     */
537    public static class ClosedCaptioningAction extends MultiAction {
538        public static int OFF = 0;
539        public static int ON = 1;
540
541        /**
542         * Constructor
543         * @param context Context used for loading resources.
544         */
545        public ClosedCaptioningAction(Context context) {
546            this(context, getIconHighlightColor(context));
547        }
548
549        /**
550         * Constructor
551         * @param context Context used for loading resources.
552         * @param highlightColor Color for the highlighted icon state.
553         */
554        public ClosedCaptioningAction(Context context, int highlightColor) {
555            super(R.id.lb_control_high_quality);
556            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
557                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
558            Drawable[] drawables = new Drawable[2];
559            drawables[OFF] = uncoloredDrawable;
560            drawables[ON] = new BitmapDrawable(context.getResources(),
561                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
562            setDrawables(drawables);
563
564            String[] labels = new String[drawables.length];
565            labels[OFF] = context.getString(R.string.lb_playback_controls_closed_captioning_enable);
566            labels[ON] = context.getString(R.string.lb_playback_controls_closed_captioning_disable);
567            setLabels(labels);
568        }
569    }
570
571    private static Bitmap createBitmap(Bitmap bitmap, int color) {
572        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
573        Canvas canvas = new Canvas(dst);
574        Paint paint = new Paint();
575        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
576        canvas.drawBitmap(bitmap, 0, 0, paint);
577        return dst;
578    }
579
580    private static int getIconHighlightColor(Context context) {
581        TypedValue outValue = new TypedValue();
582        if (context.getTheme().resolveAttribute(R.attr.playbackControlsIconHighlightColor,
583                outValue, true)) {
584            return outValue.data;
585        }
586        return context.getResources().getColor(R.color.lb_playback_icon_highlight_no_theme);
587    }
588
589    private static Drawable getStyledDrawable(Context context, int index) {
590        TypedValue outValue = new TypedValue();
591        if (!context.getTheme().resolveAttribute(
592                R.attr.playbackControlsActionIcons, outValue, false)) {
593            return null;
594        }
595        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
596                R.styleable.lbPlaybackControlsActionIcons);
597        Drawable drawable = array.getDrawable(index);
598        array.recycle();
599        return drawable;
600    }
601
602    private Object mItem;
603    private Drawable mImageDrawable;
604    private ObjectAdapter mPrimaryActionsAdapter;
605    private ObjectAdapter mSecondaryActionsAdapter;
606    private int mTotalTimeMs;
607    private int mCurrentTimeMs;
608    private int mBufferedProgressMs;
609    private OnPlaybackStateChangedListener mListener;
610
611    /**
612     * Constructor for a PlaybackControlsRow that displays some details from
613     * the given item.
614     *
615     * @param item The main item for the row.
616     */
617    public PlaybackControlsRow(Object item) {
618        mItem = item;
619    }
620
621    /**
622     * Constructor for a PlaybackControlsRow that has no item details.
623     */
624    public PlaybackControlsRow() {
625    }
626
627    /**
628     * Gets the main item for the details page.
629     */
630    public final Object getItem() {
631        return mItem;
632    }
633
634    /**
635     * Sets a {link @Drawable} image for this row.
636     *
637     * @param drawable The drawable to set.
638     */
639    public final void setImageDrawable(Drawable drawable) {
640        mImageDrawable = drawable;
641    }
642
643    /**
644     * Sets a {@link Bitmap} for this row.
645     *
646     * @param context The context to retrieve display metrics from.
647     * @param bm The bitmap to set.
648     */
649    public final void setImageBitmap(Context context, Bitmap bm) {
650        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
651    }
652
653    /**
654     * Gets the image {@link Drawable} of this row.
655     *
656     * @return The overview's image drawable, or null if no drawable has been
657     *         assigned.
658     */
659    public final Drawable getImageDrawable() {
660        return mImageDrawable;
661    }
662
663    /**
664     * Sets the primary actions {@link ObjectAdapter}.
665     */
666    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
667        mPrimaryActionsAdapter = adapter;
668    }
669
670    /**
671     * Sets the secondary actions {@link ObjectAdapter}.
672     */
673    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
674        mSecondaryActionsAdapter = adapter;
675    }
676
677    /**
678     * Returns the primary actions {@link ObjectAdapter}.
679     */
680    public final ObjectAdapter getPrimaryActionsAdapter() {
681        return mPrimaryActionsAdapter;
682    }
683
684    /**
685     * Returns the secondary actions {@link ObjectAdapter}.
686     */
687    public final ObjectAdapter getSecondaryActionsAdapter() {
688        return mSecondaryActionsAdapter;
689    }
690
691    /**
692     * Sets the total time in milliseconds for the playback controls row.
693     */
694    public void setTotalTime(int ms) {
695        mTotalTimeMs = ms;
696    }
697
698    /**
699     * Returns the total time in milliseconds for the playback controls row.
700     */
701    public int getTotalTime() {
702        return mTotalTimeMs;
703    }
704
705    /**
706     * Sets the current time in milliseconds for the playback controls row.
707     * If this row is bound to a view, the view will automatically
708     * be updated to reflect the new value.
709     */
710    public void setCurrentTime(int ms) {
711        if (mCurrentTimeMs != ms) {
712            mCurrentTimeMs = ms;
713            currentTimeChanged();
714        }
715    }
716
717    /**
718     * Returns the current time in milliseconds for the playback controls row.
719     */
720    public int getCurrentTime() {
721        return mCurrentTimeMs;
722    }
723
724    /**
725     * Sets the buffered progress for the playback controls row.
726     * If this row is bound to a view, the view will automatically
727     * be updated to reflect the new value.
728     */
729    public void setBufferedProgress(int ms) {
730        if (mBufferedProgressMs != ms) {
731            mBufferedProgressMs = ms;
732            bufferedProgressChanged();
733        }
734    }
735
736    /**
737     * Returns the buffered progress for the playback controls row.
738     */
739    public int getBufferedProgress() {
740        return mBufferedProgressMs;
741    }
742
743    /**
744     * Returns the Action associated with the given keycode, or null if no associated action exists.
745     * Searches the primary adapter first, then the secondary adapter.
746     */
747    public Action getActionForKeyCode(int keyCode) {
748        Action action = getActionForKeyCode(getPrimaryActionsAdapter(), keyCode);
749        if (action != null) {
750            return action;
751        }
752        return getActionForKeyCode(getSecondaryActionsAdapter(), keyCode);
753    }
754
755    /**
756     * Returns the Action associated with the given keycode, or null if no associated action exists.
757     */
758    public Action getActionForKeyCode(ObjectAdapter adapter, int keyCode) {
759        if (adapter != mPrimaryActionsAdapter && adapter != mSecondaryActionsAdapter) {
760            throw new IllegalArgumentException("Invalid adapter");
761        }
762        for (int i = 0; i < adapter.size(); i++) {
763            Action action = (Action) adapter.get(i);
764            if (action.respondsToKeyCode(keyCode)) {
765                return action;
766            }
767        }
768        return null;
769    }
770
771    interface OnPlaybackStateChangedListener {
772        public void onCurrentTimeChanged(int currentTimeMs);
773        public void onBufferedProgressChanged(int bufferedProgressMs);
774    }
775
776    /**
777     * Sets a listener to be called when the playback state changes.
778     */
779    void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
780        mListener = listener;
781    }
782
783    /**
784     * Returns the playback state listener.
785     */
786    OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
787        return mListener;
788    }
789
790    private void currentTimeChanged() {
791        if (mListener != null) {
792            mListener.onCurrentTimeChanged(mCurrentTimeMs);
793        }
794    }
795
796    private void bufferedProgressChanged() {
797        if (mListener != null) {
798            mListener.onBufferedProgressChanged(mBufferedProgressMs);
799        }
800    }
801}
802