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