• 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.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
onCreate(Bundle savedInstanceState)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
onStart()151     public void onStart() {
152         super.onStart();
153         mStarted = true;
154         updateVerticalPosition();
155     }
156 
157     @Override
onActivityCreated(Bundle savedInstanceState)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
onPause()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
onDestroy()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. */
onNewIntent(Intent intent)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      */
onWindowSizeChanged(final int windowWidth, final int windowHeight)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. */
getNextEpisode(RecordedProgram 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      */
getTracks(int trackType)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. */
getSelectedTrackId(int trackType)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      */
getTrackSetting(int trackType)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      */
selectTrack(int trackType, TvTrackInfo selectedTrack)352     void selectTrack(int trackType, TvTrackInfo selectedTrack) {
353         if (mDvrPlayer.isPlaybackPrepared()) {
354             mDvrPlayer.selectTrack(trackType, selectedTrack);
355         }
356     }
357 
handleIntent(Intent intent, boolean finishActivity)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 
selectBestMatchedTrack(int trackType)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 
updateAspectRatio(float videoAspectRatio)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 
preparePlayback(Intent intent)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 
updateRelatedRecordingsRow()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 
setUpRows()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 
getRelatedRecordingsRow()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 
getProgramFromIntent(Intent intent)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 
getSeekTimeFromIntent(Intent intent)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 
updateVerticalPosition()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> {
RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter)507         RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
508             super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
509         }
510 
511         @Override
getId(BaseProgram item)512         public long getId(BaseProgram item) {
513             return item.getId();
514         }
515     }
516 }
517