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