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.content.Context;
17import android.content.res.TypedArray;
18import android.graphics.Bitmap;
19import android.graphics.Canvas;
20import android.graphics.Paint;
21import android.graphics.PorterDuff;
22import android.graphics.PorterDuffColorFilter;
23import android.graphics.drawable.BitmapDrawable;
24import android.graphics.drawable.Drawable;
25import android.support.v17.leanback.R;
26import android.support.v17.leanback.util.MathUtil;
27import android.util.TypedValue;
28import android.view.KeyEvent;
29
30/**
31 * A {@link Row} of playback controls to be displayed by a {@link PlaybackControlsRowPresenter}.
32 *
33 * This row consists of some optional item detail, a series of primary actions,
34 * and an optional series of secondary actions.
35 *
36 * <p>
37 * Controls are specified via an {@link ObjectAdapter} containing one or more
38 * {@link Action}s.
39 * </p>
40 * <p>
41 * Adapters should have their {@link PresenterSelector} set to an instance of
42 * {@link ControlButtonPresenterSelector}.
43 * </p>
44 */
45public class PlaybackControlsRow extends Row {
46
47    /**
48     * Listener for progress or duration change.
49     */
50    public static class OnPlaybackProgressCallback {
51        /**
52         * Called when {@link PlaybackControlsRow#getCurrentPosition()} changed.
53         * @param row The PlaybackControlsRow that current time changed.
54         * @param currentTimeMs Current time in milliseconds.
55         */
56        public void onCurrentPositionChanged(PlaybackControlsRow row, long currentTimeMs) {
57        }
58
59        /**
60         * Called when {@link PlaybackControlsRow#getDuration()} changed.
61         * @param row The PlaybackControlsRow that total time changed.
62         * @param totalTime Total time in milliseconds.
63         */
64        public void onDurationChanged(PlaybackControlsRow row, long totalTime) {
65        }
66
67        /**
68         * Called when {@link PlaybackControlsRow#getBufferedPosition()} changed.
69         * @param row The PlaybackControlsRow that buffered progress changed.
70         * @param bufferedProgressMs Buffered time in milliseconds.
71         */
72        public void onBufferedPositionChanged(PlaybackControlsRow row, long bufferedProgressMs) {
73        }
74    }
75
76    /**
77     * Base class for an action comprised of a series of icons.
78     */
79    public static abstract class MultiAction extends Action {
80        private int mIndex;
81        private Drawable[] mDrawables;
82        private String[] mLabels;
83        private String[] mLabels2;
84
85        /**
86         * Constructor
87         * @param id The id of the Action.
88         */
89        public MultiAction(int id) {
90            super(id);
91        }
92
93        /**
94         * Sets the array of drawables.  The size of the array defines the range
95         * of valid indices for this action.
96         */
97        public void setDrawables(Drawable[] drawables) {
98            mDrawables = drawables;
99            setIndex(0);
100        }
101
102        /**
103         * Sets the array of strings used as labels.  The size of the array defines the range
104         * of valid indices for this action.  The labels are used to define the accessibility
105         * content description unless secondary labels are provided.
106         */
107        public void setLabels(String[] labels) {
108            mLabels = labels;
109            setIndex(0);
110        }
111
112        /**
113         * Sets the array of strings used as secondary labels.  These labels are used
114         * in place of the primary labels for accessibility content description only.
115         */
116        public void setSecondaryLabels(String[] labels) {
117            mLabels2 = labels;
118            setIndex(0);
119        }
120
121        /**
122         * Returns the number of actions.
123         */
124        public int getActionCount() {
125            if (mDrawables != null) {
126                return mDrawables.length;
127            }
128            if (mLabels != null) {
129                return mLabels.length;
130            }
131            return 0;
132        }
133
134        /**
135         * Returns the drawable at the given index.
136         */
137        public Drawable getDrawable(int index) {
138            return mDrawables == null ? null : mDrawables[index];
139        }
140
141        /**
142         * Returns the label at the given index.
143         */
144        public String getLabel(int index) {
145            return mLabels == null ? null : mLabels[index];
146        }
147
148        /**
149         * Returns the secondary label at the given index.
150         */
151        public String getSecondaryLabel(int index) {
152            return mLabels2 == null ? null : mLabels2[index];
153        }
154
155        /**
156         * Increments the index, wrapping to zero once the end is reached.
157         */
158        public void nextIndex() {
159            setIndex(mIndex < getActionCount() - 1 ? mIndex + 1 : 0);
160        }
161
162        /**
163         * Sets the current index.
164         */
165        public void setIndex(int index) {
166            mIndex = index;
167            if (mDrawables != null) {
168                setIcon(mDrawables[mIndex]);
169            }
170            if (mLabels != null) {
171                setLabel1(mLabels[mIndex]);
172            }
173            if (mLabels2 != null) {
174                setLabel2(mLabels2[mIndex]);
175            }
176        }
177
178        /**
179         * Returns the current index.
180         */
181        public int getIndex() {
182            return mIndex;
183        }
184    }
185
186    /**
187     * An action displaying icons for play and pause.
188     */
189    public static class PlayPauseAction extends MultiAction {
190        /**
191         * Action index for the play icon.
192         * @deprecated Use {@link #INDEX_PLAY}
193         */
194        @Deprecated
195        public static int PLAY = 0;
196
197        /**
198         * Action index for the pause icon.
199         * @deprecated Use {@link #INDEX_PAUSE}
200         */
201        @Deprecated
202        public static int PAUSE = 1;
203
204        /**
205         * Action index for the play icon.
206         */
207        public static final int INDEX_PLAY = 0;
208
209        /**
210         * Action index for the pause icon.
211         */
212        public static final int INDEX_PAUSE = 1;
213
214        /**
215         * Constructor
216         * @param context Context used for loading resources.
217         */
218        public PlayPauseAction(Context context) {
219            super(R.id.lb_control_play_pause);
220            Drawable[] drawables = new Drawable[2];
221            drawables[INDEX_PLAY] = getStyledDrawable(context,
222                    R.styleable.lbPlaybackControlsActionIcons_play);
223            drawables[INDEX_PAUSE] = getStyledDrawable(context,
224                    R.styleable.lbPlaybackControlsActionIcons_pause);
225            setDrawables(drawables);
226
227            String[] labels = new String[drawables.length];
228            labels[INDEX_PLAY] = context.getString(R.string.lb_playback_controls_play);
229            labels[INDEX_PAUSE] = context.getString(R.string.lb_playback_controls_pause);
230            setLabels(labels);
231            addKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
232            addKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
233            addKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
234        }
235    }
236
237    /**
238     * An action displaying an icon for fast forward.
239     */
240    public static class FastForwardAction extends MultiAction {
241        /**
242         * Constructor
243         * @param context Context used for loading resources.
244         */
245        public FastForwardAction(Context context) {
246            this(context, 1);
247        }
248
249        /**
250         * Constructor
251         * @param context Context used for loading resources.
252         * @param numSpeeds Number of supported fast forward speeds.
253         */
254        public FastForwardAction(Context context, int numSpeeds) {
255            super(R.id.lb_control_fast_forward);
256
257            if (numSpeeds < 1) {
258                throw new IllegalArgumentException("numSpeeds must be > 0");
259            }
260            Drawable[] drawables = new Drawable[numSpeeds + 1];
261            drawables[0] = getStyledDrawable(context,
262                    R.styleable.lbPlaybackControlsActionIcons_fast_forward);
263            setDrawables(drawables);
264
265            String[] labels = new String[getActionCount()];
266            labels[0] = context.getString(R.string.lb_playback_controls_fast_forward);
267
268            String[] labels2 = new String[getActionCount()];
269            labels2[0] = labels[0];
270
271            for (int i = 1; i <= numSpeeds; i++) {
272                int multiplier = i + 1;
273                labels[i] = context.getResources().getString(
274                        R.string.lb_control_display_fast_forward_multiplier, multiplier);
275                labels2[i] = context.getResources().getString(
276                        R.string.lb_playback_controls_fast_forward_multiplier, multiplier);
277            }
278            setLabels(labels);
279            setSecondaryLabels(labels2);
280            addKeyCode(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
281        }
282    }
283
284    /**
285     * An action displaying an icon for rewind.
286     */
287    public static class RewindAction extends MultiAction {
288        /**
289         * Constructor
290         * @param context Context used for loading resources.
291         */
292        public RewindAction(Context context) {
293            this(context, 1);
294        }
295
296        /**
297         * Constructor
298         * @param context Context used for loading resources.
299         * @param numSpeeds Number of supported fast forward speeds.
300         */
301        public RewindAction(Context context, int numSpeeds) {
302            super(R.id.lb_control_fast_rewind);
303
304            if (numSpeeds < 1) {
305                throw new IllegalArgumentException("numSpeeds must be > 0");
306            }
307            Drawable[] drawables = new Drawable[numSpeeds + 1];
308            drawables[0] = getStyledDrawable(context,
309                    R.styleable.lbPlaybackControlsActionIcons_rewind);
310            setDrawables(drawables);
311
312            String[] labels = new String[getActionCount()];
313            labels[0] = context.getString(R.string.lb_playback_controls_rewind);
314
315            String[] labels2 = new String[getActionCount()];
316            labels2[0] = labels[0];
317
318            for (int i = 1; i <= numSpeeds; i++) {
319                int multiplier = i + 1;
320                labels[i] = labels[i] = context.getResources().getString(
321                        R.string.lb_control_display_rewind_multiplier, multiplier);
322                labels2[i] = context.getResources().getString(
323                        R.string.lb_playback_controls_rewind_multiplier, multiplier);
324            }
325            setLabels(labels);
326            setSecondaryLabels(labels2);
327            addKeyCode(KeyEvent.KEYCODE_MEDIA_REWIND);
328        }
329    }
330
331    /**
332     * An action displaying an icon for skip next.
333     */
334    public static class SkipNextAction extends Action {
335        /**
336         * Constructor
337         * @param context Context used for loading resources.
338         */
339        public SkipNextAction(Context context) {
340            super(R.id.lb_control_skip_next);
341            setIcon(getStyledDrawable(context,
342                    R.styleable.lbPlaybackControlsActionIcons_skip_next));
343            setLabel1(context.getString(R.string.lb_playback_controls_skip_next));
344            addKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
345        }
346    }
347
348    /**
349     * An action displaying an icon for skip previous.
350     */
351    public static class SkipPreviousAction extends Action {
352        /**
353         * Constructor
354         * @param context Context used for loading resources.
355         */
356        public SkipPreviousAction(Context context) {
357            super(R.id.lb_control_skip_previous);
358            setIcon(getStyledDrawable(context,
359                    R.styleable.lbPlaybackControlsActionIcons_skip_previous));
360            setLabel1(context.getString(R.string.lb_playback_controls_skip_previous));
361            addKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
362        }
363    }
364
365    /**
366     * An action displaying an icon for picture-in-picture.
367     */
368    public static class PictureInPictureAction extends Action {
369        /**
370         * Constructor
371         * @param context Context used for loading resources.
372         */
373        public PictureInPictureAction(Context context) {
374            super(R.id.lb_control_picture_in_picture);
375            setIcon(getStyledDrawable(context,
376                    R.styleable.lbPlaybackControlsActionIcons_picture_in_picture));
377            setLabel1(context.getString(R.string.lb_playback_controls_picture_in_picture));
378            addKeyCode(KeyEvent.KEYCODE_WINDOW);
379        }
380    }
381
382    /**
383     * An action displaying an icon for "more actions".
384     */
385    public static class MoreActions extends Action {
386        /**
387         * Constructor
388         * @param context Context used for loading resources.
389         */
390        public MoreActions(Context context) {
391            super(R.id.lb_control_more_actions);
392            setIcon(context.getResources().getDrawable(R.drawable.lb_ic_more));
393            setLabel1(context.getString(R.string.lb_playback_controls_more_actions));
394        }
395    }
396
397    /**
398     * A base class for displaying a thumbs action.
399     */
400    public static abstract class ThumbsAction extends MultiAction {
401        /**
402         * Action index for the solid thumb icon.
403         * @deprecated Use {@link #INDEX_SOLID}
404         */
405        @Deprecated
406        public static int SOLID = 0;
407
408        /**
409         * Action index for the outline thumb icon.
410         * @deprecated Use {@link #INDEX_OUTLINE}
411         */
412        @Deprecated
413        public static int OUTLINE = 1;
414
415        /**
416         * Action index for the solid thumb icon.
417         */
418        public static final int INDEX_SOLID = 0;
419
420        /**
421         * Action index for the outline thumb icon.
422         */
423        public static final int INDEX_OUTLINE = 1;
424
425        /**
426         * Constructor
427         * @param context Context used for loading resources.
428         */
429        public ThumbsAction(int id, Context context, int solidIconIndex, int outlineIconIndex) {
430            super(id);
431            Drawable[] drawables = new Drawable[2];
432            drawables[INDEX_SOLID] = getStyledDrawable(context, solidIconIndex);
433            drawables[INDEX_OUTLINE] = getStyledDrawable(context, outlineIconIndex);
434            setDrawables(drawables);
435        }
436    }
437
438    /**
439     * An action displaying an icon for thumbs up.
440     */
441    public static class ThumbsUpAction extends ThumbsAction {
442        public ThumbsUpAction(Context context) {
443            super(R.id.lb_control_thumbs_up, context,
444                    R.styleable.lbPlaybackControlsActionIcons_thumb_up,
445                    R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
446            String[] labels = new String[getActionCount()];
447            labels[INDEX_SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
448            labels[INDEX_OUTLINE] = context.getString(
449                    R.string.lb_playback_controls_thumb_up_outline);
450            setLabels(labels);
451        }
452    }
453
454    /**
455     * An action displaying an icon for thumbs down.
456     */
457    public static class ThumbsDownAction extends ThumbsAction {
458        public ThumbsDownAction(Context context) {
459            super(R.id.lb_control_thumbs_down, context,
460                    R.styleable.lbPlaybackControlsActionIcons_thumb_down,
461                    R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
462            String[] labels = new String[getActionCount()];
463            labels[INDEX_SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
464            labels[INDEX_OUTLINE] = context.getString(
465                    R.string.lb_playback_controls_thumb_down_outline);
466            setLabels(labels);
467        }
468    }
469
470    /**
471     * An action for displaying three repeat states: none, one, or all.
472     */
473    public static class RepeatAction extends MultiAction {
474        /**
475         * Action index for the repeat-none icon.
476         * @deprecated Use {@link #INDEX_NONE}
477         */
478        @Deprecated
479        public static int NONE = 0;
480
481        /**
482         * Action index for the repeat-all icon.
483         * @deprecated Use {@link #INDEX_ALL}
484         */
485        @Deprecated
486        public static int ALL = 1;
487
488        /**
489         * Action index for the repeat-one icon.
490         * @deprecated Use {@link #INDEX_ONE}
491         */
492        @Deprecated
493        public static int ONE = 2;
494
495        /**
496         * Action index for the repeat-none icon.
497         */
498        public static final int INDEX_NONE = 0;
499
500        /**
501         * Action index for the repeat-all icon.
502         */
503        public static final int INDEX_ALL = 1;
504
505        /**
506         * Action index for the repeat-one icon.
507         */
508        public static final int INDEX_ONE = 2;
509
510        /**
511         * Constructor
512         * @param context Context used for loading resources.
513         */
514        public RepeatAction(Context context) {
515            this(context, getIconHighlightColor(context));
516        }
517
518        /**
519         * Constructor
520         * @param context Context used for loading resources
521         * @param highlightColor Color to display the repeat-all and repeat0one icons.
522         */
523        public RepeatAction(Context context, int highlightColor) {
524            this(context, highlightColor, highlightColor);
525        }
526
527        /**
528         * Constructor
529         * @param context Context used for loading resources
530         * @param repeatAllColor Color to display the repeat-all icon.
531         * @param repeatOneColor Color to display the repeat-one icon.
532         */
533        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
534            super(R.id.lb_control_repeat);
535            Drawable[] drawables = new Drawable[3];
536            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
537                    R.styleable.lbPlaybackControlsActionIcons_repeat);
538            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
539                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
540            drawables[INDEX_NONE] = repeatDrawable;
541            drawables[INDEX_ALL] = repeatDrawable == null ? null
542                    : new BitmapDrawable(context.getResources(),
543                            createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
544            drawables[INDEX_ONE] = repeatOneDrawable == null ? null
545                    : new BitmapDrawable(context.getResources(),
546                            createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
547            setDrawables(drawables);
548
549            String[] labels = new String[drawables.length];
550            // Note, labels denote the action taken when clicked
551            labels[INDEX_NONE] = context.getString(R.string.lb_playback_controls_repeat_all);
552            labels[INDEX_ALL] = context.getString(R.string.lb_playback_controls_repeat_one);
553            labels[INDEX_ONE] = context.getString(R.string.lb_playback_controls_repeat_none);
554            setLabels(labels);
555        }
556    }
557
558    /**
559     * An action for displaying a shuffle icon.
560     */
561    public static class ShuffleAction extends MultiAction {
562        /**
563         * Action index for shuffle is off.
564         * @deprecated Use {@link #INDEX_OFF}
565         */
566        @Deprecated
567        public static int OFF = 0;
568
569        /**
570         * Action index for shuffle is on.
571         * @deprecated Use {@link #INDEX_ON}
572         */
573        @Deprecated
574        public static int ON = 1;
575
576        /**
577         * Action index for shuffle is off
578         */
579        public static final int INDEX_OFF = 0;
580
581        /**
582         * Action index for shuffle is on.
583         */
584        public static final int INDEX_ON = 1;
585
586        /**
587         * Constructor
588         * @param context Context used for loading resources.
589         */
590        public ShuffleAction(Context context) {
591            this(context, getIconHighlightColor(context));
592        }
593
594        /**
595         * Constructor
596         * @param context Context used for loading resources.
597         * @param highlightColor Color for the highlighted icon state.
598         */
599        public ShuffleAction(Context context, int highlightColor) {
600            super(R.id.lb_control_shuffle);
601            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
602                    R.styleable.lbPlaybackControlsActionIcons_shuffle);
603            Drawable[] drawables = new Drawable[2];
604            drawables[INDEX_OFF] = uncoloredDrawable;
605            drawables[INDEX_ON] = new BitmapDrawable(context.getResources(),
606                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
607            setDrawables(drawables);
608
609            String[] labels = new String[drawables.length];
610            labels[INDEX_OFF] = context.getString(R.string.lb_playback_controls_shuffle_enable);
611            labels[INDEX_ON] = context.getString(R.string.lb_playback_controls_shuffle_disable);
612            setLabels(labels);
613        }
614    }
615
616    /**
617     * An action for displaying a HQ (High Quality) icon.
618     */
619    public static class HighQualityAction extends MultiAction {
620        /**
621         * Action index for high quality is off.
622         * @deprecated Use {@link #INDEX_OFF}
623         */
624        @Deprecated
625        public static int OFF = 0;
626
627        /**
628         * Action index for high quality is on.
629         * @deprecated Use {@link #INDEX_ON}
630         */
631        @Deprecated
632        public static int ON = 1;
633
634        /**
635         * Action index for high quality is off.
636         */
637        public static final int INDEX_OFF = 0;
638
639        /**
640         * Action index for high quality is on.
641         */
642        public static final int INDEX_ON = 1;
643
644        /**
645         * Constructor
646         * @param context Context used for loading resources.
647         */
648        public HighQualityAction(Context context) {
649            this(context, getIconHighlightColor(context));
650        }
651
652        /**
653         * Constructor
654         * @param context Context used for loading resources.
655         * @param highlightColor Color for the highlighted icon state.
656         */
657        public HighQualityAction(Context context, int highlightColor) {
658            super(R.id.lb_control_high_quality);
659            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
660                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
661            Drawable[] drawables = new Drawable[2];
662            drawables[INDEX_OFF] = uncoloredDrawable;
663            drawables[INDEX_ON] = new BitmapDrawable(context.getResources(),
664                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
665            setDrawables(drawables);
666
667            String[] labels = new String[drawables.length];
668            labels[INDEX_OFF] = context.getString(
669                    R.string.lb_playback_controls_high_quality_enable);
670            labels[INDEX_ON] = context.getString(
671                    R.string.lb_playback_controls_high_quality_disable);
672            setLabels(labels);
673        }
674    }
675
676    /**
677     * An action for displaying a CC (Closed Captioning) icon.
678     */
679    public static class ClosedCaptioningAction extends MultiAction {
680        /**
681         * Action index for closed caption is off.
682         * @deprecated Use {@link #INDEX_OFF}
683         */
684        @Deprecated
685        public static int OFF = 0;
686
687        /**
688         * Action index for closed caption is on.
689         * @deprecated Use {@link #INDEX_ON}
690         */
691        @Deprecated
692        public static int ON = 1;
693
694        /**
695         * Action index for closed caption is off.
696         */
697        public static final int INDEX_OFF = 0;
698
699        /**
700         * Action index for closed caption is on.
701         */
702        public static final int INDEX_ON = 1;
703
704
705        /**
706         * Constructor
707         * @param context Context used for loading resources.
708         */
709        public ClosedCaptioningAction(Context context) {
710            this(context, getIconHighlightColor(context));
711        }
712
713        /**
714         * Constructor
715         * @param context Context used for loading resources.
716         * @param highlightColor Color for the highlighted icon state.
717         */
718        public ClosedCaptioningAction(Context context, int highlightColor) {
719            super(R.id.lb_control_closed_captioning);
720            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
721                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
722            Drawable[] drawables = new Drawable[2];
723            drawables[INDEX_OFF] = uncoloredDrawable;
724            drawables[INDEX_ON] = new BitmapDrawable(context.getResources(),
725                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
726            setDrawables(drawables);
727
728            String[] labels = new String[drawables.length];
729            labels[INDEX_OFF] = context.getString(
730                    R.string.lb_playback_controls_closed_captioning_enable);
731            labels[INDEX_ON] = context.getString(
732                    R.string.lb_playback_controls_closed_captioning_disable);
733            setLabels(labels);
734        }
735    }
736
737    static Bitmap createBitmap(Bitmap bitmap, int color) {
738        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
739        Canvas canvas = new Canvas(dst);
740        Paint paint = new Paint();
741        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
742        canvas.drawBitmap(bitmap, 0, 0, paint);
743        return dst;
744    }
745
746    static int getIconHighlightColor(Context context) {
747        TypedValue outValue = new TypedValue();
748        if (context.getTheme().resolveAttribute(R.attr.playbackControlsIconHighlightColor,
749                outValue, true)) {
750            return outValue.data;
751        }
752        return context.getResources().getColor(R.color.lb_playback_icon_highlight_no_theme);
753    }
754
755    static Drawable getStyledDrawable(Context context, int index) {
756        TypedValue outValue = new TypedValue();
757        if (!context.getTheme().resolveAttribute(
758                R.attr.playbackControlsActionIcons, outValue, false)) {
759            return null;
760        }
761        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
762                R.styleable.lbPlaybackControlsActionIcons);
763        Drawable drawable = array.getDrawable(index);
764        array.recycle();
765        return drawable;
766    }
767
768    private Object mItem;
769    private Drawable mImageDrawable;
770    private ObjectAdapter mPrimaryActionsAdapter;
771    private ObjectAdapter mSecondaryActionsAdapter;
772    private long mTotalTimeMs;
773    private long mCurrentTimeMs;
774    private long mBufferedProgressMs;
775    private OnPlaybackProgressCallback mListener;
776
777    /**
778     * Constructor for a PlaybackControlsRow that displays some details from
779     * the given item.
780     *
781     * @param item The main item for the row.
782     */
783    public PlaybackControlsRow(Object item) {
784        mItem = item;
785    }
786
787    /**
788     * Constructor for a PlaybackControlsRow that has no item details.
789     */
790    public PlaybackControlsRow() {
791    }
792
793    /**
794     * Returns the main item for the details page.
795     */
796    public final Object getItem() {
797        return mItem;
798    }
799
800    /**
801     * Sets a {link @Drawable} image for this row.
802     * <p>If set after the row has been bound to a view, the adapter must be notified that
803     * this row has changed.</p>
804     *
805     * @param drawable The drawable to set.
806     */
807    public final void setImageDrawable(Drawable drawable) {
808        mImageDrawable = drawable;
809    }
810
811    /**
812     * Sets a {@link Bitmap} for this row.
813     * <p>If set after the row has been bound to a view, the adapter must be notified that
814     * this row has changed.</p>
815     *
816     * @param context The context to retrieve display metrics from.
817     * @param bm The bitmap to set.
818     */
819    public final void setImageBitmap(Context context, Bitmap bm) {
820        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
821    }
822
823    /**
824     * Returns the image {@link Drawable} of this row.
825     *
826     * @return The overview's image drawable, or null if no drawable has been
827     *         assigned.
828     */
829    public final Drawable getImageDrawable() {
830        return mImageDrawable;
831    }
832
833    /**
834     * Sets the primary actions {@link ObjectAdapter}.
835     * <p>If set after the row has been bound to a view, the adapter must be notified that
836     * this row has changed.</p>
837     */
838    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
839        mPrimaryActionsAdapter = adapter;
840    }
841
842    /**
843     * Sets the secondary actions {@link ObjectAdapter}.
844     * <p>If set after the row has been bound to a view, the adapter must be notified that
845     * this row has changed.</p>
846     */
847    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
848        mSecondaryActionsAdapter = adapter;
849    }
850
851    /**
852     * Returns the primary actions {@link ObjectAdapter}.
853     */
854    public final ObjectAdapter getPrimaryActionsAdapter() {
855        return mPrimaryActionsAdapter;
856    }
857
858    /**
859     * Returns the secondary actions {@link ObjectAdapter}.
860     */
861    public final ObjectAdapter getSecondaryActionsAdapter() {
862        return mSecondaryActionsAdapter;
863    }
864
865    /**
866     * Sets the total time in milliseconds for the playback controls row.
867     * <p>If set after the row has been bound to a view, the adapter must be notified that
868     * this row has changed.</p>
869     * @deprecated Use {@link #setDuration(long)}
870     */
871    @Deprecated
872    public void setTotalTime(int ms) {
873        setDuration((long) ms);
874    }
875
876    /**
877     * Sets the total time in milliseconds (long type) for the playback controls row.
878     * @param ms Total time in milliseconds of long type.
879     * @deprecated Use {@link #setDuration(long)}
880     */
881    @Deprecated
882    public void setTotalTimeLong(long ms) {
883        setDuration(ms);
884    }
885
886    /**
887     * Sets the total time in milliseconds (long type) for the playback controls row.
888     * If this row is bound to a view, the view will automatically
889     * be updated to reflect the new value.
890     * @param ms Total time in milliseconds of long type.
891     */
892    public void setDuration(long ms) {
893        if (mTotalTimeMs != ms) {
894            mTotalTimeMs = ms;
895            if (mListener != null) {
896                mListener.onDurationChanged(this, mTotalTimeMs);
897            }
898        }
899    }
900
901    /**
902     * Returns the total time in milliseconds for the playback controls row.
903     * @throws ArithmeticException If total time in milliseconds overflows int.
904     * @deprecated use {@link #getDuration()}
905     */
906    @Deprecated
907    public int getTotalTime() {
908        return MathUtil.safeLongToInt(getTotalTimeLong());
909    }
910
911    /**
912     * Returns the total time in milliseconds of long type for the playback controls row.
913     * @deprecated use {@link #getDuration()}
914     */
915    @Deprecated
916    public long getTotalTimeLong() {
917        return mTotalTimeMs;
918    }
919
920    /**
921     * Returns duration in milliseconds.
922     * @return Duration in milliseconds.
923     */
924    public long getDuration() {
925        return mTotalTimeMs;
926    }
927
928    /**
929     * Sets the current time in milliseconds for the playback controls row.
930     * If this row is bound to a view, the view will automatically
931     * be updated to reflect the new value.
932     * @deprecated use {@link #setCurrentPosition(long)}
933     */
934    @Deprecated
935    public void setCurrentTime(int ms) {
936        setCurrentTimeLong((long) ms);
937    }
938
939    /**
940     * Sets the current time in milliseconds for playback controls row in long type.
941     * @param ms Current time in milliseconds of long type.
942     * @deprecated use {@link #setCurrentPosition(long)}
943     */
944    @Deprecated
945    public void setCurrentTimeLong(long ms) {
946        setCurrentPosition(ms);
947    }
948
949    /**
950     * Sets the current time in milliseconds for the playback controls row.
951     * If this row is bound to a view, the view will automatically
952     * be updated to reflect the new value.
953     * @param ms Current time in milliseconds of long type.
954     */
955    public void setCurrentPosition(long ms) {
956        if (mCurrentTimeMs != ms) {
957            mCurrentTimeMs = ms;
958            if (mListener != null) {
959                mListener.onCurrentPositionChanged(this, mCurrentTimeMs);
960            }
961        }
962    }
963
964    /**
965     * Returns the current time in milliseconds for the playback controls row.
966     * @throws ArithmeticException If current time in milliseconds overflows int.
967     * @deprecated Use {@link #getCurrentPosition()}
968     */
969    @Deprecated
970    public int getCurrentTime() {
971        return MathUtil.safeLongToInt(getCurrentTimeLong());
972    }
973
974    /**
975     * Returns the current time in milliseconds of long type for playback controls row.
976     * @deprecated Use {@link #getCurrentPosition()}
977     */
978    @Deprecated
979    public long getCurrentTimeLong() {
980        return mCurrentTimeMs;
981    }
982
983    /**
984     * Returns the current time in milliseconds of long type for playback controls row.
985     */
986    public long getCurrentPosition() {
987        return mCurrentTimeMs;
988    }
989
990    /**
991     * Sets the buffered progress for the playback controls row.
992     * If this row is bound to a view, the view will automatically
993     * be updated to reflect the new value.
994     * @deprecated Use {@link #setBufferedPosition(long)}
995     */
996    @Deprecated
997    public void setBufferedProgress(int ms) {
998        setBufferedPosition((long) ms);
999    }
1000
1001    /**
1002     * Sets the buffered progress for the playback controls row.
1003     * @param ms Buffered progress in milliseconds of long type.
1004     * @deprecated Use {@link #setBufferedPosition(long)}
1005     */
1006    @Deprecated
1007    public void setBufferedProgressLong(long ms) {
1008        setBufferedPosition(ms);
1009    }
1010
1011    /**
1012     * Sets the buffered progress for the playback controls row.
1013     * @param ms Buffered progress in milliseconds of long type.
1014     */
1015    public void setBufferedPosition(long ms) {
1016        if (mBufferedProgressMs != ms) {
1017            mBufferedProgressMs = ms;
1018            if (mListener != null) {
1019                mListener.onBufferedPositionChanged(this, mBufferedProgressMs);
1020            }
1021        }
1022    }
1023    /**
1024     * Returns the buffered progress for the playback controls row.
1025     * @throws ArithmeticException If buffered progress in milliseconds overflows int.
1026     * @deprecated Use {@link #getBufferedPosition()}
1027     */
1028    @Deprecated
1029    public int getBufferedProgress() {
1030        return MathUtil.safeLongToInt(getBufferedPosition());
1031    }
1032
1033    /**
1034     * Returns the buffered progress of long type for the playback controls row.
1035     * @deprecated Use {@link #getBufferedPosition()}
1036     */
1037    @Deprecated
1038    public long getBufferedProgressLong() {
1039        return mBufferedProgressMs;
1040    }
1041
1042    /**
1043     * Returns the buffered progress of long type for the playback controls row.
1044     */
1045    public long getBufferedPosition() {
1046        return mBufferedProgressMs;
1047    }
1048
1049    /**
1050     * Returns the Action associated with the given keycode, or null if no associated action exists.
1051     * Searches the primary adapter first, then the secondary adapter.
1052     */
1053    public Action getActionForKeyCode(int keyCode) {
1054        Action action = getActionForKeyCode(getPrimaryActionsAdapter(), keyCode);
1055        if (action != null) {
1056            return action;
1057        }
1058        return getActionForKeyCode(getSecondaryActionsAdapter(), keyCode);
1059    }
1060
1061    /**
1062     * Returns the Action associated with the given keycode, or null if no associated action exists.
1063     */
1064    public Action getActionForKeyCode(ObjectAdapter adapter, int keyCode) {
1065        if (adapter != mPrimaryActionsAdapter && adapter != mSecondaryActionsAdapter) {
1066            throw new IllegalArgumentException("Invalid adapter");
1067        }
1068        for (int i = 0; i < adapter.size(); i++) {
1069            Action action = (Action) adapter.get(i);
1070            if (action.respondsToKeyCode(keyCode)) {
1071                return action;
1072            }
1073        }
1074        return null;
1075    }
1076
1077    /**
1078     * Sets a listener to be called when the playback state changes.
1079     */
1080    public void setOnPlaybackProgressChangedListener(OnPlaybackProgressCallback listener) {
1081        mListener = listener;
1082    }
1083}
1084