• 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.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
onCreate(Bundle savedInstanceState)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
onStart()143     public void onStart() {
144         super.onStart();
145         mStarted = true;
146         updateVerticalPosition();
147     }
148 
149     @Override
onActivityCreated(Bundle savedInstanceState)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
onPause()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
onDestroy()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      */
onNewIntent(Intent intent)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      */
onWindowSizeChanged(final int windowWidth, final int windowHeight)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      */
getNextEpisode(RecordedProgram program)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      */
getTracks(int trackType)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      */
getSelectedTrackId(int trackType)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      */
getTrackSetting(int trackType)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      */
selectTrack(int trackType, TvTrackInfo selectedTrack)340     void selectTrack(int trackType, TvTrackInfo selectedTrack) {
341         if (mDvrPlayer.isPlaybackPrepared()) {
342             mDvrPlayer.selectTrack(trackType, selectedTrack);
343         }
344     }
345 
handleIntent(Intent intent, boolean finishActivity)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 
selectBestMatchedTrack(int trackType)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 
updateAspectRatio(float videoAspectRatio)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 
preparePlayback(Intent intent)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 
updateRelatedRecordingsRow()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 
setUpRows()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 
getRelatedRecordingsRow()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 
getProgramFromIntent(Intent intent)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 
getSeekTimeFromIntent(Intent intent)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 
updateVerticalPosition()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> {
RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter)485         RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
486             super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
487         }
488 
489         @Override
getId(BaseProgram item)490         public long getId(BaseProgram item) {
491             return item.getId();
492         }
493     }
494 }