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;
29
30/**
31 * A 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 * Controls are specified via an {@link ObjectAdapter} containing one or more
37 * {@link Action}s.
38 *
39 * Adapters should have their {@link PresenterSelector} set to an instance of
40 * {@link ControlButtonPresenterSelector}.
41 *
42 */
43public class PlaybackControlsRow extends Row {
44
45    /**
46     * Base class for an action comprised of a series of icons.
47     */
48    public static abstract class MultiAction extends Action {
49        private int mIndex;
50        private Drawable[] mDrawables;
51        private String[] mLabels;
52
53        /**
54         * Constructor
55         * @param id The id of the Action.
56         */
57        public MultiAction(int id) {
58            super(id);
59        }
60
61        /**
62         * Sets the array of drawables.  The size of the array defines the range
63         * of valid indices for this action.
64         */
65        public void setDrawables(Drawable[] drawables) {
66            mDrawables = drawables;
67            setIndex(0);
68        }
69
70        public void setLabels(String[] labels) {
71            mLabels = labels;
72            setIndex(0);
73        }
74
75        /**
76         * Returns the number of drawables.
77         */
78        public int getNumberOfDrawables() {
79            return mDrawables.length;
80        }
81
82        /**
83         * Returns the drawable at the given index.
84         */
85        public Drawable getDrawable(int index) {
86            return mDrawables[index];
87        }
88
89        /**
90         * Returns the label at the given index.
91         */
92        public String getLabel(int index) {
93            return mLabels[index];
94        }
95
96        /**
97         * Increments the index, wrapping to zero once the end is reached.
98         */
99        public void nextIndex() {
100            setIndex(mIndex < mDrawables.length - 1 ? mIndex + 1 : 0);
101        }
102
103        /**
104         * Sets the current index.
105         */
106        public void setIndex(int index) {
107            mIndex = index;
108            setIcon(mDrawables[mIndex]);
109            if (mLabels != null) {
110                setLabel1(mLabels[mIndex]);
111            }
112        }
113
114        /**
115         * Gets the current index.
116         */
117        public int getIndex() {
118            return mIndex;
119        }
120    }
121
122    /**
123     * An action displaying icons for play and pause.
124     */
125    public static class PlayPauseAction extends MultiAction {
126        /**
127         * Action index for the play icon.
128         */
129        public static int PLAY = 0;
130
131        /**
132         * Action index for the pause icon.
133         */
134        public static int PAUSE = 1;
135
136        /**
137         * Constructor
138         * @param context Context used for loading resources.
139         */
140        public PlayPauseAction(Context context) {
141            super(R.id.lb_control_play_pause);
142            Drawable[] drawables = new Drawable[2];
143            drawables[PLAY] = getStyledDrawable(context,
144                    R.styleable.lbPlaybackControlsActionIcons_play);
145            drawables[PAUSE] = getStyledDrawable(context,
146                    R.styleable.lbPlaybackControlsActionIcons_pause);
147            setDrawables(drawables);
148
149            String[] labels = new String[drawables.length];
150            labels[PLAY] = context.getString(R.string.lb_playback_controls_play);
151            labels[PAUSE] = context.getString(R.string.lb_playback_controls_pause);
152            setLabels(labels);
153        }
154    }
155
156    /**
157     * An action displaying an icon for fast forward.
158     */
159    public static class FastForwardAction extends Action {
160        /**
161         * Constructor
162         * @param context Context used for loading resources.
163         */
164        public FastForwardAction(Context context) {
165            super(R.id.lb_control_fast_forward);
166            setIcon(getStyledDrawable(context,
167                    R.styleable.lbPlaybackControlsActionIcons_fast_forward));
168            setLabel1(context.getString(R.string.lb_playback_controls_fast_forward));
169        }
170    }
171
172    /**
173     * An action displaying an icon for rewind.
174     */
175    public static class RewindAction extends Action {
176        /**
177         * Constructor
178         * @param context Context used for loading resources.
179         */
180        public RewindAction(Context context) {
181            super(R.id.lb_control_fast_rewind);
182            setIcon(getStyledDrawable(context,
183                    R.styleable.lbPlaybackControlsActionIcons_rewind));
184            setLabel1(context.getString(R.string.lb_playback_controls_rewind));
185        }
186    }
187
188    /**
189     * An action displaying an icon for skip next.
190     */
191    public static class SkipNextAction extends Action {
192        /**
193         * Constructor
194         * @param context Context used for loading resources.
195         */
196        public SkipNextAction(Context context) {
197            super(R.id.lb_control_skip_next);
198            setIcon(getStyledDrawable(context,
199                    R.styleable.lbPlaybackControlsActionIcons_skip_next));
200            setLabel1(context.getString(R.string.lb_playback_controls_skip_next));
201        }
202    }
203
204    /**
205     * An action displaying an icon for skip previous.
206     */
207    public static class SkipPreviousAction extends Action {
208        /**
209         * Constructor
210         * @param context Context used for loading resources.
211         */
212        public SkipPreviousAction(Context context) {
213            super(R.id.lb_control_skip_previous);
214            setIcon(getStyledDrawable(context,
215                    R.styleable.lbPlaybackControlsActionIcons_skip_previous));
216            setLabel1(context.getString(R.string.lb_playback_controls_skip_previous));
217        }
218    }
219
220    /**
221     * An action displaying an icon for "more actions".
222     */
223    public static class MoreActions extends Action {
224        /**
225         * Constructor
226         * @param context Context used for loading resources.
227         */
228        public MoreActions(Context context) {
229            super(R.id.lb_control_more_actions);
230            setIcon(context.getResources().getDrawable(R.drawable.lb_ic_more));
231            setLabel1(context.getString(R.string.lb_playback_controls_more_actions));
232        }
233    }
234
235    /**
236     * A base class for displaying a thumbs action.
237     */
238    public static abstract class ThumbsAction extends MultiAction {
239        /**
240         * Action index for the solid thumb icon.
241         */
242        public static int SOLID = 0;
243
244        /**
245         * Action index for the outline thumb icon.
246         */
247        public static int OUTLINE = 1;
248
249        /**
250         * Constructor
251         * @param context Context used for loading resources.
252         */
253        public ThumbsAction(int id, Context context, int solidIconIndex, int outlineIconIndex) {
254            super(id);
255            Drawable[] drawables = new Drawable[2];
256            drawables[SOLID] = getStyledDrawable(context, solidIconIndex);
257            drawables[OUTLINE] = getStyledDrawable(context, outlineIconIndex);
258            setDrawables(drawables);
259        }
260    }
261
262    /**
263     * An action displaying an icon for thumbs up.
264     */
265    public static class ThumbsUpAction extends ThumbsAction {
266        public ThumbsUpAction(Context context) {
267            super(R.id.lb_control_thumbs_up, context,
268                    R.styleable.lbPlaybackControlsActionIcons_thumb_up,
269                    R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
270            String[] labels = new String[getNumberOfDrawables()];
271            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
272            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_up_outline);
273            setLabels(labels);
274        }
275    }
276
277    /**
278     * An action displaying an icon for thumbs down.
279     */
280    public static class ThumbsDownAction extends ThumbsAction {
281        public ThumbsDownAction(Context context) {
282            super(R.id.lb_control_thumbs_down, context,
283                    R.styleable.lbPlaybackControlsActionIcons_thumb_down,
284                    R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
285            String[] labels = new String[getNumberOfDrawables()];
286            labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
287            labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_down_outline);
288            setLabels(labels);
289        }
290    }
291
292    /**
293     * An action for displaying three repeat states: none, one, or all.
294     */
295    public static class RepeatAction extends MultiAction {
296        /**
297         * Action index for the repeat-none icon.
298         */
299        public static int NONE = 0;
300
301        /**
302         * Action index for the repeat-all icon.
303         */
304        public static int ALL = 1;
305
306        /**
307         * Action index for the repeat-one icon.
308         */
309        public static int ONE = 2;
310
311        /**
312         * Constructor
313         * @param context Context used for loading resources.
314         */
315        public RepeatAction(Context context) {
316            this(context, getColorFromTheme(context,
317                    R.attr.playbackControlsIconHighlightColor));
318        }
319
320        /**
321         * Constructor
322         * @param context Context used for loading resources
323         * @param highlightColor Color to display the repeat-all and repeat0one icons.
324         */
325        public RepeatAction(Context context, int highlightColor) {
326            this(context, highlightColor, highlightColor);
327        }
328
329        /**
330         * Constructor
331         * @param context Context used for loading resources
332         * @param repeatAllColor Color to display the repeat-all icon.
333         * @param repeatOneColor Color to display the repeat-one icon.
334         */
335        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
336            super(R.id.lb_control_repeat);
337            Drawable[] drawables = new Drawable[3];
338            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
339                    R.styleable.lbPlaybackControlsActionIcons_repeat);
340            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
341                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
342            drawables[NONE] = repeatDrawable;
343            drawables[ALL] = new BitmapDrawable(context.getResources(),
344                    createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
345            drawables[ONE] = new BitmapDrawable(context.getResources(),
346                    createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
347            setDrawables(drawables);
348
349            String[] labels = new String[drawables.length];
350            // Note, labels denote the action taken when clicked
351            labels[NONE] = context.getString(R.string.lb_playback_controls_repeat_all);
352            labels[ALL] = context.getString(R.string.lb_playback_controls_repeat_one);
353            labels[ONE] = context.getString(R.string.lb_playback_controls_repeat_none);
354            setLabels(labels);
355        }
356    }
357
358    /**
359     * An action for displaying a shuffle icon.
360     */
361    public static class ShuffleAction extends MultiAction {
362        public static int OFF = 0;
363        public static int ON = 1;
364
365        /**
366         * Constructor
367         * @param context Context used for loading resources.
368         */
369        public ShuffleAction(Context context) {
370            this(context, getColorFromTheme(context,
371                    R.attr.playbackControlsIconHighlightColor));
372        }
373
374        /**
375         * Constructor
376         * @param context Context used for loading resources.
377         * @param highlightColor Color for the highlighted icon state.
378         */
379        public ShuffleAction(Context context, int highlightColor) {
380            super(R.id.lb_control_shuffle);
381            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
382                    R.styleable.lbPlaybackControlsActionIcons_shuffle);
383            Drawable[] drawables = new Drawable[2];
384            drawables[OFF] = uncoloredDrawable;
385            drawables[ON] = new BitmapDrawable(context.getResources(),
386                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
387            setDrawables(drawables);
388
389            String[] labels = new String[drawables.length];
390            labels[OFF] = context.getString(R.string.lb_playback_controls_shuffle_enable);
391            labels[ON] = context.getString(R.string.lb_playback_controls_shuffle_disable);
392            setLabels(labels);
393        }
394    }
395
396    /**
397     * An action for displaying a HQ (High Quality) icon.
398     */
399    public static class HighQualityAction extends MultiAction {
400        public static int OFF = 0;
401        public static int ON = 1;
402
403        /**
404         * Constructor
405         * @param context Context used for loading resources.
406         */
407        public HighQualityAction(Context context) {
408            this(context, getColorFromTheme(context,
409                    R.attr.playbackControlsIconHighlightColor));
410        }
411
412        /**
413         * Constructor
414         * @param context Context used for loading resources.
415         * @param highlightColor Color for the highlighted icon state.
416         */
417        public HighQualityAction(Context context, int highlightColor) {
418            super(R.id.lb_control_high_quality);
419            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
420                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
421            Drawable[] drawables = new Drawable[2];
422            drawables[OFF] = uncoloredDrawable;
423            drawables[ON] = new BitmapDrawable(context.getResources(),
424                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
425            setDrawables(drawables);
426
427            String[] labels = new String[drawables.length];
428            labels[OFF] = context.getString(R.string.lb_playback_controls_high_quality_enable);
429            labels[ON] = context.getString(R.string.lb_playback_controls_high_quality_disable);
430            setLabels(labels);
431        }
432    }
433
434    /**
435     * An action for displaying a CC (Closed Captioning) icon.
436     */
437    public static class ClosedCaptioningAction extends MultiAction {
438        public static int OFF = 0;
439        public static int ON = 1;
440
441        /**
442         * Constructor
443         * @param context Context used for loading resources.
444         */
445        public ClosedCaptioningAction(Context context) {
446            this(context, getColorFromTheme(context,
447                    R.attr.playbackControlsIconHighlightColor));
448        }
449
450        /**
451         * Constructor
452         * @param context Context used for loading resources.
453         * @param highlightColor Color for the highlighted icon state.
454         */
455        public ClosedCaptioningAction(Context context, int highlightColor) {
456            super(R.id.lb_control_high_quality);
457            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
458                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
459            Drawable[] drawables = new Drawable[2];
460            drawables[OFF] = uncoloredDrawable;
461            drawables[ON] = new BitmapDrawable(context.getResources(),
462                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
463            setDrawables(drawables);
464
465            String[] labels = new String[drawables.length];
466            labels[OFF] = context.getString(R.string.lb_playback_controls_closed_captioning_enable);
467            labels[ON] = context.getString(R.string.lb_playback_controls_closed_captioning_disable);
468            setLabels(labels);
469        }
470    }
471
472    private static Bitmap createBitmap(Bitmap bitmap, int color) {
473        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
474        Canvas canvas = new Canvas(dst);
475        Paint paint = new Paint();
476        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
477        canvas.drawBitmap(bitmap, 0, 0, paint);
478        return dst;
479    }
480
481    private static int getColorFromTheme(Context context, int attributeResId) {
482        TypedValue outValue = new TypedValue();
483        context.getTheme().resolveAttribute(attributeResId, outValue, true);
484        return outValue.data;
485    }
486
487    private static Drawable getStyledDrawable(Context context, int index) {
488        TypedValue outValue = new TypedValue();
489        context.getTheme().resolveAttribute(
490                R.attr.playbackControlsActionIcons, outValue, false);
491        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
492                R.styleable.lbPlaybackControlsActionIcons);
493        Drawable drawable = array.getDrawable(index);
494        array.recycle();
495        return drawable;
496    }
497
498    private Object mItem;
499    private Drawable mImageDrawable;
500    private ObjectAdapter mPrimaryActionsAdapter;
501    private ObjectAdapter mSecondaryActionsAdapter;
502    private int mTotalTimeMs;
503    private int mCurrentTimeMs;
504    private int mBufferedProgressMs;
505    private OnPlaybackStateChangedListener mListener;
506
507    /**
508     * Constructor for a PlaybackControlsRow that displays some details from
509     * the given item.
510     *
511     * @param item The main item for the row.
512     */
513    public PlaybackControlsRow(Object item) {
514        mItem = item;
515    }
516
517    /**
518     * Constructor for a PlaybackControlsRow that has no item details.
519     */
520    public PlaybackControlsRow() {
521    }
522
523    /**
524     * Gets the main item for the details page.
525     */
526    public final Object getItem() {
527        return mItem;
528    }
529
530    /**
531     * Sets a {link @Drawable} image for this row.
532     *
533     * @param drawable The drawable to set.
534     */
535    public final void setImageDrawable(Drawable drawable) {
536        mImageDrawable = drawable;
537    }
538
539    /**
540     * Sets a {@link Bitmap} for this row.
541     *
542     * @param context The context to retrieve display metrics from.
543     * @param bm The bitmap to set.
544     */
545    public final void setImageBitmap(Context context, Bitmap bm) {
546        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
547    }
548
549    /**
550     * Gets the image {@link Drawable} of this row.
551     *
552     * @return The overview's image drawable, or null if no drawable has been
553     *         assigned.
554     */
555    public final Drawable getImageDrawable() {
556        return mImageDrawable;
557    }
558
559    /**
560     * Sets the primary actions {@link ObjectAdapter}.
561     */
562    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
563        mPrimaryActionsAdapter = adapter;
564    }
565
566    /**
567     * Sets the secondary actions {@link ObjectAdapter}.
568     */
569    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
570        mSecondaryActionsAdapter = adapter;
571    }
572
573    /**
574     * Returns the primary actions {@link ObjectAdapter}.
575     */
576    public final ObjectAdapter getPrimaryActionsAdapter() {
577        return mPrimaryActionsAdapter;
578    }
579
580    /**
581     * Returns the secondary actions {@link ObjectAdapter}.
582     */
583    public final ObjectAdapter getSecondaryActionsAdapter() {
584        return mSecondaryActionsAdapter;
585    }
586
587    /**
588     * Sets the total time in milliseconds for the playback controls row.
589     */
590    public void setTotalTime(int ms) {
591        mTotalTimeMs = ms;
592    }
593
594    /**
595     * Returns the total time in milliseconds for the playback controls row.
596     */
597    public int getTotalTime() {
598        return mTotalTimeMs;
599    }
600
601    /**
602     * Sets the current time in milliseconds for the playback controls row.
603     */
604    public void setCurrentTime(int ms) {
605        if (mCurrentTimeMs != ms) {
606            mCurrentTimeMs = ms;
607            currentTimeChanged();
608        }
609    }
610
611    /**
612     * Returns the current time in milliseconds for the playback controls row.
613     */
614    public int getCurrentTime() {
615        return mCurrentTimeMs;
616    }
617
618    /**
619     * Sets the buffered progress for the playback controls row.
620     */
621    public void setBufferedProgress(int ms) {
622        if (mBufferedProgressMs != ms) {
623            mBufferedProgressMs = ms;
624            bufferedProgressChanged();
625        }
626    }
627
628    /**
629     * Returns the buffered progress for the playback controls row.
630     */
631    public int getBufferedProgress() {
632        return mBufferedProgressMs;
633    }
634
635    interface OnPlaybackStateChangedListener {
636        public void onCurrentTimeChanged(int currentTimeMs);
637        public void onBufferedProgressChanged(int bufferedProgressMs);
638    }
639
640    /**
641     * Sets a listener to be called when the playback state changes.
642     */
643    public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
644        mListener = listener;
645    }
646
647    /**
648     * Returns the playback state listener.
649     */
650    public OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
651        return mListener;
652    }
653
654    private void currentTimeChanged() {
655        if (mListener != null) {
656            mListener.onCurrentTimeChanged(mCurrentTimeMs);
657        }
658    }
659
660    private void bufferedProgressChanged() {
661        if (mListener != null) {
662            mListener.onBufferedProgressChanged(mBufferedProgressMs);
663        }
664    }
665}
666