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 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 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 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 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 */ 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 */ 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 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 212 void onMediaControllerUpdated() { 213 mRowsAdapter.notifyArrayItemRangeChanged(0, 1); 214 } 215 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 231 private void preparePlayback(Intent intent) { 232 mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent)); 233 getActivity().getMediaController().getTransportControls().prepare(); 234 updateRelatedRecordingsRow(); 235 } 236 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 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 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 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 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> { 295 RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) { 296 super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR); 297 } 298 299 @Override 300 long getId(BaseProgram item) { 301 return item.getId(); 302 } 303 } 304 }