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