1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package com.example.android.leanback; 15 16 import android.content.Context; 17 import android.graphics.drawable.Drawable; 18 import android.media.session.MediaController; 19 import android.media.session.MediaSessionManager; 20 import android.os.Build; 21 import android.os.Bundle; 22 import android.os.Handler; 23 import android.os.RemoteException; 24 import android.support.v17.leanback.app.MediaControllerGlue; 25 import android.support.v17.leanback.app.PlaybackControlGlue; 26 import android.support.v17.leanback.widget.Action; 27 import android.support.v17.leanback.widget.ArrayObjectAdapter; 28 import android.support.v17.leanback.widget.PlaybackControlsRow; 29 import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction; 30 import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction; 31 import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction; 32 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; 33 import android.support.v17.leanback.widget.HeaderItem; 34 import android.support.v17.leanback.widget.PresenterSelector; 35 import android.support.v17.leanback.widget.Row; 36 import android.support.v17.leanback.widget.ListRow; 37 import android.support.v17.leanback.widget.Presenter; 38 import android.support.v17.leanback.widget.RowPresenter; 39 import android.support.v17.leanback.widget.ListRowPresenter; 40 import android.support.v17.leanback.widget.OnItemViewSelectedListener; 41 import android.support.v17.leanback.widget.OnItemViewClickedListener; 42 import android.support.v17.leanback.widget.ControlButtonPresenterSelector; 43 import android.support.v17.leanback.widget.SparseArrayObjectAdapter; 44 import android.support.v4.media.session.MediaControllerCompat; 45 import android.support.v4.media.session.MediaSessionCompat; 46 import android.util.Log; 47 import android.widget.Toast; 48 49 import java.util.List; 50 51 public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment { 52 private static final String TAG = "leanback.PlaybackControlsFragment"; 53 54 /** 55 * Change this to choose a different overlay background. 56 */ 57 private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT; 58 59 /** 60 * Change the number of related content rows. 61 */ 62 private static final int RELATED_CONTENT_ROWS = 3; 63 64 /** 65 * Change the location of the thumbs up/down controls 66 */ 67 private static final boolean THUMBS_PRIMARY = true; 68 69 /** 70 * Change this to select hidden 71 */ 72 private static final boolean SECONDARY_HIDDEN = false; 73 74 private static final String FAUX_TITLE = "A short song of silence"; 75 private static final String FAUX_SUBTITLE = "2014"; 76 private static final int FAUX_DURATION = 33 * 1000; 77 78 private static final int ROW_CONTROLS = 0; 79 80 private PlaybackControlGlue mGlue; 81 private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter; 82 private ListRowPresenter mListRowPresenter; 83 84 private RepeatAction mRepeatAction; 85 private ThumbsUpAction mThumbsUpAction; 86 private ThumbsDownAction mThumbsDownAction; 87 private Handler mHandler; 88 89 // These should match the playback service FF behavior 90 private int[] mFastForwardSpeeds = { 2, 3, 4, 5 }; 91 92 private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { 93 @Override 94 public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, 95 RowPresenter.ViewHolder rowViewHolder, Row row) { 96 if (item instanceof Action) { 97 onActionClicked((Action) item); 98 } 99 } 100 }; 101 102 private OnItemViewSelectedListener mOnItemViewSelectedListener = new OnItemViewSelectedListener() { 103 @Override 104 public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, 105 RowPresenter.ViewHolder rowViewHolder, Row row) { 106 Log.i(TAG, "onItemSelected: " + item + " row " + row); 107 } 108 }; 109 110 final Runnable mUpdateProgressRunnable = new Runnable() { 111 @Override 112 public void run() { 113 mGlue.updateProgress(); 114 mHandler.postDelayed(this, mGlue.getUpdatePeriod()); 115 } 116 }; 117 118 public SparseArrayObjectAdapter getAdapter() { 119 return (SparseArrayObjectAdapter) super.getAdapter(); 120 } 121 122 @Override 123 public void onCreate(Bundle savedInstanceState) { 124 Log.i(TAG, "onCreate"); 125 super.onCreate(savedInstanceState); 126 127 setBackgroundType(BACKGROUND_TYPE); 128 setOnItemViewSelectedListener(mOnItemViewSelectedListener); 129 130 createComponents(getActivity()); 131 } 132 133 private void createComponents(Context context) { 134 mHandler = new Handler(); 135 mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context); 136 mThumbsUpAction.setIndex(ThumbsUpAction.OUTLINE); 137 mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context); 138 mThumbsDownAction.setIndex(ThumbsDownAction.OUTLINE); 139 mRepeatAction = new PlaybackControlsRow.RepeatAction(context); 140 141 mGlue = new PlaybackControlGlue(context, this, mFastForwardSpeeds) { 142 private boolean mIsPlaying; 143 private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; 144 private long mStartTime; 145 private long mStartPosition = 0; 146 147 @Override 148 protected SparseArrayObjectAdapter createPrimaryActionsAdapter( 149 PresenterSelector presenterSelector) { 150 return PlaybackOverlayFragment.this.createPrimaryActionsAdapter( 151 presenterSelector); 152 } 153 154 @Override 155 public boolean hasValidMedia() { 156 return true; 157 } 158 159 @Override 160 public boolean isMediaPlaying() { 161 return mIsPlaying; 162 } 163 164 @Override 165 public CharSequence getMediaTitle() { 166 return FAUX_TITLE; 167 } 168 169 @Override 170 public CharSequence getMediaSubtitle() { 171 return FAUX_SUBTITLE; 172 } 173 174 @Override 175 public int getMediaDuration() { 176 return FAUX_DURATION; 177 } 178 179 @Override 180 public Drawable getMediaArt() { 181 return null; 182 } 183 184 @Override 185 public long getSupportedActions() { 186 return PlaybackControlGlue.ACTION_PLAY_PAUSE | 187 PlaybackControlGlue.ACTION_FAST_FORWARD | 188 PlaybackControlGlue.ACTION_REWIND; 189 } 190 191 @Override 192 public int getCurrentSpeedId() { 193 return mSpeed; 194 } 195 196 @Override 197 public int getCurrentPosition() { 198 int speed; 199 if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { 200 speed = 0; 201 } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) { 202 speed = 1; 203 } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { 204 int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; 205 speed = getFastForwardSpeeds()[index]; 206 } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { 207 int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; 208 speed = -getRewindSpeeds()[index]; 209 } else { 210 return -1; 211 } 212 long position = mStartPosition + 213 (System.currentTimeMillis() - mStartTime) * speed; 214 if (position > getMediaDuration()) { 215 position = getMediaDuration(); 216 onPlaybackComplete(true); 217 } else if (position < 0) { 218 position = 0; 219 onPlaybackComplete(false); 220 } 221 return (int) position; 222 } 223 224 void onPlaybackComplete(final boolean ended) { 225 mHandler.post(new Runnable() { 226 @Override 227 public void run() { 228 if (mRepeatAction.getIndex() == RepeatAction.NONE) { 229 pausePlayback(); 230 } else { 231 startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL); 232 } 233 mStartPosition = 0; 234 onStateChanged(); 235 } 236 }); 237 } 238 239 @Override 240 protected void startPlayback(int speed) { 241 if (speed == mSpeed) { 242 return; 243 } 244 mStartPosition = getCurrentPosition(); 245 mSpeed = speed; 246 mIsPlaying = true; 247 mStartTime = System.currentTimeMillis(); 248 } 249 250 @Override 251 protected void pausePlayback() { 252 if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { 253 return; 254 } 255 mStartPosition = getCurrentPosition(); 256 mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; 257 mIsPlaying = false; 258 } 259 260 @Override 261 protected void skipToNext() { 262 // Not supported 263 } 264 265 @Override 266 protected void skipToPrevious() { 267 // Not supported 268 } 269 270 @Override 271 protected void onRowChanged(PlaybackControlsRow row) { 272 PlaybackOverlayFragment.this.onRowChanged(row); 273 } 274 275 @Override 276 public void enableProgressUpdating(boolean enable) { 277 PlaybackOverlayFragment.this.enableProgressUpdating(enable); 278 } 279 280 @Override 281 public int getUpdatePeriod() { 282 return PlaybackOverlayFragment.this.getUpdatePeriod(); 283 } 284 }; 285 286 mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener); 287 288 mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter(); 289 mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN); 290 mListRowPresenter = new ListRowPresenter(); 291 292 setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() { 293 @Override 294 public Presenter getPresenter(Object object) { 295 if (object instanceof PlaybackControlsRow) { 296 return mPlaybackControlsRowPresenter; 297 } else if (object instanceof ListRow) { 298 return mListRowPresenter; 299 } 300 throw new IllegalArgumentException("Unhandled object: " + object); 301 } 302 })); 303 304 // Set secondary control actions 305 PlaybackControlsRow controlsRow = mGlue.getControlsRow(); 306 ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector()); 307 controlsRow.setSecondaryActionsAdapter(adapter); 308 if (!THUMBS_PRIMARY) { 309 adapter.add(mThumbsDownAction); 310 } 311 adapter.add(mRepeatAction); 312 if (!THUMBS_PRIMARY) { 313 adapter.add(mThumbsUpAction); 314 } 315 316 // Add the controls row 317 getAdapter().set(ROW_CONTROLS, controlsRow); 318 319 // Add related content rows 320 for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) { 321 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter()); 322 listRowAdapter.add("Some related content"); 323 listRowAdapter.add("Other related content"); 324 HeaderItem header = new HeaderItem(i, "Row " + i); 325 getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter)); 326 } 327 } 328 329 private SparseArrayObjectAdapter createPrimaryActionsAdapter( 330 PresenterSelector presenterSelector) { 331 SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector); 332 if (THUMBS_PRIMARY) { 333 adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction); 334 adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction); 335 } 336 return adapter; 337 } 338 339 private void onRowChanged(PlaybackControlsRow row) { 340 if (getAdapter() == null) { 341 return; 342 } 343 int index = getAdapter().indexOf(row); 344 if (index >= 0) { 345 getAdapter().notifyArrayItemRangeChanged(index, 1); 346 } 347 } 348 349 private void enableProgressUpdating(boolean enable) { 350 Log.v(TAG, "enableProgressUpdating " + enable + " this " + this); 351 mHandler.removeCallbacks(mUpdateProgressRunnable); 352 if (enable) { 353 mUpdateProgressRunnable.run(); 354 } 355 } 356 357 private int getUpdatePeriod() { 358 int totalTime = mGlue.getControlsRow().getTotalTime(); 359 if (getView() == null || totalTime <= 0) { 360 return 1000; 361 } 362 return Math.max(16, totalTime / getView().getWidth()); 363 } 364 365 private void onActionClicked(Action action) { 366 Log.v(TAG, "onActionClicked " + action); 367 Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show(); 368 if (action instanceof PlaybackControlsRow.MultiAction) { 369 PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action; 370 multiAction.nextIndex(); 371 notifyActionChanged(multiAction); 372 } 373 } 374 375 private SparseArrayObjectAdapter getPrimaryActionsAdapter() { 376 return (SparseArrayObjectAdapter) mGlue.getControlsRow().getPrimaryActionsAdapter(); 377 } 378 379 private ArrayObjectAdapter getSecondaryActionsAdapter() { 380 return (ArrayObjectAdapter) mGlue.getControlsRow().getSecondaryActionsAdapter(); 381 } 382 383 private void notifyActionChanged(PlaybackControlsRow.MultiAction action) { 384 int index; 385 index = getPrimaryActionsAdapter().indexOf(action); 386 if (index >= 0) { 387 getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); 388 } else { 389 index = getSecondaryActionsAdapter().indexOf(action); 390 if (index >= 0) { 391 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); 392 } 393 } 394 } 395 396 @Override 397 public void onStart() { 398 super.onStart(); 399 mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying()); 400 } 401 402 @Override 403 public void onStop() { 404 mGlue.enableProgressUpdating(false); 405 super.onStop(); 406 } 407 } 408