• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package com.example.android.leanback;
15 
16 import android.content.Context;
17 import android.graphics.drawable.Drawable;
18 import android.media.session.MediaController;
19 import android.media.session.MediaSessionManager;
20 import android.os.Build;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.RemoteException;
24 import android.support.v17.leanback.app.MediaControllerGlue;
25 import android.support.v17.leanback.app.PlaybackControlGlue;
26 import android.support.v17.leanback.widget.Action;
27 import android.support.v17.leanback.widget.ArrayObjectAdapter;
28 import android.support.v17.leanback.widget.PlaybackControlsRow;
29 import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction;
30 import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction;
31 import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction;
32 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
33 import android.support.v17.leanback.widget.HeaderItem;
34 import android.support.v17.leanback.widget.PresenterSelector;
35 import android.support.v17.leanback.widget.Row;
36 import android.support.v17.leanback.widget.ListRow;
37 import android.support.v17.leanback.widget.Presenter;
38 import android.support.v17.leanback.widget.RowPresenter;
39 import android.support.v17.leanback.widget.ListRowPresenter;
40 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
41 import android.support.v17.leanback.widget.OnItemViewClickedListener;
42 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
43 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
44 import android.support.v4.media.session.MediaControllerCompat;
45 import android.support.v4.media.session.MediaSessionCompat;
46 import android.util.Log;
47 import android.widget.Toast;
48 
49 import java.util.List;
50 
51 public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {
52     private static final String TAG = "leanback.PlaybackControlsFragment";
53 
54     /**
55      * Change this to choose a different overlay background.
56      */
57     private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
58 
59     /**
60      * Change the number of related content rows.
61      */
62     private static final int RELATED_CONTENT_ROWS = 3;
63 
64     /**
65      * Change the location of the thumbs up/down controls
66      */
67     private static final boolean THUMBS_PRIMARY = true;
68 
69     /**
70      * Change this to select hidden
71      */
72     private static final boolean SECONDARY_HIDDEN = false;
73 
74     private static final String FAUX_TITLE = "A short song of silence";
75     private static final String FAUX_SUBTITLE = "2014";
76     private static final int FAUX_DURATION = 33 * 1000;
77 
78     private static final int ROW_CONTROLS = 0;
79 
80     private PlaybackControlGlue mGlue;
81     private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter;
82     private ListRowPresenter mListRowPresenter;
83 
84     private RepeatAction mRepeatAction;
85     private ThumbsUpAction mThumbsUpAction;
86     private ThumbsDownAction mThumbsDownAction;
87     private Handler mHandler;
88 
89     // These should match the playback service FF behavior
90     private int[] mFastForwardSpeeds = { 2, 3, 4, 5 };
91 
92     private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
93         @Override
94         public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
95                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
96             if (item instanceof Action) {
97                 onActionClicked((Action) item);
98             }
99         }
100     };
101 
102     private OnItemViewSelectedListener mOnItemViewSelectedListener = new OnItemViewSelectedListener() {
103         @Override
104         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
105                                    RowPresenter.ViewHolder rowViewHolder, Row row) {
106             Log.i(TAG, "onItemSelected: " + item + " row " + row);
107         }
108     };
109 
110     final Runnable mUpdateProgressRunnable = new Runnable() {
111         @Override
112         public void run() {
113             mGlue.updateProgress();
114             mHandler.postDelayed(this, mGlue.getUpdatePeriod());
115         }
116     };
117 
getAdapter()118     public SparseArrayObjectAdapter getAdapter() {
119         return (SparseArrayObjectAdapter) super.getAdapter();
120     }
121 
122     @Override
onCreate(Bundle savedInstanceState)123     public void onCreate(Bundle savedInstanceState) {
124         Log.i(TAG, "onCreate");
125         super.onCreate(savedInstanceState);
126 
127         setBackgroundType(BACKGROUND_TYPE);
128         setOnItemViewSelectedListener(mOnItemViewSelectedListener);
129 
130         createComponents(getActivity());
131     }
132 
createComponents(Context context)133     private void createComponents(Context context) {
134         mHandler = new Handler();
135         mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
136         mThumbsUpAction.setIndex(ThumbsUpAction.OUTLINE);
137         mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
138         mThumbsDownAction.setIndex(ThumbsDownAction.OUTLINE);
139         mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
140 
141         mGlue = new PlaybackControlGlue(context, this, mFastForwardSpeeds) {
142             private boolean mIsPlaying;
143             private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
144             private long mStartTime;
145             private long mStartPosition = 0;
146 
147             @Override
148             protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
149                     PresenterSelector presenterSelector) {
150                 return PlaybackOverlayFragment.this.createPrimaryActionsAdapter(
151                         presenterSelector);
152             }
153 
154             @Override
155             public boolean hasValidMedia() {
156                 return true;
157             }
158 
159             @Override
160             public boolean isMediaPlaying() {
161                 return mIsPlaying;
162             }
163 
164             @Override
165             public CharSequence getMediaTitle() {
166                 return FAUX_TITLE;
167             }
168 
169             @Override
170             public CharSequence getMediaSubtitle() {
171                 return FAUX_SUBTITLE;
172             }
173 
174             @Override
175             public int getMediaDuration() {
176                 return FAUX_DURATION;
177             }
178 
179             @Override
180             public Drawable getMediaArt() {
181                 return null;
182             }
183 
184             @Override
185             public long getSupportedActions() {
186                 return PlaybackControlGlue.ACTION_PLAY_PAUSE |
187                         PlaybackControlGlue.ACTION_FAST_FORWARD |
188                         PlaybackControlGlue.ACTION_REWIND;
189             }
190 
191             @Override
192             public int getCurrentSpeedId() {
193                 return mSpeed;
194             }
195 
196             @Override
197             public int getCurrentPosition() {
198                 int speed;
199                 if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
200                     speed = 0;
201                 } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
202                     speed = 1;
203                 } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
204                     int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
205                     speed = getFastForwardSpeeds()[index];
206                 } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
207                     int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
208                     speed = -getRewindSpeeds()[index];
209                 } else {
210                     return -1;
211                 }
212                 long position = mStartPosition +
213                         (System.currentTimeMillis() - mStartTime) * speed;
214                 if (position > getMediaDuration()) {
215                     position = getMediaDuration();
216                     onPlaybackComplete(true);
217                 } else if (position < 0) {
218                     position = 0;
219                     onPlaybackComplete(false);
220                 }
221                 return (int) position;
222             }
223 
224             void onPlaybackComplete(final boolean ended) {
225                 mHandler.post(new Runnable() {
226                     @Override
227                     public void run() {
228                         if (mRepeatAction.getIndex() == RepeatAction.NONE) {
229                             pausePlayback();
230                         } else {
231                             startPlayback(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
232                         }
233                         mStartPosition = 0;
234                         onStateChanged();
235                     }
236                 });
237             }
238 
239             @Override
240             protected void startPlayback(int speed) {
241                 if (speed == mSpeed) {
242                     return;
243                 }
244                 mStartPosition = getCurrentPosition();
245                 mSpeed = speed;
246                 mIsPlaying = true;
247                 mStartTime = System.currentTimeMillis();
248             }
249 
250             @Override
251             protected void pausePlayback() {
252                 if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
253                     return;
254                 }
255                 mStartPosition = getCurrentPosition();
256                 mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
257                 mIsPlaying = false;
258             }
259 
260             @Override
261             protected void skipToNext() {
262                 // Not supported
263             }
264 
265             @Override
266             protected void skipToPrevious() {
267                 // Not supported
268             }
269 
270             @Override
271             protected void onRowChanged(PlaybackControlsRow row) {
272                 PlaybackOverlayFragment.this.onRowChanged(row);
273             }
274 
275             @Override
276             public void enableProgressUpdating(boolean enable) {
277                 PlaybackOverlayFragment.this.enableProgressUpdating(enable);
278             }
279 
280             @Override
281             public int getUpdatePeriod() {
282                 return PlaybackOverlayFragment.this.getUpdatePeriod();
283             }
284         };
285 
286         mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener);
287 
288         mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter();
289         mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN);
290         mListRowPresenter = new ListRowPresenter();
291 
292         setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
293             @Override
294             public Presenter getPresenter(Object object) {
295                 if (object instanceof PlaybackControlsRow) {
296                     return mPlaybackControlsRowPresenter;
297                 } else if (object instanceof ListRow) {
298                     return mListRowPresenter;
299                 }
300                 throw new IllegalArgumentException("Unhandled object: " + object);
301             }
302         }));
303 
304         // Set secondary control actions
305         PlaybackControlsRow controlsRow = mGlue.getControlsRow();
306         ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
307         controlsRow.setSecondaryActionsAdapter(adapter);
308         if (!THUMBS_PRIMARY) {
309             adapter.add(mThumbsDownAction);
310         }
311         adapter.add(mRepeatAction);
312         if (!THUMBS_PRIMARY) {
313             adapter.add(mThumbsUpAction);
314         }
315 
316         // Add the controls row
317         getAdapter().set(ROW_CONTROLS, controlsRow);
318 
319         // Add related content rows
320         for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
321             ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
322             listRowAdapter.add("Some related content");
323             listRowAdapter.add("Other related content");
324             HeaderItem header = new HeaderItem(i, "Row " + i);
325             getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
326         }
327     }
328 
createPrimaryActionsAdapter( PresenterSelector presenterSelector)329     private SparseArrayObjectAdapter createPrimaryActionsAdapter(
330             PresenterSelector presenterSelector) {
331         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
332         if (THUMBS_PRIMARY) {
333             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
334             adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
335         }
336         return adapter;
337     }
338 
onRowChanged(PlaybackControlsRow row)339     private void onRowChanged(PlaybackControlsRow row) {
340         if (getAdapter() == null) {
341             return;
342         }
343         int index = getAdapter().indexOf(row);
344         if (index >= 0) {
345             getAdapter().notifyArrayItemRangeChanged(index, 1);
346         }
347     }
348 
enableProgressUpdating(boolean enable)349     private void enableProgressUpdating(boolean enable) {
350         Log.v(TAG, "enableProgressUpdating " + enable + " this " + this);
351         mHandler.removeCallbacks(mUpdateProgressRunnable);
352         if (enable) {
353             mUpdateProgressRunnable.run();
354         }
355     }
356 
getUpdatePeriod()357     private int getUpdatePeriod() {
358         int totalTime = mGlue.getControlsRow().getTotalTime();
359         if (getView() == null || totalTime <= 0) {
360             return 1000;
361         }
362         return Math.max(16, totalTime / getView().getWidth());
363     }
364 
onActionClicked(Action action)365     private void onActionClicked(Action action) {
366         Log.v(TAG, "onActionClicked " + action);
367         Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
368         if (action instanceof PlaybackControlsRow.MultiAction) {
369             PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
370             multiAction.nextIndex();
371             notifyActionChanged(multiAction);
372         }
373     }
374 
getPrimaryActionsAdapter()375     private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
376         return (SparseArrayObjectAdapter) mGlue.getControlsRow().getPrimaryActionsAdapter();
377     }
378 
getSecondaryActionsAdapter()379     private ArrayObjectAdapter getSecondaryActionsAdapter() {
380         return (ArrayObjectAdapter) mGlue.getControlsRow().getSecondaryActionsAdapter();
381     }
382 
notifyActionChanged(PlaybackControlsRow.MultiAction action)383     private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
384         int index;
385         index = getPrimaryActionsAdapter().indexOf(action);
386         if (index >= 0) {
387             getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
388         } else {
389             index = getSecondaryActionsAdapter().indexOf(action);
390             if (index >= 0) {
391                 getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
392             }
393         }
394     }
395 
396     @Override
onStart()397     public void onStart() {
398         super.onStart();
399         mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
400     }
401 
402     @Override
onStop()403     public void onStop() {
404         mGlue.enableProgressUpdating(false);
405         super.onStop();
406     }
407 }
408