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;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.graphics.Point;
22 import android.hardware.display.DisplayManager;
23 import android.media.tv.TvContentRating;
24 import android.os.Bundle;
25 import android.media.session.PlaybackState;
26 import android.media.tv.TvInputManager;
27 import android.media.tv.TvView;
28 import android.support.v17.leanback.app.PlaybackOverlayFragment;
29 import android.support.v17.leanback.widget.ArrayObjectAdapter;
30 import android.support.v17.leanback.widget.ClassPresenterSelector;
31 import android.support.v17.leanback.widget.HeaderItem;
32 import android.support.v17.leanback.widget.ListRow;
33 import android.support.v17.leanback.widget.ListRowPresenter;
34 import android.support.v17.leanback.widget.PlaybackControlsRow;
35 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
36 import android.support.v17.leanback.widget.SinglePresenterSelector;
37 import android.view.Display;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.widget.Toast;
41 import android.text.TextUtils;
42 import android.util.Log;
43 
44 import com.android.tv.R;
45 import com.android.tv.TvApplication;
46 import com.android.tv.data.BaseProgram;
47 import com.android.tv.dvr.RecordedProgram;
48 import com.android.tv.dialog.PinDialogFragment;
49 import com.android.tv.dvr.DvrDataManager;
50 import com.android.tv.dvr.DvrPlayer;
51 import com.android.tv.dvr.DvrPlaybackMediaSessionHelper;
52 import com.android.tv.parental.ContentRatingsManager;
53 import com.android.tv.util.Utils;
54 
55 public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
56     // TODO: Handles audio focus. Deals with block and ratings.
57     private static final String TAG = "DvrPlaybackOverlayFragment";
58     private static final boolean DEBUG = false;
59 
60     private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession";
61     private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
62 
63     // mProgram is only used to store program from intent. Don't use it elsewhere.
64     private RecordedProgram mProgram;
65     private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
66     private DvrPlaybackControlHelper mPlaybackControlHelper;
67     private ArrayObjectAdapter mRowsAdapter;
68     private SortedArrayAdapter<BaseProgram> mRelatedRecordingsRowAdapter;
69     private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter;
70     private DvrDataManager mDvrDataManager;
71     private ContentRatingsManager mContentRatingsManager;
72     private TvView mTvView;
73     private View mBlockScreenView;
74     private ListRow mRelatedRecordingsRow;
75     private int mExtraPaddingNoRelatedRow;
76     private int mWindowWidth;
77     private int mWindowHeight;
78     private float mAppliedAspectRatio;
79     private float mWindowAspectRatio;
80     private boolean mPinChecked;
81 
82     @Override
onCreate(Bundle savedInstanceState)83     public void onCreate(Bundle savedInstanceState) {
84         if (DEBUG) Log.d(TAG, "onCreate");
85         super.onCreate(savedInstanceState);
86         mExtraPaddingNoRelatedRow = getActivity().getResources()
87                 .getDimensionPixelOffset(R.dimen.dvr_playback_fragment_extra_padding_top);
88         mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
89         mContentRatingsManager = TvApplication.getSingletons(getContext())
90                 .getTvInputManagerHelper().getContentRatingsManager();
91         mProgram = getProgramFromIntent(getActivity().getIntent());
92         if (mProgram == null) {
93             Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
94                     Toast.LENGTH_SHORT).show();
95             getActivity().finish();
96             return;
97         }
98         Point size = new Point();
99         ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
100                 .getDisplay(Display.DEFAULT_DISPLAY).getSize(size);
101         mWindowWidth = size.x;
102         mWindowHeight = size.y;
103         mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight;
104         setBackgroundType(PlaybackOverlayFragment.BG_LIGHT);
105         setFadingEnabled(true);
106     }
107 
108     @Override
onActivityCreated(Bundle savedInstanceState)109     public void onActivityCreated(Bundle savedInstanceState) {
110         super.onActivityCreated(savedInstanceState);
111         mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
112         mBlockScreenView = getActivity().findViewById(R.id.block_screen);
113         mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
114                 getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this);
115         mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
116         setUpRows();
117         preparePlayback(getActivity().getIntent());
118         DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer();
119         dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() {
120             @Override
121             public void onAspectRatioChanged(float videoAspectRatio) {
122                 updateAspectRatio(videoAspectRatio);
123             }
124         });
125         mPinChecked = getActivity().getIntent()
126                 .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
127         dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() {
128             @Override
129             public void onContentBlocked(TvContentRating rating) {
130                 if (mPinChecked) {
131                     mTvView.unblockContent(rating);
132                     return;
133                 }
134                 mBlockScreenView.setVisibility(View.VISIBLE);
135                 getActivity().getMediaController().getTransportControls().pause();
136                 new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR,
137                         new PinDialogFragment.ResultListener() {
138                             @Override
139                             public void done(boolean success) {
140                                 if (success) {
141                                     mPinChecked = true;
142                                     mTvView.unblockContent(rating);
143                                     mBlockScreenView.setVisibility(View.GONE);
144                                     getActivity().getMediaController()
145                                             .getTransportControls().play();
146                                 }
147                             }
148                         }, mContentRatingsManager.getDisplayNameForRating(rating))
149                         .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
150                 }
151             });
152     }
153 
154     @Override
onPause()155     public void onPause() {
156         if (DEBUG) Log.d(TAG, "onPause");
157         super.onPause();
158         if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING
159                 || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) {
160             getActivity().getMediaController().getTransportControls().pause();
161         }
162         if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) {
163             getActivity().requestVisibleBehind(false);
164         } else {
165             getActivity().requestVisibleBehind(true);
166         }
167     }
168 
169     @Override
onDestroy()170     public void onDestroy() {
171         if (DEBUG) Log.d(TAG, "onDestroy");
172         mPlaybackControlHelper.unregisterCallback();
173         mMediaSessionHelper.release();
174         mRelatedRecordingCardPresenter.unbindAllViewHolders();
175         super.onDestroy();
176     }
177 
178     /**
179      * Passes the intent to the fragment.
180      */
onNewIntent(Intent intent)181     public void onNewIntent(Intent intent) {
182         mProgram = getProgramFromIntent(intent);
183         if (mProgram == null) {
184             Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
185                     Toast.LENGTH_SHORT).show();
186             // Continue playing the original program
187             return;
188         }
189         preparePlayback(intent);
190     }
191 
192     /**
193      * Should be called when windows' size is changed in order to notify DVR player
194      * to update it's view width/height and position.
195      */
onWindowSizeChanged(final int windowWidth, final int windowHeight)196     public void onWindowSizeChanged(final int windowWidth, final int windowHeight) {
197         mWindowWidth = windowWidth;
198         mWindowHeight = windowHeight;
199         mWindowAspectRatio = (float) mWindowWidth / mWindowHeight;
200         updateAspectRatio(mAppliedAspectRatio);
201     }
202 
getNextEpisode(RecordedProgram program)203     public RecordedProgram getNextEpisode(RecordedProgram program) {
204         int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
205         if (position == mRelatedRecordingsRowAdapter.size()) {
206             return null;
207         } else {
208             return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position);
209         }
210     }
211 
onMediaControllerUpdated()212     void onMediaControllerUpdated() {
213         mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
214     }
215 
updateAspectRatio(float videoAspectRatio)216     private void updateAspectRatio(float videoAspectRatio) {
217         if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
218             // No need to change
219             return;
220         }
221         if (videoAspectRatio < mWindowAspectRatio) {
222             int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
223             ((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
224         } else {
225             int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2;
226             ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding);
227         }
228         mAppliedAspectRatio = videoAspectRatio;
229     }
230 
preparePlayback(Intent intent)231     private void preparePlayback(Intent intent) {
232         mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
233         getActivity().getMediaController().getTransportControls().prepare();
234         updateRelatedRecordingsRow();
235     }
236 
updateRelatedRecordingsRow()237     private void updateRelatedRecordingsRow() {
238         boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0);
239         mRelatedRecordingsRowAdapter.clear();
240         long programId = mProgram.getId();
241         String seriesId = mProgram.getSeriesId();
242         if (!TextUtils.isEmpty(seriesId)) {
243             if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
244             for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) {
245                 if (seriesId.equals(program.getSeriesId()) && programId != program.getId()) {
246                     mRelatedRecordingsRowAdapter.add(program);
247                 }
248             }
249         }
250         View view = getView();
251         if (mRelatedRecordingsRowAdapter.size() == 0) {
252             mRowsAdapter.remove(mRelatedRecordingsRow);
253             view.setPadding(view.getPaddingLeft(), mExtraPaddingNoRelatedRow,
254                     view.getPaddingRight(), view.getPaddingBottom());
255         } else if (wasEmpty){
256             mRowsAdapter.add(mRelatedRecordingsRow);
257             view.setPadding(view.getPaddingLeft(), 0,
258                     view.getPaddingRight(), view.getPaddingBottom());
259         }
260     }
261 
setUpRows()262     private void setUpRows() {
263         PlaybackControlsRowPresenter controlsRowPresenter =
264                 mPlaybackControlHelper.createControlsRowAndPresenter();
265 
266         ClassPresenterSelector selector = new ClassPresenterSelector();
267         selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
268         selector.addClassPresenter(ListRow.class, new ListRowPresenter());
269 
270         mRowsAdapter = new ArrayObjectAdapter(selector);
271         mRowsAdapter.add(mPlaybackControlHelper.getControlsRow());
272         mRelatedRecordingsRow = getRelatedRecordingsRow();
273         setAdapter(mRowsAdapter);
274     }
275 
getRelatedRecordingsRow()276     private ListRow getRelatedRecordingsRow() {
277         mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
278         mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
279         HeaderItem header = new HeaderItem(0,
280                 getActivity().getString(R.string.dvr_playback_related_recordings));
281         return new ListRow(header, mRelatedRecordingsRowAdapter);
282     }
283 
getProgramFromIntent(Intent intent)284     private RecordedProgram getProgramFromIntent(Intent intent) {
285         long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1);
286         return mDvrDataManager.getRecordedProgram(programId);
287     }
288 
getSeekTimeFromIntent(Intent intent)289     private long getSeekTimeFromIntent(Intent intent) {
290         return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME,
291                 TvInputManager.TIME_SHIFT_INVALID_TIME);
292     }
293 
294     private class RelatedRecordingsAdapter extends SortedArrayAdapter<BaseProgram> {
RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter)295         RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
296             super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
297         }
298 
299         @Override
getId(BaseProgram item)300         long getId(BaseProgram item) {
301             return item.getId();
302         }
303     }
304 }