• 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.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 }