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.ui; 18 19 import android.annotation.TargetApi; 20 import android.app.Activity; 21 import android.app.ProgressDialog; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.media.tv.TvInputManager; 26 import android.os.Build; 27 import android.os.Bundle; 28 import android.support.annotation.MainThread; 29 import android.support.annotation.NonNull; 30 import android.support.annotation.Nullable; 31 import android.support.v4.app.ActivityOptionsCompat; 32 import android.text.Html; 33 import android.text.Spannable; 34 import android.text.SpannableString; 35 import android.text.SpannableStringBuilder; 36 import android.text.TextUtils; 37 import android.text.style.TextAppearanceSpan; 38 import android.widget.ImageView; 39 import android.widget.Toast; 40 import com.android.tv.MainActivity; 41 import com.android.tv.R; 42 import com.android.tv.TvSingletons; 43 import com.android.tv.common.BuildConfig; 44 import com.android.tv.common.SoftPreconditions; 45 import com.android.tv.common.recording.RecordingStorageStatusManager; 46 import com.android.tv.common.util.CommonUtils; 47 import com.android.tv.data.BaseProgram; 48 import com.android.tv.data.Program; 49 import com.android.tv.data.api.Channel; 50 import com.android.tv.dialog.HalfSizedDialogFragment; 51 import com.android.tv.dvr.DvrManager; 52 import com.android.tv.dvr.data.RecordedProgram; 53 import com.android.tv.dvr.data.ScheduledRecording; 54 import com.android.tv.dvr.data.SeriesRecording; 55 import com.android.tv.dvr.provider.EpisodicProgramLoadTask; 56 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment; 57 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment; 58 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment; 59 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment; 60 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrFutureProgramInfoDialogFragment; 61 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment; 62 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment; 63 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment; 64 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment; 65 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment; 66 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment; 67 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment; 68 import com.android.tv.dvr.ui.browse.DvrBrowseActivity; 69 import com.android.tv.dvr.ui.browse.DvrDetailsActivity; 70 import com.android.tv.dvr.ui.list.DvrHistoryActivity; 71 import com.android.tv.dvr.ui.list.DvrSchedulesActivity; 72 import com.android.tv.dvr.ui.list.DvrSchedulesFragment; 73 import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; 74 import com.android.tv.dvr.ui.playback.DvrPlaybackActivity; 75 import com.android.tv.util.ToastUtils; 76 import com.android.tv.util.Utils; 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Set; 81 82 /** A helper class for DVR UI. */ 83 @MainThread 84 @TargetApi(Build.VERSION_CODES.N) 85 public class DvrUiHelper { 86 private static final String TAG = "DvrUiHelper"; 87 88 private static ProgressDialog sProgressDialog = null; 89 90 /** 91 * Checks if the storage status is good for recording and shows error messages if needed. 92 * 93 * @param recordingRequestRunnable if the storage status is OK to record or users choose to 94 * perform the operation anyway, this Runnable will run. 95 */ 96 public static void checkStorageStatusAndShowErrorMessage( 97 Activity activity, String inputId, Runnable recordingRequestRunnable) { 98 if (CommonUtils.isBundledInput(inputId)) { 99 switch (TvSingletons.getSingletons(activity) 100 .getRecordingStorageStatusManager() 101 .getDvrStorageStatus()) { 102 case RecordingStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL: 103 showDvrSmallSizedStorageErrorDialog(activity); 104 return; 105 case RecordingStorageStatusManager.STORAGE_STATUS_MISSING: 106 showDvrMissingStorageErrorDialog(activity); 107 return; 108 case RecordingStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT: 109 showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable); 110 return; 111 default: // fall out 112 } 113 } 114 recordingRequestRunnable.run(); 115 } 116 117 /** Shows the schedule dialog. */ 118 public static void showScheduleDialog( 119 Activity activity, Program program, boolean addCurrentProgramToSeries) { 120 if (SoftPreconditions.checkNotNull(program) == null) { 121 return; 122 } 123 Bundle args = new Bundle(); 124 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 125 args.putBoolean( 126 DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, addCurrentProgramToSeries); 127 showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true); 128 } 129 130 /** Shows the recording duration options dialog. */ 131 public static void showChannelRecordDurationOptions(Activity activity, Channel channel) { 132 if (SoftPreconditions.checkNotNull(channel) == null) { 133 return; 134 } 135 Bundle args = new Bundle(); 136 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); 137 showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args); 138 } 139 140 /** Shows the dialog which says that the new schedule conflicts with others. */ 141 public static void showScheduleConflictDialog(Activity activity, Program program) { 142 if (program == null) { 143 return; 144 } 145 Bundle args = new Bundle(); 146 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 147 showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true); 148 } 149 150 /** Shows the conflict dialog for the channel watching. */ 151 public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) { 152 if (channel == null) { 153 return; 154 } 155 Bundle args = new Bundle(); 156 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId()); 157 showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args); 158 } 159 160 /** Shows DVR insufficient space error dialog. */ 161 public static void showDvrInsufficientSpaceErrorDialog( 162 MainActivity activity, Set<String> failedScheduledRecordingInfoSet) { 163 Bundle args = new Bundle(); 164 ArrayList<String> failedScheduledRecordingInfoArray = 165 new ArrayList<>(failedScheduledRecordingInfoSet); 166 args.putStringArrayList( 167 DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS, 168 failedScheduledRecordingInfoArray); 169 showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args); 170 Utils.clearRecordingFailedReason( 171 activity, TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE); 172 Utils.clearFailedScheduledRecordingInfoSet(activity); 173 } 174 175 /** 176 * Shows DVR no free space error dialog. 177 * 178 * @param recordingRequestRunnable the recording request to be executed when users choose {@link 179 * DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}. 180 */ 181 public static void showDvrNoFreeSpaceErrorDialog( 182 Activity activity, Runnable recordingRequestRunnable) { 183 DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment(); 184 fragment.setOnActionClickListener( 185 new HalfSizedDialogFragment.OnActionClickListener() { 186 @Override 187 public void onActionClick(long actionId) { 188 if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) { 189 recordingRequestRunnable.run(); 190 } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) { 191 Intent intent = new Intent(activity, DvrBrowseActivity.class); 192 activity.startActivity(intent); 193 } 194 } 195 }); 196 showDialogFragment(activity, fragment, null); 197 } 198 199 /** Shows DVR missing storage error dialog. */ 200 private static void showDvrMissingStorageErrorDialog(Activity activity) { 201 showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null); 202 } 203 204 /** Shows DVR small sized storage error dialog. */ 205 public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { 206 showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); 207 } 208 209 /** Shows stop recording dialog. */ 210 public static void showStopRecordingDialog( 211 Activity activity, 212 long channelId, 213 int reason, 214 HalfSizedDialogFragment.OnActionClickListener listener) { 215 Bundle args = new Bundle(); 216 args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId); 217 args.putInt(DvrStopRecordingFragment.KEY_REASON, reason); 218 DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment(); 219 fragment.setOnActionClickListener(listener); 220 showDialogFragment(activity, fragment, args); 221 } 222 223 /** Shows "already scheduled" dialog. */ 224 public static void showAlreadyScheduleDialog(Activity activity, Program program) { 225 if (program == null) { 226 return; 227 } 228 Bundle args = new Bundle(); 229 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 230 showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true); 231 } 232 233 /** Shows "already recorded" dialog. */ 234 public static void showAlreadyRecordedDialog(Activity 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 DvrAlreadyRecordedDialogFragment(), args, false, true); 241 } 242 243 /** Shows program information dialog. */ 244 public static void showProgramInfoDialog(Activity activity, Program program) { 245 if (program == null || !BuildConfig.ENG) { 246 return; 247 } 248 Bundle args = new Bundle(); 249 args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); 250 showDialogFragment(activity, new DvrFutureProgramInfoDialogFragment(), args, false, true); 251 } 252 253 /** 254 * Handle the request of recording a current program. It will handle creating schedules and 255 * shows the proper dialog and toast message respectively for timed-recording and program 256 * recording cases. 257 * 258 * @param addProgramToSeries denotes whether the program to be recorded should be added into the 259 * series recording when users choose to record the entire series. 260 */ 261 public static void requestRecordingCurrentProgram( 262 Activity activity, Channel channel, Program program, boolean addProgramToSeries) { 263 if (program == null) { 264 DvrUiHelper.showChannelRecordDurationOptions(activity, channel); 265 } else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { 266 String msg = 267 activity.getString( 268 R.string.dvr_msg_current_program_scheduled, 269 program.getTitle(), 270 Utils.toTimeString(program.getEndTimeUtcMillis(), false)); 271 Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); 272 } 273 } 274 275 /** 276 * Handle the request of recording a future program. It will handle creating schedules and shows 277 * the proper toast message. 278 * 279 * @param addProgramToSeries denotes whether the program to be recorded should be added into the 280 * series recording when users choose to record the entire series. 281 */ 282 public static void requestRecordingFutureProgram( 283 Activity activity, Program program, boolean addProgramToSeries) { 284 if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) { 285 String msg = activity.getString(R.string.dvr_msg_program_scheduled, program.getTitle()); 286 ToastUtils.show(activity, msg, Toast.LENGTH_SHORT); 287 } 288 } 289 290 /** 291 * Handles the action to create the new schedule. It returns {@code true} if the schedule is 292 * added and there's no additional UI, otherwise {@code false}. 293 */ 294 private static boolean handleCreateSchedule( 295 Activity activity, Program program, boolean addProgramToSeries) { 296 if (program == null) { 297 return false; 298 } 299 DvrManager dvrManager = TvSingletons.getSingletons(activity).getDvrManager(); 300 if (!program.isEpisodic()) { 301 // One time recording. 302 dvrManager.addSchedule(program); 303 if (!dvrManager.getConflictingSchedules(program).isEmpty()) { 304 DvrUiHelper.showScheduleConflictDialog(activity, program); 305 return false; 306 } 307 } else { 308 // Show recorded program rather than the schedule. 309 RecordedProgram recordedProgram = 310 dvrManager.getRecordedProgram( 311 program.getTitle(), 312 program.getSeasonNumber(), 313 program.getEpisodeNumber()); 314 if (recordedProgram != null) { 315 DvrUiHelper.showAlreadyRecordedDialog(activity, program); 316 return false; 317 } 318 ScheduledRecording duplicate = 319 dvrManager.getScheduledRecording( 320 program.getTitle(), 321 program.getSeasonNumber(), 322 program.getEpisodeNumber()); 323 if (duplicate != null 324 && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED 325 || duplicate.getState() 326 == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { 327 DvrUiHelper.showAlreadyScheduleDialog(activity, program); 328 return false; 329 } 330 SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program); 331 if (seriesRecording == null || seriesRecording.isStopped()) { 332 DvrUiHelper.showScheduleDialog(activity, program, addProgramToSeries); 333 return false; 334 } else { 335 // Just add the schedule. 336 dvrManager.addSchedule(program); 337 } 338 } 339 return true; 340 } 341 342 private static void showDialogFragment( 343 Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args) { 344 showDialogFragment(activity, dialogFragment, args, false, false); 345 } 346 347 private static void showDialogFragment( 348 Activity activity, 349 DvrHalfSizedDialogFragment dialogFragment, 350 Bundle args, 351 boolean keepSidePanelHistory, 352 boolean keepProgramGuide) { 353 dialogFragment.setArguments(args); 354 if (activity instanceof MainActivity) { 355 ((MainActivity) activity) 356 .getOverlayManager() 357 .showDialogFragment( 358 DvrHalfSizedDialogFragment.DIALOG_TAG, 359 dialogFragment, 360 keepSidePanelHistory, 361 keepProgramGuide); 362 } else { 363 dialogFragment.show( 364 activity.getFragmentManager(), DvrHalfSizedDialogFragment.DIALOG_TAG); 365 } 366 } 367 368 /** Checks whether channel watch conflict dialog is open or not. */ 369 public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { 370 return activity.getOverlayManager().getCurrentDialog() 371 instanceof DvrChannelWatchConflictDialogFragment; 372 } 373 374 private static ScheduledRecording getEarliestScheduledRecording( 375 List<ScheduledRecording> recordings) { 376 ScheduledRecording earlistScheduledRecording = null; 377 if (!recordings.isEmpty()) { 378 Collections.sort( 379 recordings, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); 380 earlistScheduledRecording = recordings.get(0); 381 } 382 return earlistScheduledRecording; 383 } 384 385 /** 386 * Launches DVR playback activity for the give recorded program. 387 * 388 * @param programId the ID of the recorded program going to be played. 389 * @param seekTimeMs the seek position to initial playback. 390 * @param pinChecked {@code true} if the pin code for parental controls has already been 391 * verified, otherwise {@code false}. 392 */ 393 public static void startPlaybackActivity( 394 Context context, long programId, long seekTimeMs, boolean pinChecked) { 395 Intent intent = new Intent(context, DvrPlaybackActivity.class); 396 intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId); 397 if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) { 398 intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs); 399 } 400 intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked); 401 context.startActivity(intent); 402 } 403 404 /** Shows the schedules activity to resolve the tune conflict. */ 405 public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) { 406 if (channel == null) { 407 return; 408 } 409 List<ScheduledRecording> conflicts = 410 TvSingletons.getSingletons(context) 411 .getDvrManager() 412 .getConflictingSchedulesForTune(channel.getId()); 413 startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); 414 } 415 416 /** Shows the schedules activity to resolve the one time recording conflict. */ 417 public static void startSchedulesActivityForOneTimeRecordingConflict( 418 Context context, List<ScheduledRecording> conflicts) { 419 startSchedulesActivity(context, getEarliestScheduledRecording(conflicts)); 420 } 421 422 /** Shows the schedules activity with full schedule. */ 423 public static void startDvrHistoryActivity(Context context) { 424 Intent intent = new Intent(context, DvrHistoryActivity.class); 425 context.startActivity(intent); 426 } 427 428 /** Shows the schedules activity with full schedule. */ 429 public static void startSchedulesActivity( 430 Context context, ScheduledRecording focusedScheduledRecording) { 431 Intent intent = new Intent(context, DvrSchedulesActivity.class); 432 intent.putExtra( 433 DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_FULL_SCHEDULE); 434 if (focusedScheduledRecording != null) { 435 intent.putExtra( 436 DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, 437 focusedScheduledRecording); 438 } 439 context.startActivity(intent); 440 } 441 442 /** Shows the schedules activity for series recording. */ 443 public static void startSchedulesActivityForSeries( 444 Context context, SeriesRecording seriesRecording) { 445 Intent intent = new Intent(context, DvrSchedulesActivity.class); 446 intent.putExtra( 447 DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_SERIES_SCHEDULE); 448 intent.putExtra( 449 DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, seriesRecording); 450 context.startActivity(intent); 451 } 452 453 /** 454 * Shows the series settings activity. 455 * 456 * @param programs list of programs which belong to the series. 457 */ 458 public static void startSeriesSettingsActivity( 459 Context context, 460 long seriesRecordingId, 461 @Nullable List<Program> programs, 462 boolean removeEmptySeriesSchedule, 463 boolean isWindowTranslucent, 464 boolean showViewScheduleOptionInDialog, 465 Program currentProgram) { 466 SeriesRecording series = 467 TvSingletons.getSingletons(context) 468 .getDvrDataManager() 469 .getSeriesRecording(seriesRecordingId); 470 if (series == null) { 471 return; 472 } 473 if (programs != null) { 474 startSeriesSettingsActivityInternal( 475 context, 476 seriesRecordingId, 477 programs, 478 removeEmptySeriesSchedule, 479 isWindowTranslucent, 480 showViewScheduleOptionInDialog, 481 currentProgram); 482 } else { 483 EpisodicProgramLoadTask episodicProgramLoadTask = 484 new EpisodicProgramLoadTask(context, series) { 485 @Override 486 protected void onPostExecute(List<Program> loadedPrograms) { 487 sProgressDialog.dismiss(); 488 sProgressDialog = null; 489 startSeriesSettingsActivityInternal( 490 context, 491 seriesRecordingId, 492 loadedPrograms == null 493 ? Collections.EMPTY_LIST 494 : loadedPrograms, 495 removeEmptySeriesSchedule, 496 isWindowTranslucent, 497 showViewScheduleOptionInDialog, 498 currentProgram); 499 } 500 }.setLoadCurrentProgram(true) 501 .setLoadDisallowedProgram(true) 502 .setLoadScheduledEpisode(true) 503 .setIgnoreChannelOption(true); 504 sProgressDialog = 505 ProgressDialog.show( 506 context, 507 null, 508 context.getString( 509 R.string.dvr_series_progress_message_reading_programs), 510 true, 511 true, 512 new DialogInterface.OnCancelListener() { 513 @Override 514 public void onCancel(DialogInterface dialogInterface) { 515 episodicProgramLoadTask.cancel(true); 516 sProgressDialog = null; 517 } 518 }); 519 episodicProgramLoadTask.execute(); 520 } 521 } 522 523 private static void startSeriesSettingsActivityInternal( 524 Context context, 525 long seriesRecordingId, 526 @NonNull List<Program> programs, 527 boolean removeEmptySeriesSchedule, 528 boolean isWindowTranslucent, 529 boolean showViewScheduleOptionInDialog, 530 Program currentProgram) { 531 SoftPreconditions.checkState( 532 programs != null, TAG, "Start series settings activity but programs is null"); 533 Intent intent = new Intent(context, DvrSeriesSettingsActivity.class); 534 intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId); 535 BigArguments.reset(); 536 BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs); 537 intent.putExtra( 538 DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, removeEmptySeriesSchedule); 539 intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent); 540 intent.putExtra( 541 DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG, 542 showViewScheduleOptionInDialog); 543 intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram); 544 context.startActivity(intent); 545 } 546 547 /** Shows "series recording scheduled" dialog activity. */ 548 public static void startSeriesScheduledDialogActivity( 549 Context context, 550 SeriesRecording seriesRecording, 551 boolean showViewScheduleOptionInDialog, 552 List<Program> programs) { 553 if (seriesRecording == null) { 554 return; 555 } 556 Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class); 557 intent.putExtra( 558 DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, seriesRecording.getId()); 559 intent.putExtra( 560 DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, 561 showViewScheduleOptionInDialog); 562 BigArguments.reset(); 563 BigArguments.setArgument( 564 DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, programs); 565 context.startActivity(intent); 566 } 567 568 /** 569 * Shows the details activity for the DVR items. The type of DVR items may be {@link 570 * ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}. 571 */ 572 public static void startDetailsActivity( 573 Activity activity, 574 Object dvrItem, 575 @Nullable ImageView imageView, 576 boolean hideViewSchedule) { 577 if (dvrItem == null) { 578 return; 579 } 580 Intent intent = new Intent(activity, DvrDetailsActivity.class); 581 long recordingId; 582 int viewType; 583 if (dvrItem instanceof ScheduledRecording) { 584 ScheduledRecording schedule = (ScheduledRecording) dvrItem; 585 recordingId = schedule.getId(); 586 if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) { 587 viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW; 588 } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { 589 viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW; 590 } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED 591 && schedule.getRecordedProgramId() != null) { 592 recordingId = schedule.getRecordedProgramId(); 593 viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW; 594 } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) { 595 viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW; 596 hideViewSchedule = true; 597 // TODO(b/72638385): pass detailed error message 598 intent.putExtra( 599 DvrDetailsActivity.EXTRA_FAILED_MESSAGE, 600 activity.getString(R.string.dvr_recording_failed)); 601 } else { 602 return; 603 } 604 } else if (dvrItem instanceof RecordedProgram) { 605 recordingId = ((RecordedProgram) dvrItem).getId(); 606 viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW; 607 } else if (dvrItem instanceof SeriesRecording) { 608 recordingId = ((SeriesRecording) dvrItem).getId(); 609 viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW; 610 } else { 611 return; 612 } 613 intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId); 614 intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType); 615 intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule); 616 Bundle bundle = null; 617 if (imageView != null) { 618 bundle = 619 ActivityOptionsCompat.makeSceneTransitionAnimation( 620 activity, imageView, DvrDetailsActivity.SHARED_ELEMENT_NAME) 621 .toBundle(); 622 } 623 activity.startActivity(intent, bundle); 624 } 625 626 /** Shows the cancel all dialog for series schedules list. */ 627 public static void showCancelAllSeriesRecordingDialog( 628 DvrSchedulesActivity activity, SeriesRecording seriesRecording) { 629 DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = 630 new DvrStopSeriesRecordingDialogFragment(); 631 Bundle arguments = new Bundle(); 632 arguments.putParcelable( 633 DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, seriesRecording); 634 dvrStopSeriesRecordingDialogFragment.setArguments(arguments); 635 dvrStopSeriesRecordingDialogFragment.show( 636 activity.getFragmentManager(), DvrStopSeriesRecordingDialogFragment.DIALOG_TAG); 637 } 638 639 /** Shows the series deletion activity. */ 640 public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) { 641 Intent intent = new Intent(context, DvrSeriesDeletionActivity.class); 642 intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId); 643 context.startActivity(intent); 644 } 645 646 public static void showAddScheduleToast( 647 Context context, String title, long startTimeMs, long endTimeMs) { 648 String msg = 649 (startTimeMs > System.currentTimeMillis()) 650 ? context.getString(R.string.dvr_msg_program_scheduled, title) 651 : context.getString( 652 R.string.dvr_msg_current_program_scheduled, 653 title, 654 Utils.toTimeString(endTimeMs, false)); 655 Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 656 } 657 658 /** Returns the styled schedule's title with its season and episode number. */ 659 public static CharSequence getStyledTitleWithEpisodeNumber( 660 Context context, ScheduledRecording schedule, int episodeNumberStyleResId) { 661 return getStyledTitleWithEpisodeNumber( 662 context, 663 schedule.getProgramTitle(), 664 schedule.getSeasonNumber(), 665 schedule.getEpisodeNumber(), 666 episodeNumberStyleResId); 667 } 668 669 /** Returns the styled program's title with its season and episode number. */ 670 public static CharSequence getStyledTitleWithEpisodeNumber( 671 Context context, BaseProgram program, int episodeNumberStyleResId) { 672 return getStyledTitleWithEpisodeNumber( 673 context, 674 program.getTitle(), 675 program.getSeasonNumber(), 676 program.getEpisodeNumber(), 677 episodeNumberStyleResId); 678 } 679 680 @NonNull 681 public static CharSequence getStyledTitleWithEpisodeNumber( 682 Context context, 683 String title, 684 String seasonNumber, 685 String episodeNumber, 686 int episodeNumberStyleResId) { 687 if (TextUtils.isEmpty(title)) { 688 return ""; 689 } 690 SpannableStringBuilder builder; 691 if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) { 692 builder = 693 TextUtils.isEmpty(episodeNumber) 694 ? new SpannableStringBuilder(title) 695 : new SpannableStringBuilder(Html.fromHtml(context.getString( 696 R.string.program_title_with_episode_number_no_season, 697 title, 698 episodeNumber))); 699 } else { 700 builder = 701 new SpannableStringBuilder( 702 Html.fromHtml( 703 context.getString( 704 R.string.program_title_with_episode_number, 705 title, 706 seasonNumber, 707 episodeNumber))); 708 } 709 Object[] spans = builder.getSpans(0, builder.length(), Object.class); 710 if (spans.length > 0) { 711 if (episodeNumberStyleResId != 0) { 712 builder.setSpan( 713 new TextAppearanceSpan(context, episodeNumberStyleResId), 714 builder.getSpanStart(spans[0]), 715 builder.getSpanEnd(spans[0]), 716 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 717 } 718 builder.removeSpan(spans[0]); 719 } 720 return new SpannableString(builder); 721 } 722 } 723