1 /* 2 * Copyright (C) 2015 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; 18 19 import android.annotation.TargetApi; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.media.tv.TvInputManager; 24 import android.os.Build; 25 import android.os.Bundle; 26 import android.support.annotation.MainThread; 27 import android.support.annotation.Nullable; 28 import android.support.v4.app.ActivityOptionsCompat; 29 import android.text.TextUtils; 30 import android.widget.ImageView; 31 import android.widget.Toast; 32 33 import com.android.tv.MainActivity; 34 import com.android.tv.R; 35 import com.android.tv.TvApplication; 36 import com.android.tv.common.SoftPreconditions; 37 import com.android.tv.data.Channel; 38 import com.android.tv.data.Program; 39 import com.android.tv.dvr.ui.DvrDetailsActivity; 40 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment; 41 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment; 42 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment; 43 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment; 44 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment; 45 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment; 46 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment; 47 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment; 48 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment; 49 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment; 50 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment; 51 import com.android.tv.dvr.ui.DvrSchedulesActivity; 52 import com.android.tv.dvr.ui.DvrSeriesDeletionActivity; 53 import com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity; 54 import com.android.tv.dvr.ui.DvrSeriesSettingsActivity; 55 import com.android.tv.dvr.ui.DvrStopRecordingFragment; 56 import com.android.tv.dvr.ui.DvrStopSeriesRecordingDialogFragment; 57 import com.android.tv.dvr.ui.DvrStopSeriesRecordingFragment; 58 import com.android.tv.dvr.ui.HalfSizedDialogFragment; 59 import com.android.tv.dvr.ui.list.DvrSchedulesFragment; 60 import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; 61 import com.android.tv.util.Utils; 62 63 import java.util.Collections; 64 import java.util.List; 65 66 /** 67 * A helper class for DVR UI. 68 */ 69 @MainThread 70 @TargetApi(Build.VERSION_CODES.N) 71 public class DvrUiHelper { 72 /** 73 * Handles the action to create the new schedule. It returns {@code true} if the schedule is 74 * added and there's no additional UI, otherwise {@code false}. 75 */ handleCreateSchedule(MainActivity activity, Program program)76 public static boolean handleCreateSchedule(MainActivity activity, Program program) { 77 if (program == null) { 78 return false; 79 } 80 DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager(); 81 if (!program.isEpisodic()) { 82 // One time recording. 83 dvrManager.addSchedule(program); 84 if (!dvrManager.getConflictingSchedules(program).isEmpty()) { 85 DvrUiHelper.showScheduleConflictDialog(activity, program); 86 return false; 87 } 88 } else { 89 SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program); 90 if (seriesRecording == null || seriesRecording.isStopped()) { 91 DvrUiHelper.showScheduleDialog(activity, program); 92 return false; 93 } else { 94 // Show recorded program rather than the schedule. 95 RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(), 96 program.getSeasonNumber(), program.getEpisodeNumber()); 97 if (recordedProgram != null) { 98 DvrUiHelper.showAlreadyRecordedDialog(activity, program); 99 return false; 100 } 101 ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(), 102 program.getSeasonNumber(), program.getEpisodeNumber()); 103 if (duplicate != null 104 && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED 105 || duplicate.getState() 106 == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { 107 DvrUiHelper.showAlreadyScheduleDialog(activity, program); 108 return false; 109 } 110 // Just add the schedule. 111 dvrManager.addSchedule(program); 112 } 113 } 114 return true; 115 116 } 117 118 /** 119 * Checks if the storage status is good for recording and shows error messages if needed. 120 * 121 * @return true if the storage status is fine to be recorded for {@code inputId}. 122 */ checkStorageStatusAndShowErrorMessage(Activity activity, String inputId)123 public static boolean checkStorageStatusAndShowErrorMessage(Activity activity, String inputId) { 124 if (!Utils.isBundledInput(inputId)) { 125 return true; 126 } 127 DvrStorageStatusManager dvrStorageStatusManager = 128 TvApplication.getSingletons(activity).getDvrStorageStatusManager(); 129 int status = dvrStorageStatusManager.getDvrStorageStatus(); 130 if (status == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { 131 showDvrSmallSizedStorageErrorDialog(activity); 132 return false; 133 } else if (status == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { 134 showDvrMissingStorageErrorDialog(activity, inputId); 135 return false; 136 } else if (status == DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT) { 137 // TODO: handle insufficient storage case. 138 return true; 139 } else { 140 return true; 141 } 142 } 143 144 /** 145 * Shows the schedule dialog. 146 */ showScheduleDialog(MainActivity activity, Program program)147 public static void showScheduleDialog(MainActivity activity, Program program) { 148 if (SoftPreconditions.checkNotNull(program) == null) { 149 return; 150 } 151 Bundle args = new Bundle(); 152 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 153 showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true); 154 } 155 156 /** 157 * Shows the recording duration options dialog. 158 */ showChannelRecordDurationOptions(MainActivity activity, Channel channel)159 public static void showChannelRecordDurationOptions(MainActivity activity, Channel channel) { 160 if (SoftPreconditions.checkNotNull(channel) == null) { 161 return; 162 } 163 Bundle args = new Bundle(); 164 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); 165 showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args); 166 } 167 168 /** 169 * Shows the dialog which says that the new schedule conflicts with others. 170 */ showScheduleConflictDialog(MainActivity activity, Program program)171 public static void showScheduleConflictDialog(MainActivity activity, Program program) { 172 if (program == null) { 173 return; 174 } 175 Bundle args = new Bundle(); 176 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 177 showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true); 178 } 179 180 /** 181 * Shows the conflict dialog for the channel watching. 182 */ showChannelWatchConflictDialog(MainActivity activity, Channel channel)183 public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) { 184 if (channel == null) { 185 return; 186 } 187 Bundle args = new Bundle(); 188 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); 189 showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args); 190 } 191 192 /** 193 * Shows DVR insufficient space error dialog. 194 */ showDvrInsufficientSpaceErrorDialog(MainActivity activity)195 public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity) { 196 showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), null); 197 Utils.clearRecordingFailedReason(activity, 198 TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 199 } 200 201 /** 202 * Shows DVR missing storage error dialog. 203 */ showDvrMissingStorageErrorDialog(Activity activity, String inputId)204 private static void showDvrMissingStorageErrorDialog(Activity activity, String inputId) { 205 SoftPreconditions.checkArgument(!TextUtils.isEmpty(inputId)); 206 Bundle args = new Bundle(); 207 args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, inputId); 208 showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), args); 209 } 210 211 /** 212 * Shows DVR small sized storage error dialog. 213 */ showDvrSmallSizedStorageErrorDialog(Activity activity)214 public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { 215 showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); 216 } 217 218 /** 219 * Shows stop recording dialog. 220 */ showStopRecordingDialog(Activity activity, long channelId, int reason, HalfSizedDialogFragment.OnActionClickListener listener)221 public static void showStopRecordingDialog(Activity activity, long channelId, int reason, 222 HalfSizedDialogFragment.OnActionClickListener listener) { 223 Bundle args = new Bundle(); 224 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId); 225 args.putInt(DvrStopRecordingFragment.KEY_REASON, reason); 226 DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment(); 227 fragment.setOnActionClickListener(listener); 228 showDialogFragment(activity, fragment, args); 229 } 230 231 /** 232 * Shows "already scheduled" dialog. 233 */ showAlreadyScheduleDialog(MainActivity activity, Program program)234 public static void showAlreadyScheduleDialog(MainActivity activity, Program program) { 235 if (program == null) { 236 return; 237 } 238 Bundle args = new Bundle(); 239 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 240 showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true); 241 } 242 243 /** 244 * Shows "already recorded" dialog. 245 */ showAlreadyRecordedDialog(MainActivity activity, Program program)246 public static void showAlreadyRecordedDialog(MainActivity activity, Program program) { 247 if (program == null) { 248 return; 249 } 250 Bundle args = new Bundle(); 251 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 252 showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true); 253 } 254 showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args)255 private static void showDialogFragment(Activity activity, 256 DvrHalfSizedDialogFragment dialogFragment, Bundle args) { 257 showDialogFragment(activity, dialogFragment, args, false, false); 258 } 259 showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, boolean keepProgramGuide)260 private static void showDialogFragment(Activity activity, 261 DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, 262 boolean keepProgramGuide) { 263 dialogFragment.setArguments(args); 264 if (activity instanceof MainActivity) { 265 ((MainActivity) activity).getOverlayManager() 266 .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment, 267 keepSidePanelHistory, keepProgramGuide); 268 } else { 269 dialogFragment.show(activity.getFragmentManager(), 270 DvrHalfSizedDialogFragment.DIALOG_TAG); 271 } 272 } 273 274 /** 275 * Checks whether channel watch conflict dialog is open or not. 276 */ isChannelWatchConflictDialogShown(MainActivity activity)277 public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { 278 return activity.getOverlayManager().getCurrentDialog() instanceof 279 DvrChannelWatchConflictDialogFragment; 280 } 281 getEarliestScheduledRecording(List<ScheduledRecording> recordings)282 private static ScheduledRecording getEarliestScheduledRecording(List<ScheduledRecording> 283 recordings) { 284 ScheduledRecording earlistScheduledRecording = null; 285 if (!recordings.isEmpty()) { 286 Collections.sort(recordings, 287 ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); 288 earlistScheduledRecording = recordings.get(0); 289 } 290 return earlistScheduledRecording; 291 } 292 293 /** 294 * Shows the schedules activity to resolve the tune conflict. 295 */ startSchedulesActivityForTuneConflict(Context context, Channel channel)296 public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) { 297 if (channel == null) { 298 return; 299 } 300 List<ScheduledRecording> conflicts = TvApplication.getSingletons(context).getDvrManager() 301 .getConflictingSchedulesForTune(channel.getId()); 302 startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); 303 } 304 305 /** 306 * Shows the schedules activity to resolve the one time recording conflict. 307 */ startSchedulesActivityForOneTimeRecordingConflict(Context context, List<ScheduledRecording> conflicts)308 public static void startSchedulesActivityForOneTimeRecordingConflict(Context context, 309 List<ScheduledRecording> conflicts) { 310 startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); 311 } 312 313 /** 314 * Shows the schedules activity with full schedule. 315 */ startSchedulesActivity(Context context, ScheduledRecording focusedScheduledRecording)316 public static void startSchedulesActivity(Context context, ScheduledRecording 317 focusedScheduledRecording) { 318 Intent intent = new Intent(context, DvrSchedulesActivity.class); 319 intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, 320 DvrSchedulesActivity.TYPE_FULL_SCHEDULE); 321 if (focusedScheduledRecording != null) { 322 intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, 323 focusedScheduledRecording); 324 } 325 context.startActivity(intent); 326 } 327 328 /** 329 * Shows the schedules activity for series recording. 330 */ startSchedulesActivityForSeries(Context context, SeriesRecording seriesRecording)331 public static void startSchedulesActivityForSeries(Context context, 332 SeriesRecording seriesRecording) { 333 Intent intent = new Intent(context, DvrSchedulesActivity.class); 334 intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, 335 DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); 336 intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, 337 seriesRecording); 338 context.startActivity(intent); 339 } 340 341 /** 342 * Shows the series settings activity. 343 * 344 * @param channelIds Channel ID list which has programs belonging to the series. 345 */ startSeriesSettingsActivity(Context context, long seriesRecordingId, @Nullable long[] channelIds, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog)346 public static void startSeriesSettingsActivity(Context context, long seriesRecordingId, 347 @Nullable long[] channelIds, boolean removeEmptySeriesSchedule, 348 boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog) { 349 Intent intent = new Intent(context, DvrSeriesSettingsActivity.class); 350 intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId); 351 intent.putExtra(DvrSeriesSettingsActivity.CHANNEL_ID_LIST, channelIds); 352 intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, 353 removeEmptySeriesSchedule); 354 intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent); 355 intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, 356 showViewScheduleOptionInDialog); 357 context.startActivity(intent); 358 } 359 360 /** 361 * Shows "series recording scheduled" dialog activity. 362 */ StartSeriesScheduledDialogActivity(Context context, SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog)363 public static void StartSeriesScheduledDialogActivity(Context context, 364 SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog) { 365 if (seriesRecording == null) { 366 return; 367 } 368 Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class); 369 intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, 370 seriesRecording.getId()); 371 intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, 372 showViewScheduleOptionInDialog); 373 context.startActivity(intent); 374 } 375 376 /** 377 * Shows the details activity for the DVR items. The type of DVR items may be 378 * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. 379 */ startDetailsActivity(Activity activity, Object dvrItem, @Nullable ImageView imageView, boolean hideViewSchedule)380 public static void startDetailsActivity(Activity activity, Object dvrItem, 381 @Nullable ImageView imageView, boolean hideViewSchedule) { 382 if (dvrItem == null) { 383 return; 384 } 385 Intent intent = new Intent(activity, DvrDetailsActivity.class); 386 long recordingId; 387 int viewType; 388 if (dvrItem instanceof ScheduledRecording) { 389 ScheduledRecording schedule = (ScheduledRecording) dvrItem; 390 recordingId = schedule.getId(); 391 if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { 392 viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW; 393 } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { 394 viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW; 395 } else { 396 return; 397 } 398 } else if (dvrItem instanceof RecordedProgram) { 399 recordingId = ((RecordedProgram) dvrItem).getId(); 400 viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW; 401 } else if (dvrItem instanceof SeriesRecording) { 402 recordingId = ((SeriesRecording) dvrItem).getId(); 403 viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW; 404 } else { 405 return; 406 } 407 intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId); 408 intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType); 409 intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule); 410 Bundle bundle = null; 411 if (imageView != null) { 412 bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, 413 DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); 414 } 415 activity.startActivity(intent, bundle); 416 } 417 418 /** 419 * Shows the cancel all dialog for series schedules list. 420 */ showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, SeriesRecording seriesRecording)421 public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, 422 SeriesRecording seriesRecording) { 423 DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = 424 new DvrStopSeriesRecordingDialogFragment(); 425 Bundle arguments = new Bundle(); 426 arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, 427 seriesRecording); 428 dvrStopSeriesRecordingDialogFragment.setArguments(arguments); 429 dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(), 430 DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); 431 } 432 433 /** 434 * Shows the series deletion activity. 435 */ startSeriesDeletionActivity(Context context, long seriesRecordingId)436 public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) { 437 Intent intent = new Intent(context, DvrSeriesDeletionActivity.class); 438 intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId); 439 context.startActivity(intent); 440 } 441 showAddScheduleToast(Context context, String title, long startTimeMs, long endTimeMs)442 public static void showAddScheduleToast(Context context, 443 String title, long startTimeMs, long endTimeMs) { 444 String msg = (startTimeMs > System.currentTimeMillis()) ? 445 context.getString(R.string.dvr_msg_program_scheduled, title) 446 : context.getString(R.string.dvr_msg_current_program_scheduled, title, 447 Utils.toTimeString(endTimeMs, false)); 448 Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 449 } 450 } 451