PlaybackTestFragment.java revision 5a4d3a98d681cc9042b28a923aa5a23a6b961998
1/* 2 * Copyright (C) 2016 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 */ 16package android.support.v17.leanback.app; 17 18import android.content.Context; 19import android.graphics.drawable.Drawable; 20import android.os.Bundle; 21import android.os.Handler; 22import android.support.v17.leanback.media.PlaybackControlGlue; 23import android.support.v17.leanback.test.R; 24import android.support.v17.leanback.widget.Action; 25import android.support.v17.leanback.widget.ArrayObjectAdapter; 26import android.support.v17.leanback.widget.ClassPresenterSelector; 27import android.support.v17.leanback.widget.HeaderItem; 28import android.support.v17.leanback.widget.ListRow; 29import android.support.v17.leanback.widget.ListRowPresenter; 30import android.support.v17.leanback.widget.OnItemViewClickedListener; 31import android.support.v17.leanback.widget.PlaybackControlsRow; 32import android.support.v17.leanback.widget.Presenter; 33import android.support.v17.leanback.widget.PresenterSelector; 34import android.support.v17.leanback.widget.Row; 35import android.support.v17.leanback.widget.RowPresenter; 36import android.support.v17.leanback.widget.SparseArrayObjectAdapter; 37import android.util.Log; 38import android.view.KeyEvent; 39import android.view.View; 40import android.widget.Toast; 41 42public class PlaybackTestFragment extends PlaybackFragment { 43 private static final String TAG = "PlaybackTestFragment"; 44 45 /** 46 * Change this to choose a different overlay background. 47 */ 48 private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT; 49 50 /** 51 * Change this to select hidden 52 */ 53 private static final boolean SECONDARY_HIDDEN = false; 54 55 /** 56 * Change the number of related content rows. 57 */ 58 private static final int RELATED_CONTENT_ROWS = 3; 59 60 private android.support.v17.leanback.media.PlaybackControlGlue mGlue; 61 boolean mDestroyCalled; 62 63 public SparseArrayObjectAdapter getAdapter() { 64 return (SparseArrayObjectAdapter) super.getAdapter(); 65 } 66 67 private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { 68 @Override 69 public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, 70 RowPresenter.ViewHolder rowViewHolder, Row row) { 71 Log.d(TAG, "onItemClicked: " + item + " row " + row); 72 } 73 }; 74 75 @Override 76 public void onDestroy() { 77 super.onDestroy(); 78 mDestroyCalled = true; 79 } 80 81 @Override 82 public void onCreate(Bundle savedInstanceState) { 83 Log.i(TAG, "onCreate"); 84 super.onCreate(savedInstanceState); 85 86 setBackgroundType(BACKGROUND_TYPE); 87 88 createComponents(getActivity()); 89 setOnItemViewClickedListener(mOnItemViewClickedListener); 90 } 91 92 private void createComponents(Context context) { 93 mGlue = new PlaybackControlHelper(context) { 94 @Override 95 public int getUpdatePeriod() { 96 int totalTime = getControlsRow().getTotalTime(); 97 if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) { 98 return 1000; 99 } 100 return Math.max(16, totalTime / getView().getWidth()); 101 } 102 103 @Override 104 public void onActionClicked(Action action) { 105 if (action.getId() == R.id.lb_control_picture_in_picture) { 106 getActivity().enterPictureInPictureMode(); 107 return; 108 } 109 super.onActionClicked(action); 110 } 111 112 @Override 113 protected void onCreateControlsRowAndPresenter() { 114 super.onCreateControlsRowAndPresenter(); 115 getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN); 116 } 117 }; 118 119 mGlue.setHost(new PlaybackFragmentGlueHost(this)); 120 ClassPresenterSelector selector = new ClassPresenterSelector(); 121 selector.addClassPresenter(ListRow.class, new ListRowPresenter()); 122 123 setAdapter(new SparseArrayObjectAdapter(selector)); 124 125 // Add related content rows 126 for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) { 127 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter()); 128 listRowAdapter.add("Some related content"); 129 listRowAdapter.add("Other related content"); 130 HeaderItem header = new HeaderItem(i, "Row " + i); 131 getAdapter().set(1 + i, new ListRow(header, listRowAdapter)); 132 } 133 } 134 135 public PlaybackControlGlue getGlue() { 136 return mGlue; 137 } 138 139 abstract static class PlaybackControlHelper extends PlaybackControlGlue { 140 /** 141 * Change the location of the thumbs up/down controls 142 */ 143 private static final boolean THUMBS_PRIMARY = true; 144 145 private static final String FAUX_TITLE = "A short song of silence"; 146 private static final String FAUX_SUBTITLE = "2014"; 147 private static final int FAUX_DURATION = 33 * 1000; 148 149 // These should match the playback service FF behavior 150 private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 }; 151 152 private boolean mIsPlaying; 153 private int mSpeed = PLAYBACK_SPEED_PAUSED; 154 private long mStartTime; 155 private long mStartPosition = 0; 156 157 private PlaybackControlsRow.RepeatAction mRepeatAction; 158 private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction; 159 private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction; 160 private PlaybackControlsRow.PictureInPictureAction mPipAction; 161 private static Handler sProgressHandler = new Handler(); 162 163 private final Runnable mUpdateProgressRunnable = new Runnable() { 164 @Override 165 public void run() { 166 updateProgress(); 167 sProgressHandler.postDelayed(this, getUpdatePeriod()); 168 } 169 }; 170 171 PlaybackControlHelper(Context context) { 172 super(context, sFastForwardSpeeds); 173 mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context); 174 mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE); 175 mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context); 176 mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE); 177 mRepeatAction = new PlaybackControlsRow.RepeatAction(context); 178 mPipAction = new PlaybackControlsRow.PictureInPictureAction(context); 179 } 180 181 @Override 182 protected SparseArrayObjectAdapter createPrimaryActionsAdapter( 183 PresenterSelector presenterSelector) { 184 SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector); 185 if (THUMBS_PRIMARY) { 186 adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction); 187 adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction); 188 } 189 return adapter; 190 } 191 192 @Override 193 public void onActionClicked(Action action) { 194 if (shouldDispatchAction(action)) { 195 dispatchAction(action); 196 return; 197 } 198 super.onActionClicked(action); 199 } 200 201 @Override 202 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { 203 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 204 Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode()); 205 if (shouldDispatchAction(action)) { 206 dispatchAction(action); 207 return true; 208 } 209 } 210 return super.onKey(view, keyCode, keyEvent); 211 } 212 213 private boolean shouldDispatchAction(Action action) { 214 return action == mRepeatAction || action == mThumbsUpAction 215 || action == mThumbsDownAction; 216 } 217 218 private void dispatchAction(Action action) { 219 Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show(); 220 PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action; 221 multiAction.nextIndex(); 222 notifyActionChanged(multiAction); 223 } 224 225 private void notifyActionChanged(PlaybackControlsRow.MultiAction action) { 226 int index; 227 index = getPrimaryActionsAdapter().indexOf(action); 228 if (index >= 0) { 229 getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); 230 } else { 231 index = getSecondaryActionsAdapter().indexOf(action); 232 if (index >= 0) { 233 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); 234 } 235 } 236 } 237 238 private SparseArrayObjectAdapter getPrimaryActionsAdapter() { 239 return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter(); 240 } 241 242 private ArrayObjectAdapter getSecondaryActionsAdapter() { 243 return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter(); 244 } 245 246 @Override 247 public boolean hasValidMedia() { 248 return true; 249 } 250 251 @Override 252 public boolean isMediaPlaying() { 253 return mIsPlaying; 254 } 255 256 @Override 257 public CharSequence getMediaTitle() { 258 return FAUX_TITLE; 259 } 260 261 @Override 262 public CharSequence getMediaSubtitle() { 263 return FAUX_SUBTITLE; 264 } 265 266 @Override 267 public int getMediaDuration() { 268 return FAUX_DURATION; 269 } 270 271 @Override 272 public Drawable getMediaArt() { 273 return null; 274 } 275 276 @Override 277 public long getSupportedActions() { 278 return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND; 279 } 280 281 @Override 282 public int getCurrentSpeedId() { 283 return mSpeed; 284 } 285 286 @Override 287 public int getCurrentPosition() { 288 int speed; 289 if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { 290 speed = 0; 291 } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) { 292 speed = 1; 293 } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { 294 int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; 295 speed = getFastForwardSpeeds()[index]; 296 } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { 297 int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; 298 speed = -getRewindSpeeds()[index]; 299 } else { 300 return -1; 301 } 302 long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed; 303 if (position > getMediaDuration()) { 304 position = getMediaDuration(); 305 onPlaybackComplete(true); 306 } else if (position < 0) { 307 position = 0; 308 onPlaybackComplete(false); 309 } 310 return (int) position; 311 } 312 313 void onPlaybackComplete(final boolean ended) { 314 sProgressHandler.post(new Runnable() { 315 @Override 316 public void run() { 317 if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) { 318 pause(); 319 } else { 320 play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL); 321 } 322 mStartPosition = 0; 323 onStateChanged(); 324 } 325 }); 326 } 327 328 @Override 329 public void play(int speed) { 330 if (speed == mSpeed) { 331 return; 332 } 333 mStartPosition = getCurrentPosition(); 334 mSpeed = speed; 335 mIsPlaying = true; 336 mStartTime = System.currentTimeMillis(); 337 } 338 339 @Override 340 public void pause() { 341 if (mSpeed == PLAYBACK_SPEED_PAUSED) { 342 return; 343 } 344 mStartPosition = getCurrentPosition(); 345 mSpeed = PLAYBACK_SPEED_PAUSED; 346 mIsPlaying = false; 347 } 348 349 @Override 350 public void next() { 351 // Not supported 352 } 353 354 @Override 355 public void previous() { 356 // Not supported 357 } 358 359 @Override 360 public void enableProgressUpdating(boolean enable) { 361 sProgressHandler.removeCallbacks(mUpdateProgressRunnable); 362 if (enable) { 363 mUpdateProgressRunnable.run(); 364 } 365 } 366 } 367} 368