Home | History | Annotate | Download | only in widget
      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          */
     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          */
     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          */
     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          */
     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          */
     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          */
    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          */
    117         public void setSecondaryLabels(String[] labels) {
    118             mLabels2 = labels;
    119             setIndex(0);
    120         }
    121 
    122         /**
    123          * Returns the number of actions.
    124          */
    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          */
    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          */
    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          */
    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          */
    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