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 */ checkStorageStatusAndShowErrorMessage( Activity activity, String inputId, Runnable recordingRequestRunnable)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. */ showScheduleDialog( Activity activity, Program program, boolean addCurrentProgramToSeries)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. */ showChannelRecordDurationOptions(Activity activity, Channel channel)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. */ showScheduleConflictDialog(Activity activity, Program program)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. */ showChannelWatchConflictDialog(MainActivity activity, Channel channel)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. */ showDvrInsufficientSpaceErrorDialog( MainActivity activity, Set<String> failedScheduledRecordingInfoSet)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 */ showDvrNoFreeSpaceErrorDialog( Activity activity, Runnable recordingRequestRunnable)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. */ showDvrMissingStorageErrorDialog(Activity activity)200 private static void showDvrMissingStorageErrorDialog(Activity activity) { 201 showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null); 202 } 203 204 /** Shows DVR small sized storage error dialog. */ showDvrSmallSizedStorageErrorDialog(Activity activity)205 public static void showDvrSmallSizedStorageErrorDialog(Activity activity) { 206 showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null); 207 } 208 209 /** Shows stop recording dialog. */ showStopRecordingDialog( Activity activity, long channelId, int reason, HalfSizedDialogFragment.OnActionClickListener listener)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. */ showAlreadyScheduleDialog(Activity activity, Program program)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. */ showAlreadyRecordedDialog(Activity activity, Program program)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. */ showProgramInfoDialog(Activity activity, Program program)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 */ requestRecordingCurrentProgram( Activity activity, Channel channel, Program program, boolean addProgramToSeries)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 */ requestRecordingFutureProgram( Activity activity, Program program, boolean addProgramToSeries)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 */ handleCreateSchedule( Activity activity, Program program, boolean addProgramToSeries)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 showDialogFragment( Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args)342 private static void showDialogFragment( 343 Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args) { 344 showDialogFragment(activity, dialogFragment, args, false, false); 345 } 346 showDialogFragment( Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, boolean keepProgramGuide)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. */ isChannelWatchConflictDialogShown(MainActivity activity)369 public static boolean isChannelWatchConflictDialogShown(MainActivity activity) { 370 return activity.getOverlayManager().getCurrentDialog() 371 instanceof DvrChannelWatchConflictDialogFragment; 372 } 373 getEarliestScheduledRecording( List<ScheduledRecording> recordings)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 */ startPlaybackActivity( Context context, long programId, long seekTimeMs, boolean pinChecked)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. */ startSchedulesActivityForTuneConflict(Context context, Channel channel)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. */ startSchedulesActivityForOneTimeRecordingConflict( Context context, List<ScheduledRecording> conflicts)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. */ startDvrHistoryActivity(Context context)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. */ startSchedulesActivity( Context context, ScheduledRecording focusedScheduledRecording)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. */ startSchedulesActivityForSeries( Context context, SeriesRecording seriesRecording)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 */ startSeriesSettingsActivity( Context context, long seriesRecordingId, @Nullable List<Program> programs, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, Program currentProgram)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 startSeriesSettingsActivityInternal( Context context, long seriesRecordingId, @NonNull List<Program> programs, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog, Program currentProgram)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. */ startSeriesScheduledDialogActivity( Context context, SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog, List<Program> programs)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 */ startDetailsActivity( Activity activity, Object dvrItem, @Nullable ImageView imageView, boolean hideViewSchedule)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. */ showCancelAllSeriesRecordingDialog( DvrSchedulesActivity activity, SeriesRecording seriesRecording)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. */ startSeriesDeletionActivity(Context context, long seriesRecordingId)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 showAddScheduleToast( Context context, String title, long startTimeMs, long endTimeMs)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. */ getStyledTitleWithEpisodeNumber( Context context, ScheduledRecording schedule, int episodeNumberStyleResId)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. */ getStyledTitleWithEpisodeNumber( Context context, BaseProgram program, int episodeNumberStyleResId)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 getStyledTitleWithEpisodeNumber( Context context, String title, String seasonNumber, String episodeNumber, int episodeNumberStyleResId)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