Home | History | Annotate | Download | only in playback
      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  */
     16 
     17 package com.android.tv.dvr.ui.playback;
     18 
     19 import android.app.Fragment;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.graphics.Point;
     23 import android.hardware.display.DisplayManager;
     24 import android.media.tv.TvContentRating;
     25 import android.media.tv.TvTrackInfo;
     26 import android.os.Bundle;
     27 import android.media.session.PlaybackState;
     28 import android.media.tv.TvInputManager;
     29 import android.media.tv.TvView;
     30 import android.support.v17.leanback.app.PlaybackFragment;
     31 import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
     32 import android.support.v17.leanback.widget.ArrayObjectAdapter;
     33 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
     34 import android.support.v17.leanback.widget.ClassPresenterSelector;
     35 import android.support.v17.leanback.widget.HeaderItem;
     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.SinglePresenterSelector;
     40 import android.view.Display;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.widget.Toast;
     44 import android.util.Log;
     45 
     46 import com.android.tv.R;
     47 import com.android.tv.TvApplication;
     48 import com.android.tv.data.BaseProgram;
     49 import com.android.tv.dialog.PinDialogFragment;
     50 import com.android.tv.dvr.DvrDataManager;
     51 import com.android.tv.dvr.data.RecordedProgram;
     52 import com.android.tv.dvr.data.SeriesRecording;
     53 import com.android.tv.dvr.ui.SortedArrayAdapter;
     54 import com.android.tv.dvr.ui.browse.DvrListRowPresenter;
     55 import com.android.tv.dvr.ui.browse.RecordingCardView;
     56 import com.android.tv.parental.ContentRatingsManager;
     57 import com.android.tv.util.TvSettings;
     58 import com.android.tv.util.TvTrackInfoUtils;
     59 import com.android.tv.util.Utils;
     60 
     61 import java.util.List;
     62 import java.util.ArrayList;
     63 
     64 public class DvrPlaybackOverlayFragment extends PlaybackFragment {
     65     // TODO: Handles audio focus. Deals with block and ratings.
     66     private static final String TAG = "DvrPlaybackOverlayFrag";
     67     private static final boolean DEBUG = false;
     68 
     69     private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession";
     70     private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
     71 
     72     // mProgram is only used to store program from intent. Don't use it elsewhere.
     73     private RecordedProgram mProgram;
     74     private DvrPlayer mDvrPlayer;
     75     private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
     76     private DvrPlaybackControlHelper mPlaybackControlHelper;
     77     private ArrayObjectAdapter mRowsAdapter;
     78     private SortedArrayAdapter<BaseProgram> mRelatedRecordingsRowAdapter;
     79     private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter;
     80     private DvrDataManager mDvrDataManager;
     81     private ContentRatingsManager mContentRatingsManager;
     82     private TvView mTvView;
     83     private View mBlockScreenView;
     84     private ListRow mRelatedRecordingsRow;
     85     private int mVerticalPaddingBase;
     86     private int mPaddingWithoutRelatedRow;
     87     private int mPaddingWithoutSecondaryRow;
     88     private int mWindowWidth;
     89     private int mWindowHeight;
     90     private float mAppliedAspectRatio;
     91     private float mWindowAspectRatio;
     92     private boolean mPinChecked;
     93     private boolean mStarted;
     94     private DvrPlayer.OnTrackSelectedListener mOnSubtitleTrackSelectedListener =
     95             new DvrPlayer.OnTrackSelectedListener() {
     96                 @Override
     97                 public void onTrackSelected(String selectedTrackId) {
     98                     mPlaybackControlHelper.onSubtitleTrackStateChanged(selectedTrackId != null);
     99                     mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
    100                 }
    101             };
    102 
    103     @Override
    104     public void onCreate(Bundle savedInstanceState) {
    105         if (DEBUG) Log.d(TAG, "onCreate");
    106         super.onCreate(savedInstanceState);
    107         mVerticalPaddingBase = getActivity().getResources()
    108                 .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base);
    109         mPaddingWithoutRelatedRow = getActivity().getResources()
    110                 .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row);
    111         mPaddingWithoutSecondaryRow = getActivity().getResources()
    112                 .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
    113         mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
    114         mContentRatingsManager = TvApplication.getSingletons(getContext())
    115                 .getTvInputManagerHelper().getContentRatingsManager();
    116         if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
    117             mDvrDataManager.addRecordedProgramLoadFinishedListener(
    118                     new DvrDataManager.OnRecordedProgramLoadFinishedListener() {
    119                         @Override
    120                         public void onRecordedProgramLoadFinished() {
    121                             mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
    122                             if (handleIntent(getActivity().getIntent(), true)) {
    123                                 setUpRows();
    124                                 preparePlayback(getActivity().getIntent());
    125                             }
    126                         }
    127                     }
    128             );
    129         } else if (!handleIntent(getActivity().getIntent(), true)) {
    130             return;
    131         }
    132         Point size = new Point();
    133         ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
    134                 .getDisplay(Display.DEFAULT_DISPLAY).getSize(size);
    135         mWindowWidth = size.x;
    136         mWindowHeight = size.y;
    137         mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight;
    138         setBackgroundType(PlaybackFragment.BG_LIGHT);
    139         setFadingEnabled(true);
    140     }
    141 
    142     @Override
    143     public void onStart() {
    144         super.onStart();
    145         mStarted = true;
    146         updateVerticalPosition();
    147     }
    148 
    149     @Override
    150     public void onActivityCreated(Bundle savedInstanceState) {
    151         super.onActivityCreated(savedInstanceState);
    152         mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
    153         mBlockScreenView = getActivity().findViewById(R.id.block_screen);
    154         mDvrPlayer = new DvrPlayer(mTvView);
    155         mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
    156                 getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this);
    157         mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
    158         mRelatedRecordingsRow = getRelatedRecordingsRow();
    159         mDvrPlayer.setOnTracksAvailabilityChangedListener(
    160                 new DvrPlayer.OnTracksAvailabilityChangedListener() {
    161                     @Override
    162                     public void onTracksAvailabilityChanged(boolean hasClosedCaption,
    163                             boolean hasMultiAudio) {
    164                         mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio);
    165                         if (hasClosedCaption) {
    166                             mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE,
    167                                     mOnSubtitleTrackSelectedListener);
    168                             selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE);
    169                         } else {
    170                             mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null);
    171                         }
    172                         if (hasMultiAudio) {
    173                             selectBestMatchedTrack(TvTrackInfo.TYPE_AUDIO);
    174                         }
    175                         updateVerticalPosition();
    176                         mPlaybackControlHelper.getHost().notifyPlaybackRowChanged();
    177                     }
    178         });
    179         mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
    180             @Override
    181             public void onAspectRatioChanged(float videoAspectRatio) {
    182                 updateAspectRatio(videoAspectRatio);
    183             }
    184         });
    185         mPinChecked = getActivity().getIntent()
    186                 .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
    187         mDvrPlayer.setOnContentBlockedListener(
    188                 new DvrPlayer.OnContentBlockedListener() {
    189                     @Override
    190                     public void onContentBlocked(TvContentRating contentRating) {
    191                         if (mPinChecked) {
    192                             mTvView.unblockContent(contentRating);
    193                             return;
    194                         }
    195                         mBlockScreenView.setVisibility(View.VISIBLE);
    196                         getActivity().getMediaController().getTransportControls().pause();
    197                         ((DvrPlaybackActivity) getActivity())
    198                                 .setOnPinCheckListener(
    199                                         new PinDialogFragment.OnPinCheckedListener() {
    200                                             @Override
    201                                             public void onPinChecked(
    202                                                     boolean checked, int type, String rating) {
    203                                                 ((DvrPlaybackActivity) getActivity())
    204                                                         .setOnPinCheckListener(null);
    205                                                 if (checked) {
    206                                                     mPinChecked = true;
    207                                                     mTvView.unblockContent(contentRating);
    208                                                     mBlockScreenView.setVisibility(View.GONE);
    209                                                     getActivity()
    210                                                             .getMediaController()
    211                                                             .getTransportControls()
    212                                                             .play();
    213                                                 }
    214                                             }
    215                                         });
    216                         PinDialogFragment.create(
    217                                         PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR,
    218                                         contentRating.flattenToString())
    219                                 .show(
    220                                         getActivity().getFragmentManager(),
    221                                         PinDialogFragment.DIALOG_TAG);
    222                     }
    223                 });
    224         setOnItemViewClickedListener(new BaseOnItemViewClickedListener() {
    225             @Override
    226             public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
    227                     RowPresenter.ViewHolder rowViewHolder, Object row) {
    228                 if (itemViewHolder.view instanceof RecordingCardView) {
    229                     setFadingEnabled(false);
    230                     long programId = ((RecordedProgram) itemViewHolder.view.getTag()).getId();
    231                     if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
    232                     Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
    233                     intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
    234                     getContext().startActivity(intent);
    235                 }
    236             }
    237         });
    238         if (mProgram != null) {
    239             setUpRows();
    240             preparePlayback(getActivity().getIntent());
    241         }
    242     }
    243 
    244     @Override
    245     public void onPause() {
    246         if (DEBUG) Log.d(TAG, "onPause");
    247         super.onPause();
    248         if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING
    249                 || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) {
    250             getActivity().getMediaController().getTransportControls().pause();
    251         }
    252         if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) {
    253             getActivity().requestVisibleBehind(false);
    254         } else {
    255             getActivity().requestVisibleBehind(true);
    256         }
    257     }
    258 
    259     @Override
    260     public void onDestroy() {
    261         if (DEBUG) Log.d(TAG, "onDestroy");
    262         mPlaybackControlHelper.unregisterCallback();
    263         mMediaSessionHelper.release();
    264         mRelatedRecordingCardPresenter.unbindAllViewHolders();
    265         super.onDestroy();
    266     }
    267 
    268     /**
    269      * Passes the intent to the fragment.
    270      */
    271     public void onNewIntent(Intent intent) {
    272         if (mDvrDataManager.isRecordedProgramLoadFinished() && handleIntent(intent, false)) {
    273             preparePlayback(intent);
    274         }
    275     }
    276 
    277     /**
    278      * Should be called when windows' size is changed in order to notify DVR player
    279      * to update it's view width/height and position.
    280      */
    281     public void onWindowSizeChanged(final int windowWidth, final int windowHeight) {
    282         mWindowWidth = windowWidth;
    283         mWindowHeight = windowHeight;
    284         mWindowAspectRatio = (float) mWindowWidth / mWindowHeight;
    285         updateAspectRatio(mAppliedAspectRatio);
    286     }
    287 
    288     /**
    289      * Returns next recorded episode in the same series as now playing program.
    290      */
    291     public RecordedProgram getNextEpisode(RecordedProgram program) {
    292         int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
    293         if (position == mRelatedRecordingsRowAdapter.size()) {
    294             return null;
    295         } else {
    296             return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position);
    297         }
    298     }
    299 
    300     /**
    301      * Returns the tracks of the give type of the current playback.
    302 
    303      * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
    304      *                  or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}.
    305      */
    306     public ArrayList<TvTrackInfo> getTracks(int trackType) {
    307         if (trackType == TvTrackInfo.TYPE_AUDIO) {
    308             return mDvrPlayer.getAudioTracks();
    309         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    310             return mDvrPlayer.getSubtitleTracks();
    311         }
    312         return null;
    313     }
    314 
    315     /**
    316      * Returns the ID of the selected track of the given type.
    317      */
    318     public String getSelectedTrackId(int trackType) {
    319         return mDvrPlayer.getSelectedTrackId(trackType);
    320     }
    321 
    322     /**
    323      * Returns the language setting of the given track type.
    324 
    325      * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
    326      *                  or {@link TvTrackInfo#TYPE_AUDIO}.
    327      * @return {@code null} if no language has been set for the given track type.
    328      */
    329     TvTrackInfo getTrackSetting(int trackType) {
    330         return TvSettings.getDvrPlaybackTrackSettings(getContext(), trackType);
    331     }
    332 
    333     /**
    334      * Selects the given audio or subtitle track for DVR playback.
    335      * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
    336      *                  or {@link TvTrackInfo#TYPE_AUDIO}.
    337      * @param selectedTrack {@code null} to disable the audio or subtitle track according to
    338      *                      trackType.
    339      */
    340     void selectTrack(int trackType, TvTrackInfo selectedTrack) {
    341         if (mDvrPlayer.isPlaybackPrepared()) {
    342             mDvrPlayer.selectTrack(trackType, selectedTrack);
    343         }
    344     }
    345 
    346     private boolean handleIntent(Intent intent, boolean finishActivity) {
    347         mProgram = getProgramFromIntent(intent);
    348         if (mProgram == null) {
    349             Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
    350                     Toast.LENGTH_SHORT).show();
    351             if (finishActivity) {
    352                 getActivity().finish();
    353             }
    354             return false;
    355         }
    356         return true;
    357     }
    358 
    359     private void selectBestMatchedTrack(int trackType) {
    360         TvTrackInfo selectedTrack = getTrackSetting(trackType);
    361         if (selectedTrack != null) {
    362             TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType),
    363                     selectedTrack.getId(), selectedTrack.getLanguage(),
    364                     trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0);
    365             if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils
    366                     .isEqualLanguage(bestMatchedTrack.getLanguage(),
    367                             selectedTrack.getLanguage()))) {
    368                 selectTrack(trackType, bestMatchedTrack);
    369                 return;
    370             }
    371         }
    372         if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    373             // Disables closed captioning if there's no matched language.
    374             selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
    375         }
    376     }
    377 
    378     private void updateAspectRatio(float videoAspectRatio) {
    379         if (videoAspectRatio <= 0) {
    380             // We don't have video's width or height information, use window's aspect ratio.
    381             videoAspectRatio = mWindowAspectRatio;
    382         }
    383         if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
    384             // No need to change
    385             return;
    386         }
    387         if (Math.abs(mWindowAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
    388             ((ViewGroup) mTvView.getParent()).setPadding(0, 0, 0, 0);
    389         } else if (videoAspectRatio < mWindowAspectRatio) {
    390             int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
    391             ((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
    392         } else {
    393             int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2;
    394             ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding);
    395         }
    396         mAppliedAspectRatio = videoAspectRatio;
    397     }
    398 
    399     private void preparePlayback(Intent intent) {
    400         mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
    401         mPlaybackControlHelper.updateSecondaryRow(false, false);
    402         getActivity().getMediaController().getTransportControls().prepare();
    403         updateRelatedRecordingsRow();
    404     }
    405 
    406     private void updateRelatedRecordingsRow() {
    407         boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0);
    408         mRelatedRecordingsRowAdapter.clear();
    409         long programId = mProgram.getId();
    410         String seriesId = mProgram.getSeriesId();
    411         SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
    412         if (seriesRecording != null) {
    413             if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
    414             List<RecordedProgram> relatedPrograms =
    415                     mDvrDataManager.getRecordedPrograms(seriesRecording.getId());
    416             for (RecordedProgram program : relatedPrograms) {
    417                 if (programId != program.getId()) {
    418                     mRelatedRecordingsRowAdapter.add(program);
    419                 }
    420             }
    421         }
    422         if (mRelatedRecordingsRowAdapter.size() == 0) {
    423             mRowsAdapter.remove(mRelatedRecordingsRow);
    424         } else if (wasEmpty){
    425             mRowsAdapter.add(mRelatedRecordingsRow);
    426         }
    427         updateVerticalPosition();
    428         mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
    429     }
    430 
    431     private void setUpRows() {
    432         mPlaybackControlHelper.createControlsRow();
    433         mPlaybackControlHelper.setHost(new PlaybackFragmentGlueHost(this));
    434         mRowsAdapter = (ArrayObjectAdapter) getAdapter();
    435         ClassPresenterSelector selector =
    436                 (ClassPresenterSelector) mRowsAdapter.getPresenterSelector();
    437         selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
    438         mRowsAdapter.setPresenterSelector(selector);
    439         if (mStarted) {
    440             // If it's started before setting up rows, vertical position has not been updated and
    441             // should be updated here.
    442             updateVerticalPosition();
    443         }
    444     }
    445 
    446     private ListRow getRelatedRecordingsRow() {
    447         mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
    448         mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
    449         HeaderItem header = new HeaderItem(0,
    450                 getActivity().getString(R.string.dvr_playback_related_recordings));
    451         return new ListRow(header, mRelatedRecordingsRowAdapter);
    452     }
    453 
    454     private RecordedProgram getProgramFromIntent(Intent intent) {
    455         long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1);
    456         return mDvrDataManager.getRecordedProgram(programId);
    457     }
    458 
    459     private long getSeekTimeFromIntent(Intent intent) {
    460         return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME,
    461                 TvInputManager.TIME_SHIFT_INVALID_TIME);
    462     }
    463 
    464     private void updateVerticalPosition() {
    465         Boolean hasSecondaryRow = mPlaybackControlHelper.hasSecondaryRow();
    466         if (hasSecondaryRow == null) {
    467             return;
    468         }
    469 
    470         int verticalPadding = mVerticalPaddingBase;
    471         if (mRelatedRecordingsRowAdapter.size() == 0) {
    472             verticalPadding += mPaddingWithoutRelatedRow;
    473         }
    474         if (!hasSecondaryRow) {
    475             verticalPadding += mPaddingWithoutSecondaryRow;
    476         }
    477         Fragment fragment = getChildFragmentManager().findFragmentById(R.id.playback_controls_dock);
    478         View view = fragment == null ? null : fragment.getView();
    479         if (view != null) {
    480             view.setTranslationY(verticalPadding);
    481         }
    482     }
    483 
    484     private class RelatedRecordingsAdapter extends SortedArrayAdapter<BaseProgram> {
    485         RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
    486             super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
    487         }
    488 
    489         @Override
    490         public long getId(BaseProgram item) {
    491             return item.getId();
    492         }
    493     }
    494 }