Updated to clarify division of functionality.

PlaybackControlHelper can be used in BrowseFragment.

Change-Id: Iac37e0a4f51ed1fab5edcff20d4a7037d4cc9ef9
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 0000000..c54f0d5
--- /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 c37ca20..0cb981a 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 @@
 
 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.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";
 
@@ -62,39 +53,23 @@
     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 @@
         }
     };
 
-    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 @@
     }
 
     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 @@
             }
         }));
 
-        // 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 @@
         }
     }
 
-    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();