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