1 /*
2  * Copyright (C) 2015 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  */
16 
17 package com.example.android.leanback;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Handler;
22 import android.support.v17.leanback.app.PlaybackControlGlue;
23 import android.support.v17.leanback.widget.Action;
24 import android.support.v17.leanback.widget.ArrayObjectAdapter;
25 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
26 import android.support.v17.leanback.widget.PlaybackControlsRow;
27 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
28 import android.support.v17.leanback.widget.PresenterSelector;
29 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
30 import android.view.KeyEvent;
31 import android.view.View;
32 import android.widget.Toast;
33 
34 abstract class PlaybackControlHelper extends PlaybackControlGlue {
35     /**
36      * Change the location of the thumbs up/down controls
37      */
38     private static final boolean THUMBS_PRIMARY = true;
39 
40     private static final String FAUX_TITLE = "A short song of silence";
41     private static final String FAUX_SUBTITLE = "2014";
42     private static final int FAUX_DURATION = 33 * 1000;
43 
44     // These should match the playback service FF behavior
45     private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
46 
47     private boolean mIsPlaying;
48     private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
49     private long mStartTime;
50     private long mStartPosition = 0;
51 
52     private PlaybackControlsRow.RepeatAction mRepeatAction;
53     private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
54     private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
55     private PlaybackControlsRow.PictureInPictureAction mPipAction;
56 
57     private Handler mHandler = new Handler();
58     private final Runnable mUpdateProgressRunnable = new Runnable() {
59         @Override
60         public void run() {
61             updateProgress();
62             mHandler.postDelayed(this, getUpdatePeriod());
63         }
64     };
65 
PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment)66     public PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) {
67         super(context, fragment, sFastForwardSpeeds);
68         mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
69         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
70         mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
71         mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
72         mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
73         mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
74     }
75 
76     @Override
createControlsRowAndPresenter()77     public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
78         PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
79 
80         ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
81         getControlsRow().setSecondaryActionsAdapter(adapter);
82         if (!THUMBS_PRIMARY) {
83             adapter.add(mThumbsDownAction);
84         }
85         if (android.os.Build.VERSION.SDK_INT > 23) {
86             adapter.add(mPipAction);
87         }
88         adapter.add(mRepeatAction);
89         if (!THUMBS_PRIMARY) {
90             adapter.add(mThumbsUpAction);
91         }
92 
93         return presenter;
94     }
95 
96     @Override
createPrimaryActionsAdapter( PresenterSelector presenterSelector)97     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
98             PresenterSelector presenterSelector) {
99         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
100         if (THUMBS_PRIMARY) {
101             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
102             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
103         }
104         return adapter;
105     }
106 
107     @Override
onActionClicked(Action action)108     public void onActionClicked(Action action) {
109         if (shouldDispatchAction(action)) {
110             dispatchAction(action);
111             return;
112         }
113         super.onActionClicked(action);
114     }
115 
116     @Override
onKey(View view, int keyCode, KeyEvent keyEvent)117     public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
118         if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
119             Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
120             if (shouldDispatchAction(action)) {
121                 dispatchAction(action);
122                 return true;
123             }
124         }
125         return super.onKey(view, keyCode, keyEvent);
126     }
127 
shouldDispatchAction(Action action)128     private boolean shouldDispatchAction(Action action) {
129         return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
130     }
131 
dispatchAction(Action action)132     private void dispatchAction(Action action) {
133         Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
134         PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
135         multiAction.nextIndex();
136         notifyActionChanged(multiAction);
137     }
138 
notifyActionChanged(PlaybackControlsRow.MultiAction action)139     private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
140         int index;
141         index = getPrimaryActionsAdapter().indexOf(action);
142         if (index >= 0) {
143             getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
144         } else {
145             index = getSecondaryActionsAdapter().indexOf(action);
146             if (index >= 0) {
147                 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
148             }
149         }
150     }
151 
getPrimaryActionsAdapter()152     private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
153         return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
154     }
155 
getSecondaryActionsAdapter()156     private ArrayObjectAdapter getSecondaryActionsAdapter() {
157         return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
158     }
159 
160     @Override
hasValidMedia()161     public boolean hasValidMedia() {
162         return true;
163     }
164 
165     @Override
isMediaPlaying()166     public boolean isMediaPlaying() {
167         return mIsPlaying;
168     }
169 
170     @Override
getMediaTitle()171     public CharSequence getMediaTitle() {
172         return FAUX_TITLE;
173     }
174 
175     @Override
getMediaSubtitle()176     public CharSequence getMediaSubtitle() {
177         return FAUX_SUBTITLE;
178     }
179 
180     @Override
getMediaDuration()181     public int getMediaDuration() {
182         return FAUX_DURATION;
183     }
184 
185     @Override
getMediaArt()186     public Drawable getMediaArt() {
187         return null;
188     }
189 
190     @Override
getSupportedActions()191     public long getSupportedActions() {
192         return PlaybackControlGlue.ACTION_PLAY_PAUSE |
193                 PlaybackControlGlue.ACTION_FAST_FORWARD |
194                 PlaybackControlGlue.ACTION_REWIND;
195     }
196 
197     @Override
getCurrentSpeedId()198     public int getCurrentSpeedId() {
199         return mSpeed;
200     }
201 
202     @Override
getCurrentPosition()203     public int getCurrentPosition() {
204         int speed;
205         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
206             speed = 0;
207         } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
208             speed = 1;
209         } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
210             int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
211             speed = getFastForwardSpeeds()[index];
212         } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
213             int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
214             speed = -getRewindSpeeds()[index];
215         } else {
216             return -1;
217         }
218         long position = mStartPosition +
219                 (System.currentTimeMillis() - mStartTime) * speed;
220         if (position > getMediaDuration()) {
221             position = getMediaDuration();
222             onPlaybackComplete(true);
223         } else if (position < 0) {
224             position = 0;
225             onPlaybackComplete(false);
226         }
227         return (int) position;
228     }
229 
onPlaybackComplete(final boolean ended)230     void onPlaybackComplete(final boolean ended) {
231         mHandler.post(new Runnable() {
232             @Override
233             public void run() {
234                 if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
235                     pausePlayback();
236                 } else {
237                     startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
238                 }
239                 mStartPosition = 0;
240                 onStateChanged();
241             }
242         });
243     }
244 
245     @Override
startPlayback(int speed)246     protected void startPlayback(int speed) {
247         if (speed == mSpeed) {
248             return;
249         }
250         mStartPosition = getCurrentPosition();
251         mSpeed = speed;
252         mIsPlaying = true;
253         mStartTime = System.currentTimeMillis();
254     }
255 
256     @Override
pausePlayback()257     protected void pausePlayback() {
258         if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
259             return;
260         }
261         mStartPosition = getCurrentPosition();
262         mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
263         mIsPlaying = false;
264     }
265 
266     @Override
skipToNext()267     protected void skipToNext() {
268         // Not supported
269     }
270 
271     @Override
skipToPrevious()272     protected void skipToPrevious() {
273         // Not supported
274     }
275 
276     @Override
enableProgressUpdating(boolean enable)277     public void enableProgressUpdating(boolean enable) {
278         mHandler.removeCallbacks(mUpdateProgressRunnable);
279         if (enable) {
280             mUpdateProgressRunnable.run();
281         }
282     }
283 };
284