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 picture-in-picture.
325     */
326    public static class PictureInPictureAction extends Action {
327        /**
328         * Constructor
329         * @param context Context used for loading resources.
330         */
331        public PictureInPictureAction(Context context) {
332            super(R.id.lb_control_picture_in_picture);
333            setIcon(getStyledDrawable(context,
334                    R.styleable.lbPlaybackControlsActionIcons_picture_in_picture));
335            setLabel1(context.getString(R.string.lb_playback_controls_picture_in_picture));
336            addKeyCode(KeyEvent.KEYCODE_WINDOW);
337        }
338    }
339
340    /**
341     * An action displaying an icon for "more actions".
342     */
343    public static class MoreActions extends Action {
344        /**
345         * Constructor
346         * @param context Context used for loading resources.
347         */
348        public MoreActions(Context context) {
349            super(R.id.lb_control_more_actions);
350            setIcon(context.getResources().getDrawable(R.drawable.lb_ic_more));
351            setLabel1(context.getString(R.string.lb_playback_controls_more_actions));
352        }
353    }
354
355    /**
356     * A base class for displaying a thumbs action.
357     */
358    public static abstract class ThumbsAction extends MultiAction {
359        /**
360         * Action index for the solid thumb icon.
361         */
362        public static int SOLID = 0;
363
364        /**
365         * Action index for the outline thumb icon.
366         */
367        public static int OUTLINE = 1;
368
369        /**
370         * Constructor
371         * @param context Context used for loading resources.
372         */
373        public ThumbsAction(int id, Context context, int solidIconIndex, int outlineIconIndex) {
374            super(id);
375            Drawable[] drawables = new Drawable[2];
376            drawables[SOLID] = getStyledDrawable(context, solidIconIndex);
377            drawables[OUTLINE] = getStyledDrawable(context, outlineIconIndex);
378            setDrawables(drawables);
379        }
380    }
381
382    /**
383     * An action displaying an icon for thumbs up.
384     */
385    public static class ThumbsUpAction extends ThumbsAction {
386        public ThumbsUpAction(Context context) {
387            super(R.id.lb_control_thumbs_up, context,
388                    R.styleable.lbPlaybackControlsActionIcons_thumb_up,
389                    R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
390            String[] labels = new String[getActionCount()];
391            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
392            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_up_outline);
393            setLabels(labels);
394        }
395    }
396
397    /**
398     * An action displaying an icon for thumbs down.
399     */
400    public static class ThumbsDownAction extends ThumbsAction {
401        public ThumbsDownAction(Context context) {
402            super(R.id.lb_control_thumbs_down, context,
403                    R.styleable.lbPlaybackControlsActionIcons_thumb_down,
404                    R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
405            String[] labels = new String[getActionCount()];
406            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
407            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_down_outline);
408            setLabels(labels);
409        }
410    }
411
412    /**
413     * An action for displaying three repeat states: none, one, or all.
414     */
415    public static class RepeatAction extends MultiAction {
416        /**
417         * Action index for the repeat-none icon.
418         */
419        public static int NONE = 0;
420
421        /**
422         * Action index for the repeat-all icon.
423         */
424        public static int ALL = 1;
425
426        /**
427         * Action index for the repeat-one icon.
428         */
429        public static int ONE = 2;
430
431        /**
432         * Constructor
433         * @param context Context used for loading resources.
434         */
435        public RepeatAction(Context context) {
436            this(context, getIconHighlightColor(context));
437        }
438
439        /**
440         * Constructor
441         * @param context Context used for loading resources
442         * @param highlightColor Color to display the repeat-all and repeat0one icons.
443         */
444        public RepeatAction(Context context, int highlightColor) {
445            this(context, highlightColor, highlightColor);
446        }
447
448        /**
449         * Constructor
450         * @param context Context used for loading resources
451         * @param repeatAllColor Color to display the repeat-all icon.
452         * @param repeatOneColor Color to display the repeat-one icon.
453         */
454        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
455            super(R.id.lb_control_repeat);
456            Drawable[] drawables = new Drawable[3];
457            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
458                    R.styleable.lbPlaybackControlsActionIcons_repeat);
459            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
460                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
461            drawables[NONE] = repeatDrawable;
462            drawables[ALL] = repeatDrawable == null ? null
463                    : new BitmapDrawable(context.getResources(),
464                            createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
465            drawables[ONE] = repeatOneDrawable == null ? null
466                    : new BitmapDrawable(context.getResources(),
467                            createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
468            setDrawables(drawables);
469
470            String[] labels = new String[drawables.length];
471            // Note, labels denote the action taken when clicked
472            labels[NONE] = context.getString(R.string.lb_playback_controls_repeat_all);
473            labels[ALL] = context.getString(R.string.lb_playback_controls_repeat_one);
474            labels[ONE] = context.getString(R.string.lb_playback_controls_repeat_none);
475            setLabels(labels);
476        }
477    }
478
479    /**
480     * An action for displaying a shuffle icon.
481     */
482    public static class ShuffleAction extends MultiAction {
483        public static int OFF = 0;
484        public static int ON = 1;
485
486        /**
487         * Constructor
488         * @param context Context used for loading resources.
489         */
490        public ShuffleAction(Context context) {
491            this(context, getIconHighlightColor(context));
492        }
493
494        /**
495         * Constructor
496         * @param context Context used for loading resources.
497         * @param highlightColor Color for the highlighted icon state.
498         */
499        public ShuffleAction(Context context, int highlightColor) {
500            super(R.id.lb_control_shuffle);
501            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
502                    R.styleable.lbPlaybackControlsActionIcons_shuffle);
503            Drawable[] drawables = new Drawable[2];
504            drawables[OFF] = uncoloredDrawable;
505            drawables[ON] = new BitmapDrawable(context.getResources(),
506                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
507            setDrawables(drawables);
508
509            String[] labels = new String[drawables.length];
510            labels[OFF] = context.getString(R.string.lb_playback_controls_shuffle_enable);
511            labels[ON] = context.getString(R.string.lb_playback_controls_shuffle_disable);
512            setLabels(labels);
513        }
514    }
515
516    /**
517     * An action for displaying a HQ (High Quality) icon.
518     */
519    public static class HighQualityAction extends MultiAction {
520        public static int OFF = 0;
521        public static int ON = 1;
522
523        /**
524         * Constructor
525         * @param context Context used for loading resources.
526         */
527        public HighQualityAction(Context context) {
528            this(context, getIconHighlightColor(context));
529        }
530
531        /**
532         * Constructor
533         * @param context Context used for loading resources.
534         * @param highlightColor Color for the highlighted icon state.
535         */
536        public HighQualityAction(Context context, int highlightColor) {
537            super(R.id.lb_control_high_quality);
538            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
539                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
540            Drawable[] drawables = new Drawable[2];
541            drawables[OFF] = uncoloredDrawable;
542            drawables[ON] = new BitmapDrawable(context.getResources(),
543                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
544            setDrawables(drawables);
545
546            String[] labels = new String[drawables.length];
547            labels[OFF] = context.getString(R.string.lb_playback_controls_high_quality_enable);
548            labels[ON] = context.getString(R.string.lb_playback_controls_high_quality_disable);
549            setLabels(labels);
550        }
551    }
552
553    /**
554     * An action for displaying a CC (Closed Captioning) icon.
555     */
556    public static class ClosedCaptioningAction extends MultiAction {
557        public static int OFF = 0;
558        public static int ON = 1;
559
560        /**
561         * Constructor
562         * @param context Context used for loading resources.
563         */
564        public ClosedCaptioningAction(Context context) {
565            this(context, getIconHighlightColor(context));
566        }
567
568        /**
569         * Constructor
570         * @param context Context used for loading resources.
571         * @param highlightColor Color for the highlighted icon state.
572         */
573        public ClosedCaptioningAction(Context context, int highlightColor) {
574            super(R.id.lb_control_closed_captioning);
575            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
576                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
577            Drawable[] drawables = new Drawable[2];
578            drawables[OFF] = uncoloredDrawable;
579            drawables[ON] = new BitmapDrawable(context.getResources(),
580                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
581            setDrawables(drawables);
582
583            String[] labels = new String[drawables.length];
584            labels[OFF] = context.getString(R.string.lb_playback_controls_closed_captioning_enable);
585            labels[ON] = context.getString(R.string.lb_playback_controls_closed_captioning_disable);
586            setLabels(labels);
587        }
588    }
589
590    private static Bitmap createBitmap(Bitmap bitmap, int color) {
591        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
592        Canvas canvas = new Canvas(dst);
593        Paint paint = new Paint();
594        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
595        canvas.drawBitmap(bitmap, 0, 0, paint);
596        return dst;
597    }
598
599    private static int getIconHighlightColor(Context context) {
600        TypedValue outValue = new TypedValue();
601        if (context.getTheme().resolveAttribute(R.attr.playbackControlsIconHighlightColor,
602                outValue, true)) {
603            return outValue.data;
604        }
605        return context.getResources().getColor(R.color.lb_playback_icon_highlight_no_theme);
606    }
607
608    private static Drawable getStyledDrawable(Context context, int index) {
609        TypedValue outValue = new TypedValue();
610        if (!context.getTheme().resolveAttribute(
611                R.attr.playbackControlsActionIcons, outValue, false)) {
612            return null;
613        }
614        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
615                R.styleable.lbPlaybackControlsActionIcons);
616        Drawable drawable = array.getDrawable(index);
617        array.recycle();
618        return drawable;
619    }
620
621    private Object mItem;
622    private Drawable mImageDrawable;
623    private ObjectAdapter mPrimaryActionsAdapter;
624    private ObjectAdapter mSecondaryActionsAdapter;
625    private int mTotalTimeMs;
626    private int mCurrentTimeMs;
627    private int mBufferedProgressMs;
628    private OnPlaybackStateChangedListener mListener;
629
630    /**
631     * Constructor for a PlaybackControlsRow that displays some details from
632     * the given item.
633     *
634     * @param item The main item for the row.
635     */
636    public PlaybackControlsRow(Object item) {
637        mItem = item;
638    }
639
640    /**
641     * Constructor for a PlaybackControlsRow that has no item details.
642     */
643    public PlaybackControlsRow() {
644    }
645
646    /**
647     * Returns the main item for the details page.
648     */
649    public final Object getItem() {
650        return mItem;
651    }
652
653    /**
654     * Sets a {link @Drawable} image for this row.
655     * <p>If set after the row has been bound to a view, the adapter must be notified that
656     * this row has changed.</p>
657     *
658     * @param drawable The drawable to set.
659     */
660    public final void setImageDrawable(Drawable drawable) {
661        mImageDrawable = drawable;
662    }
663
664    /**
665     * Sets a {@link Bitmap} for this row.
666     * <p>If set after the row has been bound to a view, the adapter must be notified that
667     * this row has changed.</p>
668     *
669     * @param context The context to retrieve display metrics from.
670     * @param bm The bitmap to set.
671     */
672    public final void setImageBitmap(Context context, Bitmap bm) {
673        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
674    }
675
676    /**
677     * Returns the image {@link Drawable} of this row.
678     *
679     * @return The overview's image drawable, or null if no drawable has been
680     *         assigned.
681     */
682    public final Drawable getImageDrawable() {
683        return mImageDrawable;
684    }
685
686    /**
687     * Sets the primary actions {@link ObjectAdapter}.
688     * <p>If set after the row has been bound to a view, the adapter must be notified that
689     * this row has changed.</p>
690     */
691    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
692        mPrimaryActionsAdapter = adapter;
693    }
694
695    /**
696     * Sets the secondary actions {@link ObjectAdapter}.
697     * <p>If set after the row has been bound to a view, the adapter must be notified that
698     * this row has changed.</p>
699     */
700    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
701        mSecondaryActionsAdapter = adapter;
702    }
703
704    /**
705     * Returns the primary actions {@link ObjectAdapter}.
706     */
707    public final ObjectAdapter getPrimaryActionsAdapter() {
708        return mPrimaryActionsAdapter;
709    }
710
711    /**
712     * Returns the secondary actions {@link ObjectAdapter}.
713     */
714    public final ObjectAdapter getSecondaryActionsAdapter() {
715        return mSecondaryActionsAdapter;
716    }
717
718    /**
719     * Sets the total time in milliseconds for the playback controls row.
720     * <p>If set after the row has been bound to a view, the adapter must be notified that
721     * this row has changed.</p>
722     */
723    public void setTotalTime(int ms) {
724        mTotalTimeMs = ms;
725    }
726
727    /**
728     * Returns the total time in milliseconds for the playback controls row.
729     */
730    public int getTotalTime() {
731        return mTotalTimeMs;
732    }
733
734    /**
735     * Sets the current time in milliseconds for the playback controls row.
736     * If this row is bound to a view, the view will automatically
737     * be updated to reflect the new value.
738     */
739    public void setCurrentTime(int ms) {
740        if (mCurrentTimeMs != ms) {
741            mCurrentTimeMs = ms;
742            currentTimeChanged();
743        }
744    }
745
746    /**
747     * Returns the current time in milliseconds for the playback controls row.
748     */
749    public int getCurrentTime() {
750        return mCurrentTimeMs;
751    }
752
753    /**
754     * Sets the buffered progress for the playback controls row.
755     * If this row is bound to a view, the view will automatically
756     * be updated to reflect the new value.
757     */
758    public void setBufferedProgress(int ms) {
759        if (mBufferedProgressMs != ms) {
760            mBufferedProgressMs = ms;
761            bufferedProgressChanged();
762        }
763    }
764
765    /**
766     * Returns the buffered progress for the playback controls row.
767     */
768    public int getBufferedProgress() {
769        return mBufferedProgressMs;
770    }
771
772    /**
773     * Returns the Action associated with the given keycode, or null if no associated action exists.
774     * Searches the primary adapter first, then the secondary adapter.
775     */
776    public Action getActionForKeyCode(int keyCode) {
777        Action action = getActionForKeyCode(getPrimaryActionsAdapter(), keyCode);
778        if (action != null) {
779            return action;
780        }
781        return getActionForKeyCode(getSecondaryActionsAdapter(), keyCode);
782    }
783
784    /**
785     * Returns the Action associated with the given keycode, or null if no associated action exists.
786     */
787    public Action getActionForKeyCode(ObjectAdapter adapter, int keyCode) {
788        if (adapter != mPrimaryActionsAdapter && adapter != mSecondaryActionsAdapter) {
789            throw new IllegalArgumentException("Invalid adapter");
790        }
791        for (int i = 0; i < adapter.size(); i++) {
792            Action action = (Action) adapter.get(i);
793            if (action.respondsToKeyCode(keyCode)) {
794                return action;
795            }
796        }
797        return null;
798    }
799
800    interface OnPlaybackStateChangedListener {
801        public void onCurrentTimeChanged(int currentTimeMs);
802        public void onBufferedProgressChanged(int bufferedProgressMs);
803    }
804
805    /**
806     * Sets a listener to be called when the playback state changes.
807     */
808    void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
809        mListener = listener;
810    }
811
812    /**
813     * Returns the playback state listener.
814     */
815    OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
816        return mListener;
817    }
818
819    private void currentTimeChanged() {
820        if (mListener != null) {
821            mListener.onCurrentTimeChanged(mCurrentTimeMs);
822        }
823    }
824
825    private void bufferedProgressChanged() {
826        if (mListener != null) {
827            mListener.onBufferedProgressChanged(mBufferedProgressMs);
828        }
829    }
830}
831