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