From 8f295cdef4f4a5eafb5728ee1689a1b6c3bb7379 Mon Sep 17 00:00:00 2001 From: Craig Stout Date: Thu, 12 Feb 2015 11:24:28 -0800 Subject: [PATCH] Updated to clarify division of functionality. PlaybackControlHelper can be used in BrowseFragment. Change-Id: Iac37e0a4f51ed1fab5edcff20d4a7037d4cc9ef9 --- .../leanback/PlaybackControlHelper.java | 278 ++++++++++++++++++ .../leanback/PlaybackOverlayFragment.java | 276 ++--------------- 2 files changed, 295 insertions(+), 259 deletions(-) create mode 100644 samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java new file mode 100644 index 000000000..c54f0d525 --- /dev/null +++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.example.android.leanback; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.support.v17.leanback.app.PlaybackControlGlue; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ControlButtonPresenterSelector; +import android.support.v17.leanback.widget.PlaybackControlsRow; +import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; +import android.support.v17.leanback.widget.PresenterSelector; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Toast; + +abstract class PlaybackControlHelper extends PlaybackControlGlue { + /** + * Change the location of the thumbs up/down controls + */ + private static final boolean THUMBS_PRIMARY = true; + + private static final String FAUX_TITLE = "A short song of silence"; + private static final String FAUX_SUBTITLE = "2014"; + private static final int FAUX_DURATION = 33 * 1000; + + // These should match the playback service FF behavior + private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 }; + + private boolean mIsPlaying; + private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; + private long mStartTime; + private long mStartPosition = 0; + + private PlaybackControlsRow.RepeatAction mRepeatAction; + private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction; + private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction; + + private Handler mHandler = new Handler(); + private final Runnable mUpdateProgressRunnable = new Runnable() { + @Override + public void run() { + updateProgress(); + mHandler.postDelayed(this, getUpdatePeriod()); + } + }; + + public PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) { + super(context, fragment, sFastForwardSpeeds); + mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context); + mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE); + mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context); + mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE); + mRepeatAction = new PlaybackControlsRow.RepeatAction(context); + } + + @Override + public PlaybackControlsRowPresenter createControlsRowAndPresenter() { + PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter(); + + ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector()); + getControlsRow().setSecondaryActionsAdapter(adapter); + if (!THUMBS_PRIMARY) { + adapter.add(mThumbsDownAction); + } + adapter.add(mRepeatAction); + if (!THUMBS_PRIMARY) { + adapter.add(mThumbsUpAction); + } + + return presenter; + } + + @Override + protected SparseArrayObjectAdapter createPrimaryActionsAdapter( + PresenterSelector presenterSelector) { + SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector); + if (THUMBS_PRIMARY) { + adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction); + adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction); + } + return adapter; + } + + @Override + public void onActionClicked(Action action) { + if (shouldDispatchAction(action)) { + dispatchAction(action); + return; + } + super.onActionClicked(action); + } + + @Override + public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { + if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode()); + if (shouldDispatchAction(action)) { + dispatchAction(action); + return true; + } + } + return super.onKey(view, keyCode, keyEvent); + } + + private boolean shouldDispatchAction(Action action) { + return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction; + } + + private void dispatchAction(Action action) { + Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show(); + PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action; + multiAction.nextIndex(); + notifyActionChanged(multiAction); + } + + private void notifyActionChanged(PlaybackControlsRow.MultiAction action) { + int index; + index = getPrimaryActionsAdapter().indexOf(action); + if (index >= 0) { + getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); + } else { + index = getSecondaryActionsAdapter().indexOf(action); + if (index >= 0) { + getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); + } + } + } + + private SparseArrayObjectAdapter getPrimaryActionsAdapter() { + return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter(); + } + + private ArrayObjectAdapter getSecondaryActionsAdapter() { + return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter(); + } + + @Override + public boolean hasValidMedia() { + return true; + } + + @Override + public boolean isMediaPlaying() { + return mIsPlaying; + } + + @Override + public CharSequence getMediaTitle() { + return FAUX_TITLE; + } + + @Override + public CharSequence getMediaSubtitle() { + return FAUX_SUBTITLE; + } + + @Override + public int getMediaDuration() { + return FAUX_DURATION; + } + + @Override + public Drawable getMediaArt() { + return null; + } + + @Override + public long getSupportedActions() { + return PlaybackControlGlue.ACTION_PLAY_PAUSE | + PlaybackControlGlue.ACTION_FAST_FORWARD | + PlaybackControlGlue.ACTION_REWIND; + } + + @Override + public int getCurrentSpeedId() { + return mSpeed; + } + + @Override + public int getCurrentPosition() { + int speed; + if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { + speed = 0; + } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) { + speed = 1; + } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { + int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; + speed = getFastForwardSpeeds()[index]; + } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { + int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; + speed = -getRewindSpeeds()[index]; + } else { + return -1; + } + long position = mStartPosition + + (System.currentTimeMillis() - mStartTime) * speed; + if (position > getMediaDuration()) { + position = getMediaDuration(); + onPlaybackComplete(true); + } else if (position < 0) { + position = 0; + onPlaybackComplete(false); + } + return (int) position; + } + + void onPlaybackComplete(final boolean ended) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) { + pausePlayback(); + } else { + startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL); + } + mStartPosition = 0; + onStateChanged(); + } + }); + } + + @Override + protected void startPlayback(int speed) { + if (speed == mSpeed) { + return; + } + mStartPosition = getCurrentPosition(); + mSpeed = speed; + mIsPlaying = true; + mStartTime = System.currentTimeMillis(); + } + + @Override + protected void pausePlayback() { + if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { + return; + } + mStartPosition = getCurrentPosition(); + mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; + mIsPlaying = false; + } + + @Override + protected void skipToNext() { + // Not supported + } + + @Override + protected void skipToPrevious() { + // Not supported + } + + @Override + public void enableProgressUpdating(boolean enable) { + mHandler.removeCallbacks(mUpdateProgressRunnable); + if (enable) { + mUpdateProgressRunnable.run(); + } + } +}; \ No newline at end of file diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java index c37ca204e..0cb981ad8 100644 --- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java +++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java @@ -15,13 +15,8 @@ package com.example.android.leanback; import android.content.Context; import android.graphics.drawable.Drawable; -import android.media.session.MediaController; -import android.media.session.MediaSessionManager; -import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.RemoteException; -import android.support.v17.leanback.app.MediaControllerGlue; import android.support.v17.leanback.app.PlaybackControlGlue; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.ArrayObjectAdapter; @@ -41,13 +36,9 @@ import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.ControlButtonPresenterSelector; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; -import android.support.v4.media.session.MediaControllerCompat; -import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import android.widget.Toast; -import java.util.List; - public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment { private static final String TAG = "leanback.PlaybackControlsFragment"; @@ -61,40 +52,24 @@ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.Pl */ private static final int RELATED_CONTENT_ROWS = 3; - /** - * Change the location of the thumbs up/down controls - */ - private static final boolean THUMBS_PRIMARY = true; - /** * Change this to select hidden */ private static final boolean SECONDARY_HIDDEN = false; - private static final String FAUX_TITLE = "A short song of silence"; - private static final String FAUX_SUBTITLE = "2014"; - private static final int FAUX_DURATION = 33 * 1000; - private static final int ROW_CONTROLS = 0; - private PlaybackControlGlue mGlue; + private PlaybackControlHelper mGlue; private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter; private ListRowPresenter mListRowPresenter; - private RepeatAction mRepeatAction; - private ThumbsUpAction mThumbsUpAction; - private ThumbsDownAction mThumbsDownAction; - private Handler mHandler; - - // These should match the playback service FF behavior - private int[] mFastForwardSpeeds = { 2, 3, 4, 5 }; - private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { + Log.i(TAG, "onItemClicked: " + item + " row " + row); if (item instanceof Action) { - onActionClicked((Action) item); + mGlue.onActionClicked((Action) item); } } }; @@ -107,14 +82,6 @@ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.Pl } }; - final Runnable mUpdateProgressRunnable = new Runnable() { - @Override - public void run() { - mGlue.updateProgress(); - mHandler.postDelayed(this, mGlue.getUpdatePeriod()); - } - }; - public SparseArrayObjectAdapter getAdapter() { return (SparseArrayObjectAdapter) super.getAdapter(); } @@ -131,155 +98,25 @@ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.Pl } private void createComponents(Context context) { - mHandler = new Handler(); - mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context); - mThumbsUpAction.setIndex(ThumbsUpAction.OUTLINE); - mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context); - mThumbsDownAction.setIndex(ThumbsDownAction.OUTLINE); - mRepeatAction = new PlaybackControlsRow.RepeatAction(context); - - mGlue = new PlaybackControlGlue(context, this, mFastForwardSpeeds) { - private boolean mIsPlaying; - private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; - private long mStartTime; - private long mStartPosition = 0; - + mGlue = new PlaybackControlHelper(context, this) { @Override - protected SparseArrayObjectAdapter createPrimaryActionsAdapter( - PresenterSelector presenterSelector) { - return PlaybackOverlayFragment.this.createPrimaryActionsAdapter( - presenterSelector); - } - - @Override - public boolean hasValidMedia() { - return true; - } - - @Override - public boolean isMediaPlaying() { - return mIsPlaying; - } - - @Override - public CharSequence getMediaTitle() { - return FAUX_TITLE; - } - - @Override - public CharSequence getMediaSubtitle() { - return FAUX_SUBTITLE; - } - - @Override - public int getMediaDuration() { - return FAUX_DURATION; - } - - @Override - public Drawable getMediaArt() { - return null; - } - - @Override - public long getSupportedActions() { - return PlaybackControlGlue.ACTION_PLAY_PAUSE | - PlaybackControlGlue.ACTION_FAST_FORWARD | - PlaybackControlGlue.ACTION_REWIND; - } - - @Override - public int getCurrentSpeedId() { - return mSpeed; - } - - @Override - public int getCurrentPosition() { - int speed; - if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { - speed = 0; - } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) { - speed = 1; - } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { - int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; - speed = getFastForwardSpeeds()[index]; - } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) { - int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0; - speed = -getRewindSpeeds()[index]; - } else { - return -1; + public int getUpdatePeriod() { + int totalTime = getControlsRow().getTotalTime(); + if (getView() == null || totalTime <= 0) { + return 1000; } - long position = mStartPosition + - (System.currentTimeMillis() - mStartTime) * speed; - if (position > getMediaDuration()) { - position = getMediaDuration(); - onPlaybackComplete(true); - } else if (position < 0) { - position = 0; - onPlaybackComplete(false); - } - return (int) position; - } - - void onPlaybackComplete(final boolean ended) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (mRepeatAction.getIndex() == RepeatAction.NONE) { - pausePlayback(); - } else { - startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL); - } - mStartPosition = 0; - onStateChanged(); - } - }); - } - - @Override - protected void startPlayback(int speed) { - if (speed == mSpeed) { - return; - } - mStartPosition = getCurrentPosition(); - mSpeed = speed; - mIsPlaying = true; - mStartTime = System.currentTimeMillis(); - } - - @Override - protected void pausePlayback() { - if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) { - return; - } - mStartPosition = getCurrentPosition(); - mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED; - mIsPlaying = false; - } - - @Override - protected void skipToNext() { - // Not supported - } - - @Override - protected void skipToPrevious() { - // Not supported + return Math.max(16, totalTime / getView().getWidth()); } @Override protected void onRowChanged(PlaybackControlsRow row) { - PlaybackOverlayFragment.this.onRowChanged(row); - } - - @Override - public void enableProgressUpdating(boolean enable) { - PlaybackOverlayFragment.this.enableProgressUpdating(enable); - } - - @Override - public int getUpdatePeriod() { - return PlaybackOverlayFragment.this.getUpdatePeriod(); + if (getAdapter() == null) { + return; + } + int index = getAdapter().indexOf(row); + if (index >= 0) { + getAdapter().notifyArrayItemRangeChanged(index, 1); + } } }; @@ -301,20 +138,8 @@ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.Pl } })); - // Set secondary control actions - PlaybackControlsRow controlsRow = mGlue.getControlsRow(); - ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector()); - controlsRow.setSecondaryActionsAdapter(adapter); - if (!THUMBS_PRIMARY) { - adapter.add(mThumbsDownAction); - } - adapter.add(mRepeatAction); - if (!THUMBS_PRIMARY) { - adapter.add(mThumbsUpAction); - } - // Add the controls row - getAdapter().set(ROW_CONTROLS, controlsRow); + getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow()); // Add related content rows for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) { @@ -326,73 +151,6 @@ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.Pl } } - private SparseArrayObjectAdapter createPrimaryActionsAdapter( - PresenterSelector presenterSelector) { - SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector); - if (THUMBS_PRIMARY) { - adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction); - adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction); - } - return adapter; - } - - private void onRowChanged(PlaybackControlsRow row) { - if (getAdapter() == null) { - return; - } - int index = getAdapter().indexOf(row); - if (index >= 0) { - getAdapter().notifyArrayItemRangeChanged(index, 1); - } - } - - private void enableProgressUpdating(boolean enable) { - Log.v(TAG, "enableProgressUpdating " + enable + " this " + this); - mHandler.removeCallbacks(mUpdateProgressRunnable); - if (enable) { - mUpdateProgressRunnable.run(); - } - } - - private int getUpdatePeriod() { - int totalTime = mGlue.getControlsRow().getTotalTime(); - if (getView() == null || totalTime <= 0) { - return 1000; - } - return Math.max(16, totalTime / getView().getWidth()); - } - - private void onActionClicked(Action action) { - Log.v(TAG, "onActionClicked " + action); - Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show(); - if (action instanceof PlaybackControlsRow.MultiAction) { - PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action; - multiAction.nextIndex(); - notifyActionChanged(multiAction); - } - } - - private SparseArrayObjectAdapter getPrimaryActionsAdapter() { - return (SparseArrayObjectAdapter) mGlue.getControlsRow().getPrimaryActionsAdapter(); - } - - private ArrayObjectAdapter getSecondaryActionsAdapter() { - return (ArrayObjectAdapter) mGlue.getControlsRow().getSecondaryActionsAdapter(); - } - - private void notifyActionChanged(PlaybackControlsRow.MultiAction action) { - int index; - index = getPrimaryActionsAdapter().indexOf(action); - if (index >= 0) { - getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); - } else { - index = getSecondaryActionsAdapter().indexOf(action); - if (index >= 0) { - getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1); - } - } - } - @Override public void onStart() { super.onStart();