• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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