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