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