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