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