Home | History | Annotate | Download | only in leanback
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.example.android.leanback;
     18 
     19 import android.content.Context;
     20 import android.graphics.drawable.Drawable;
     21 import android.os.Handler;
     22 import android.support.v17.leanback.app.PlaybackControlGlue;
     23 import android.support.v17.leanback.widget.Action;
     24 import android.support.v17.leanback.widget.ArrayObjectAdapter;
     25 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
     26 import android.support.v17.leanback.widget.PlaybackControlsRow;
     27 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
     28 import android.support.v17.leanback.widget.PresenterSelector;
     29 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
     30 import android.view.KeyEvent;
     31 import android.view.View;
     32 import android.widget.Toast;
     33 
     34 abstract class PlaybackControlHelper extends PlaybackControlGlue {
     35     /**
     36      * Change the location of the thumbs up/down controls
     37      */
     38     private static final boolean THUMBS_PRIMARY = true;
     39 
     40     private static final String FAUX_TITLE = "A short song of silence";
     41     private static final String FAUX_SUBTITLE = "2014";
     42     private static final int FAUX_DURATION = 33 * 1000;
     43 
     44     // These should match the playback service FF behavior
     45     private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
     46 
     47     private boolean mIsPlaying;
     48     private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
     49     private long mStartTime;
     50     private long mStartPosition = 0;
     51 
     52     private PlaybackControlsRow.RepeatAction mRepeatAction;
     53     private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
     54     private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
     55     private PlaybackControlsRow.PictureInPictureAction mPipAction;
     56 
     57     private Handler mHandler = new Handler();
     58     private final Runnable mUpdateProgressRunnable = new Runnable() {
     59         @Override
     60         public void run() {
     61             updateProgress();
     62             mHandler.postDelayed(this, getUpdatePeriod());
     63         }
     64     };
     65 
     66     public PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) {
     67         super(context, fragment, sFastForwardSpeeds);
     68         mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
     69         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
     70         mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
     71         mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
     72         mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
     73         mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
     74     }
     75 
     76     @Override
     77     public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
     78         PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
     79 
     80         ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
     81         getControlsRow().setSecondaryActionsAdapter(adapter);
     82         if (!THUMBS_PRIMARY) {
     83             adapter.add(mThumbsDownAction);
     84         }
     85         if (android.os.Build.VERSION.SDK_INT > 23) {
     86             adapter.add(mPipAction);
     87         }
     88         adapter.add(mRepeatAction);
     89         if (!THUMBS_PRIMARY) {
     90             adapter.add(mThumbsUpAction);
     91         }
     92 
     93         return presenter;
     94     }
     95 
     96     @Override
     97     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
     98             PresenterSelector presenterSelector) {
     99         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
    100         if (THUMBS_PRIMARY) {
    101             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
    102             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
    103         }
    104         return adapter;
    105     }
    106 
    107     @Override
    108     public void onActionClicked(Action action) {
    109         if (shouldDispatchAction(action)) {
    110             dispatchAction(action);
    111             return;
    112         }
    113         super.onActionClicked(action);
    114     }
    115 
    116     @Override
    117     public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
    118         if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
    119             Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
    120             if (shouldDispatchAction(action)) {
    121                 dispatchAction(action);
    122                 return true;
    123             }
    124         }
    125         return super.onKey(view, keyCode, keyEvent);
    126     }
    127 
    128     private boolean shouldDispatchAction(Action action) {
    129         return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
    130     }
    131 
    132     private void dispatchAction(Action action) {
    133         Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
    134         PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
    135         multiAction.nextIndex();
    136         notifyActionChanged(multiAction);
    137     }
    138 
    139     private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
    140         int index;
    141         index = getPrimaryActionsAdapter().indexOf(action);
    142         if (index >= 0) {
    143             getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
    144         } else {
    145             index = getSecondaryActionsAdapter().indexOf(action);
    146             if (index >= 0) {
    147                 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
    148             }
    149         }
    150     }
    151 
    152     private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
    153         return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
    154     }
    155 
    156     private ArrayObjectAdapter getSecondaryActionsAdapter() {
    157         return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
    158     }
    159 
    160     @Override
    161     public boolean hasValidMedia() {
    162         return true;
    163     }
    164 
    165     @Override
    166     public boolean isMediaPlaying() {
    167         return mIsPlaying;
    168     }
    169 
    170     @Override
    171     public CharSequence getMediaTitle() {
    172         return FAUX_TITLE;
    173     }
    174 
    175     @Override
    176     public CharSequence getMediaSubtitle() {
    177         return FAUX_SUBTITLE;
    178     }
    179 
    180     @Override
    181     public int getMediaDuration() {
    182         return FAUX_DURATION;
    183     }
    184 
    185     @Override
    186     public Drawable getMediaArt() {
    187         return null;
    188     }
    189 
    190     @Override
    191     public long getSupportedActions() {
    192         return PlaybackControlGlue.ACTION_PLAY_PAUSE |
    193                 PlaybackControlGlue.ACTION_FAST_FORWARD |
    194                 PlaybackControlGlue.ACTION_REWIND;
    195     }
    196 
    197     @Override
    198     public int getCurrentSpeedId() {
    199         return mSpeed;
    200     }
    201 
    202     @Override
    203     public int getCurrentPosition() {
    204         int speed;
    205         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
    206             speed = 0;
    207         } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
    208             speed = 1;
    209         } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
    210             int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
    211             speed = getFastForwardSpeeds()[index];
    212         } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
    213             int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
    214             speed = -getRewindSpeeds()[index];
    215         } else {
    216             return -1;
    217         }
    218         long position = mStartPosition +
    219                 (System.currentTimeMillis() - mStartTime) * speed;
    220         if (position > getMediaDuration()) {
    221             position = getMediaDuration();
    222             onPlaybackComplete(true);
    223         } else if (position < 0) {
    224             position = 0;
    225             onPlaybackComplete(false);
    226         }
    227         return (int) position;
    228     }
    229 
    230     void onPlaybackComplete(final boolean ended) {
    231         mHandler.post(new Runnable() {
    232             @Override
    233             public void run() {
    234                 if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
    235                     pausePlayback();
    236                 } else {
    237                     startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
    238                 }
    239                 mStartPosition = 0;
    240                 onStateChanged();
    241             }
    242         });
    243     }
    244 
    245     @Override
    246     protected void startPlayback(int speed) {
    247         if (speed == mSpeed) {
    248             return;
    249         }
    250         mStartPosition = getCurrentPosition();
    251         mSpeed = speed;
    252         mIsPlaying = true;
    253         mStartTime = System.currentTimeMillis();
    254     }
    255 
    256     @Override
    257     protected void pausePlayback() {
    258         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
    259             return;
    260         }
    261         mStartPosition = getCurrentPosition();
    262         mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
    263         mIsPlaying = false;
    264     }
    265 
    266     @Override
    267     protected void skipToNext() {
    268         // Not supported
    269     }
    270 
    271     @Override
    272     protected void skipToPrevious() {
    273         // Not supported
    274     }
    275 
    276     @Override
    277     public void enableProgressUpdating(boolean enable) {
    278         mHandler.removeCallbacks(mUpdateProgressRunnable);
    279         if (enable) {
    280             mUpdateProgressRunnable.run();
    281         }
    282     }
    283 };
    284