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