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