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