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 
     56     private Handler mHandler = new Handler();
     57     private final Runnable mUpdateProgressRunnable = new Runnable() {
     58         @Override
     59         public void run() {
     60             updateProgress();
     61             mHandler.postDelayed(this, getUpdatePeriod());
     62         }
     63     };
     64 
     65     public PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) {
     66         super(context, fragment, sFastForwardSpeeds);
     67         mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
     68         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
     69         mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
     70         mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
     71         mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
     72     }
     73 
     74     @Override
     75     public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
     76         PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
     77 
     78         ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
     79         getControlsRow().setSecondaryActionsAdapter(adapter);
     80         if (!THUMBS_PRIMARY) {
     81             adapter.add(mThumbsDownAction);
     82         }
     83         adapter.add(mRepeatAction);
     84         if (!THUMBS_PRIMARY) {
     85             adapter.add(mThumbsUpAction);
     86         }
     87 
     88         return presenter;
     89     }
     90 
     91     @Override
     92     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
     93             PresenterSelector presenterSelector) {
     94         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
     95         if (THUMBS_PRIMARY) {
     96             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
     97             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
     98         }
     99         return adapter;
    100     }
    101 
    102     @Override
    103     public void onActionClicked(Action action) {
    104         if (shouldDispatchAction(action)) {
    105             dispatchAction(action);
    106             return;
    107         }
    108         super.onActionClicked(action);
    109     }
    110 
    111     @Override
    112     public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
    113         if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
    114             Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
    115             if (shouldDispatchAction(action)) {
    116                 dispatchAction(action);
    117                 return true;
    118             }
    119         }
    120         return super.onKey(view, keyCode, keyEvent);
    121     }
    122 
    123     private boolean shouldDispatchAction(Action action) {
    124         return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
    125     }
    126 
    127     private void dispatchAction(Action action) {
    128         Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
    129         PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
    130         multiAction.nextIndex();
    131         notifyActionChanged(multiAction);
    132     }
    133 
    134     private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
    135         int index;
    136         index = getPrimaryActionsAdapter().indexOf(action);
    137         if (index >= 0) {
    138             getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
    139         } else {
    140             index = getSecondaryActionsAdapter().indexOf(action);
    141             if (index >= 0) {
    142                 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
    143             }
    144         }
    145     }
    146 
    147     private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
    148         return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
    149     }
    150 
    151     private ArrayObjectAdapter getSecondaryActionsAdapter() {
    152         return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
    153     }
    154 
    155     @Override
    156     public boolean hasValidMedia() {
    157         return true;
    158     }
    159 
    160     @Override
    161     public boolean isMediaPlaying() {
    162         return mIsPlaying;
    163     }
    164 
    165     @Override
    166     public CharSequence getMediaTitle() {
    167         return FAUX_TITLE;
    168     }
    169 
    170     @Override
    171     public CharSequence getMediaSubtitle() {
    172         return FAUX_SUBTITLE;
    173     }
    174 
    175     @Override
    176     public int getMediaDuration() {
    177         return FAUX_DURATION;
    178     }
    179 
    180     @Override
    181     public Drawable getMediaArt() {
    182         return null;
    183     }
    184 
    185     @Override
    186     public long getSupportedActions() {
    187         return PlaybackControlGlue.ACTION_PLAY_PAUSE |
    188                 PlaybackControlGlue.ACTION_FAST_FORWARD |
    189                 PlaybackControlGlue.ACTION_REWIND;
    190     }
    191 
    192     @Override
    193     public int getCurrentSpeedId() {
    194         return mSpeed;
    195     }
    196 
    197     @Override
    198     public int getCurrentPosition() {
    199         int speed;
    200         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
    201             speed = 0;
    202         } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
    203             speed = 1;
    204         } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
    205             int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
    206             speed = getFastForwardSpeeds()[index];
    207         } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
    208             int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
    209             speed = -getRewindSpeeds()[index];
    210         } else {
    211             return -1;
    212         }
    213         long position = mStartPosition +
    214                 (System.currentTimeMillis() - mStartTime) * speed;
    215         if (position > getMediaDuration()) {
    216             position = getMediaDuration();
    217             onPlaybackComplete(true);
    218         } else if (position < 0) {
    219             position = 0;
    220             onPlaybackComplete(false);
    221         }
    222         return (int) position;
    223     }
    224 
    225     void onPlaybackComplete(final boolean ended) {
    226         mHandler.post(new Runnable() {
    227             @Override
    228             public void run() {
    229                 if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
    230                     pausePlayback();
    231                 } else {
    232                     startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
    233                 }
    234                 mStartPosition = 0;
    235                 onStateChanged();
    236             }
    237         });
    238     }
    239 
    240     @Override
    241     protected void startPlayback(int speed) {
    242         if (speed == mSpeed) {
    243             return;
    244         }
    245         mStartPosition = getCurrentPosition();
    246         mSpeed = speed;
    247         mIsPlaying = true;
    248         mStartTime = System.currentTimeMillis();
    249     }
    250 
    251     @Override
    252     protected void pausePlayback() {
    253         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
    254             return;
    255         }
    256         mStartPosition = getCurrentPosition();
    257         mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
    258         mIsPlaying = false;
    259     }
    260 
    261     @Override
    262     protected void skipToNext() {
    263         // Not supported
    264     }
    265 
    266     @Override
    267     protected void skipToPrevious() {
    268         // Not supported
    269     }
    270 
    271     @Override
    272     public void enableProgressUpdating(boolean enable) {
    273         mHandler.removeCallbacks(mUpdateProgressRunnable);
    274         if (enable) {
    275             mUpdateProgressRunnable.run();
    276         }
    277     }
    278 };