Home | History | Annotate | Download | only in leanback
      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 
    118     public SparseArrayObjectAdapter getAdapter() {
    119         return (SparseArrayObjectAdapter) super.getAdapter();
    120     }
    121 
    122     @Override
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    375     private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
    376         return (SparseArrayObjectAdapter) mGlue.getControlsRow().getPrimaryActionsAdapter();
    377     }
    378 
    379     private ArrayObjectAdapter getSecondaryActionsAdapter() {
    380         return (ArrayObjectAdapter) mGlue.getControlsRow().getSecondaryActionsAdapter();
    381     }
    382 
    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
    397     public void onStart() {
    398         super.onStart();
    399         mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
    400     }
    401 
    402     @Override
    403     public void onStop() {
    404         mGlue.enableProgressUpdating(false);
    405         super.onStop();
    406     }
    407 }
    408