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 PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment)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 createControlsRowAndPresenter()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 createPrimaryActionsAdapter( PresenterSelector presenterSelector)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 onActionClicked(Action action)103 public void onActionClicked(Action action) { 104 if (shouldDispatchAction(action)) { 105 dispatchAction(action); 106 return; 107 } 108 super.onActionClicked(action); 109 } 110 111 @Override onKey(View view, int keyCode, KeyEvent keyEvent)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 shouldDispatchAction(Action action)123 private boolean shouldDispatchAction(Action action) { 124 return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction; 125 } 126 dispatchAction(Action action)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 notifyActionChanged(PlaybackControlsRow.MultiAction action)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 getPrimaryActionsAdapter()147 private SparseArrayObjectAdapter getPrimaryActionsAdapter() { 148 return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter(); 149 } 150 getSecondaryActionsAdapter()151 private ArrayObjectAdapter getSecondaryActionsAdapter() { 152 return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter(); 153 } 154 155 @Override hasValidMedia()156 public boolean hasValidMedia() { 157 return true; 158 } 159 160 @Override isMediaPlaying()161 public boolean isMediaPlaying() { 162 return mIsPlaying; 163 } 164 165 @Override getMediaTitle()166 public CharSequence getMediaTitle() { 167 return FAUX_TITLE; 168 } 169 170 @Override getMediaSubtitle()171 public CharSequence getMediaSubtitle() { 172 return FAUX_SUBTITLE; 173 } 174 175 @Override getMediaDuration()176 public int getMediaDuration() { 177 return FAUX_DURATION; 178 } 179 180 @Override getMediaArt()181 public Drawable getMediaArt() { 182 return null; 183 } 184 185 @Override getSupportedActions()186 public long getSupportedActions() { 187 return PlaybackControlGlue.ACTION_PLAY_PAUSE | 188 PlaybackControlGlue.ACTION_FAST_FORWARD | 189 PlaybackControlGlue.ACTION_REWIND; 190 } 191 192 @Override getCurrentSpeedId()193 public int getCurrentSpeedId() { 194 return mSpeed; 195 } 196 197 @Override getCurrentPosition()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 onPlaybackComplete(final boolean ended)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 startPlayback(int speed)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 pausePlayback()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 skipToNext()262 protected void skipToNext() { 263 // Not supported 264 } 265 266 @Override skipToPrevious()267 protected void skipToPrevious() { 268 // Not supported 269 } 270 271 @Override enableProgressUpdating(boolean enable)272 public void enableProgressUpdating(boolean enable) { 273 mHandler.removeCallbacks(mUpdateProgressRunnable); 274 if (enable) { 275 mUpdateProgressRunnable.run(); 276 } 277 } 278 };