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;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.media.tv.TvInputManager;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.support.annotation.MainThread;
27 import android.support.annotation.Nullable;
28 import android.support.v4.app.ActivityOptionsCompat;
29 import android.text.TextUtils;
30 import android.widget.ImageView;
31 import android.widget.Toast;
32 
33 import com.android.tv.MainActivity;
34 import com.android.tv.R;
35 import com.android.tv.TvApplication;
36 import com.android.tv.common.SoftPreconditions;
37 import com.android.tv.data.Channel;
38 import com.android.tv.data.Program;
39 import com.android.tv.dvr.ui.DvrDetailsActivity;
40 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment;
41 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
42 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
43 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
44 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
45 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
46 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
47 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
48 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
49 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
50 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
51 import com.android.tv.dvr.ui.DvrSchedulesActivity;
52 import com.android.tv.dvr.ui.DvrSeriesDeletionActivity;
53 import com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity;
54 import com.android.tv.dvr.ui.DvrSeriesSettingsActivity;
55 import com.android.tv.dvr.ui.DvrStopRecordingFragment;
56 import com.android.tv.dvr.ui.DvrStopSeriesRecordingDialogFragment;
57 import com.android.tv.dvr.ui.DvrStopSeriesRecordingFragment;
58 import com.android.tv.dvr.ui.HalfSizedDialogFragment;
59 import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
60 import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
61 import com.android.tv.util.Utils;
62 
63 import java.util.Collections;
64 import java.util.List;
65 
66 /**
67  * A helper class for DVR UI.
68  */
69 @MainThread
70 @TargetApi(Build.VERSION_CODES.N)
71 public class DvrUiHelper {
72     /**
73      * Handles the action to create the new schedule. It returns {@code true} if the schedule is
74      * added and there's no additional UI, otherwise {@code false}.
75      */
handleCreateSchedule(MainActivity activity, Program program)76     public static boolean handleCreateSchedule(MainActivity activity, Program program) {
77         if (program == null) {
78             return false;
79         }
80         DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
81         if (!program.isEpisodic()) {
82             // One time recording.
83             dvrManager.addSchedule(program);
84             if (!dvrManager.getConflictingSchedules(program).isEmpty()) {
85                 DvrUiHelper.showScheduleConflictDialog(activity, program);
86                 return false;
87             }
88         } else {
89             SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
90             if (seriesRecording == null || seriesRecording.isStopped()) {
91                 DvrUiHelper.showScheduleDialog(activity, program);
92                 return false;
93             } else {
94                 // Show recorded program rather than the schedule.
95                 RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(),
96                         program.getSeasonNumber(), program.getEpisodeNumber());
97                 if (recordedProgram != null) {
98                     DvrUiHelper.showAlreadyRecordedDialog(activity, program);
99                     return false;
100                 }
101                 ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(),
102                         program.getSeasonNumber(), program.getEpisodeNumber());
103                 if (duplicate != null
104                         && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
105                         || duplicate.getState()
106                         == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
107                     DvrUiHelper.showAlreadyScheduleDialog(activity, program);
108                     return false;
109                 }
110                 // Just add the schedule.
111                 dvrManager.addSchedule(program);
112             }
113         }
114         return true;
115 
116     }
117 
118     /**
119      * Checks if the storage status is good for recording and shows error messages if needed.
120      *
121      * @return true if the storage status is fine to be recorded for {@code inputId}.
122      */
checkStorageStatusAndShowErrorMessage(Activity activity, String inputId)123     public static boolean checkStorageStatusAndShowErrorMessage(Activity activity, String inputId) {
124         if (!Utils.isBundledInput(inputId)) {
125             return true;
126         }
127         DvrStorageStatusManager dvrStorageStatusManager =
128                 TvApplication.getSingletons(activity).getDvrStorageStatusManager();
129         int status = dvrStorageStatusManager.getDvrStorageStatus();
130         if (status == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
131             showDvrSmallSizedStorageErrorDialog(activity);
132             return false;
133         } else if (status == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
134             showDvrMissingStorageErrorDialog(activity, inputId);
135             return false;
136         } else if (status == DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT) {
137             // TODO: handle insufficient storage case.
138             return true;
139         } else {
140             return true;
141         }
142     }
143 
144     /**
145      * Shows the schedule dialog.
146      */
showScheduleDialog(MainActivity activity, Program program)147     public static void showScheduleDialog(MainActivity activity, Program program) {
148         if (SoftPreconditions.checkNotNull(program) == null) {
149             return;
150         }
151         Bundle args = new Bundle();
152         args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
153         showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
154     }
155 
156     /**
157      * Shows the recording duration options dialog.
158      */
showChannelRecordDurationOptions(MainActivity activity, Channel channel)159     public static void showChannelRecordDurationOptions(MainActivity activity, Channel channel) {
160         if (SoftPreconditions.checkNotNull(channel) == null) {
161             return;
162         }
163         Bundle args = new Bundle();
164         args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
165         showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args);
166     }
167 
168     /**
169      * Shows the dialog which says that the new schedule conflicts with others.
170      */
showScheduleConflictDialog(MainActivity activity, Program program)171     public static void showScheduleConflictDialog(MainActivity activity, Program program) {
172         if (program == null) {
173             return;
174         }
175         Bundle args = new Bundle();
176         args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
177         showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true);
178     }
179 
180     /**
181      * Shows the conflict dialog for the channel watching.
182      */
showChannelWatchConflictDialog(MainActivity activity, Channel channel)183     public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) {
184         if (channel == null) {
185             return;
186         }
187         Bundle args = new Bundle();
188         args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
189         showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args);
190     }
191 
192     /**
193      * Shows DVR insufficient space error dialog.
194      */
showDvrInsufficientSpaceErrorDialog(MainActivity activity)195     public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity) {
196         showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), null);
197         Utils.clearRecordingFailedReason(activity,
198                 TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
199     }
200 
201     /**
202      * Shows DVR missing storage error dialog.
203      */
showDvrMissingStorageErrorDialog(Activity activity, String inputId)204     private static void showDvrMissingStorageErrorDialog(Activity activity, String inputId) {
205         SoftPreconditions.checkArgument(!TextUtils.isEmpty(inputId));
206         Bundle args = new Bundle();
207         args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, inputId);
208         showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), args);
209     }
210 
211     /**
212      * Shows DVR small sized storage error dialog.
213      */
showDvrSmallSizedStorageErrorDialog(Activity activity)214     public static void showDvrSmallSizedStorageErrorDialog(Activity activity) {
215         showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null);
216     }
217 
218     /**
219      * Shows stop recording dialog.
220      */
showStopRecordingDialog(Activity activity, long channelId, int reason, HalfSizedDialogFragment.OnActionClickListener listener)221     public static void showStopRecordingDialog(Activity activity, long channelId, int reason,
222             HalfSizedDialogFragment.OnActionClickListener listener) {
223         Bundle args = new Bundle();
224         args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId);
225         args.putInt(DvrStopRecordingFragment.KEY_REASON, reason);
226         DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment();
227         fragment.setOnActionClickListener(listener);
228         showDialogFragment(activity, fragment, args);
229     }
230 
231     /**
232      * Shows "already scheduled" dialog.
233      */
showAlreadyScheduleDialog(MainActivity activity, Program program)234     public static void showAlreadyScheduleDialog(MainActivity 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 DvrAlreadyScheduledDialogFragment(), args, false, true);
241     }
242 
243     /**
244      * Shows "already recorded" dialog.
245      */
showAlreadyRecordedDialog(MainActivity activity, Program program)246     public static void showAlreadyRecordedDialog(MainActivity activity, Program program) {
247         if (program == null) {
248             return;
249         }
250         Bundle args = new Bundle();
251         args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
252         showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
253     }
254 
showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args)255     private static void showDialogFragment(Activity activity,
256             DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
257         showDialogFragment(activity, dialogFragment, args, false, false);
258     }
259 
showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory, boolean keepProgramGuide)260     private static void showDialogFragment(Activity activity,
261             DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory,
262             boolean keepProgramGuide) {
263         dialogFragment.setArguments(args);
264         if (activity instanceof MainActivity) {
265             ((MainActivity) activity).getOverlayManager()
266                     .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment,
267                             keepSidePanelHistory, keepProgramGuide);
268         } else {
269             dialogFragment.show(activity.getFragmentManager(),
270                     DvrHalfSizedDialogFragment.DIALOG_TAG);
271         }
272     }
273 
274     /**
275      * Checks whether channel watch conflict dialog is open or not.
276      */
isChannelWatchConflictDialogShown(MainActivity activity)277     public static boolean isChannelWatchConflictDialogShown(MainActivity activity) {
278         return activity.getOverlayManager().getCurrentDialog() instanceof
279                 DvrChannelWatchConflictDialogFragment;
280     }
281 
getEarliestScheduledRecording(List<ScheduledRecording> recordings)282     private static ScheduledRecording getEarliestScheduledRecording(List<ScheduledRecording>
283             recordings) {
284         ScheduledRecording earlistScheduledRecording = null;
285         if (!recordings.isEmpty()) {
286             Collections.sort(recordings,
287                     ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
288             earlistScheduledRecording = recordings.get(0);
289         }
290         return earlistScheduledRecording;
291     }
292 
293     /**
294      * Shows the schedules activity to resolve the tune conflict.
295      */
startSchedulesActivityForTuneConflict(Context context, Channel channel)296     public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) {
297         if (channel == null) {
298             return;
299         }
300         List<ScheduledRecording> conflicts = TvApplication.getSingletons(context).getDvrManager()
301                 .getConflictingSchedulesForTune(channel.getId());
302         startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
303     }
304 
305     /**
306      * Shows the schedules activity to resolve the one time recording conflict.
307      */
startSchedulesActivityForOneTimeRecordingConflict(Context context, List<ScheduledRecording> conflicts)308     public static void startSchedulesActivityForOneTimeRecordingConflict(Context context,
309             List<ScheduledRecording> conflicts) {
310         startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
311     }
312 
313     /**
314      * Shows the schedules activity with full schedule.
315      */
startSchedulesActivity(Context context, ScheduledRecording focusedScheduledRecording)316     public static void startSchedulesActivity(Context context, ScheduledRecording
317             focusedScheduledRecording) {
318         Intent intent = new Intent(context, DvrSchedulesActivity.class);
319         intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
320                 DvrSchedulesActivity.TYPE_FULL_SCHEDULE);
321         if (focusedScheduledRecording != null) {
322             intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING,
323                     focusedScheduledRecording);
324         }
325         context.startActivity(intent);
326     }
327 
328     /**
329      * Shows the schedules activity for series recording.
330      */
startSchedulesActivityForSeries(Context context, SeriesRecording seriesRecording)331     public static void startSchedulesActivityForSeries(Context context,
332             SeriesRecording seriesRecording) {
333         Intent intent = new Intent(context, DvrSchedulesActivity.class);
334         intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
335                 DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
336         intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
337                 seriesRecording);
338         context.startActivity(intent);
339     }
340 
341     /**
342      * Shows the series settings activity.
343      *
344      * @param channelIds Channel ID list which has programs belonging to the series.
345      */
startSeriesSettingsActivity(Context context, long seriesRecordingId, @Nullable long[] channelIds, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog)346     public static void startSeriesSettingsActivity(Context context, long seriesRecordingId,
347             @Nullable long[] channelIds, boolean removeEmptySeriesSchedule,
348             boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog) {
349         Intent intent = new Intent(context, DvrSeriesSettingsActivity.class);
350         intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId);
351         intent.putExtra(DvrSeriesSettingsActivity.CHANNEL_ID_LIST, channelIds);
352         intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING,
353                 removeEmptySeriesSchedule);
354         intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent);
355         intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
356                 showViewScheduleOptionInDialog);
357         context.startActivity(intent);
358     }
359 
360     /**
361      * Shows "series recording scheduled" dialog activity.
362      */
StartSeriesScheduledDialogActivity(Context context, SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog)363     public static void StartSeriesScheduledDialogActivity(Context context,
364             SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog) {
365         if (seriesRecording == null) {
366             return;
367         }
368         Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class);
369         intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID,
370                 seriesRecording.getId());
371         intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
372                 showViewScheduleOptionInDialog);
373         context.startActivity(intent);
374     }
375 
376     /**
377      * Shows the details activity for the DVR items. The type of DVR items may be
378      * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}.
379      */
startDetailsActivity(Activity activity, Object dvrItem, @Nullable ImageView imageView, boolean hideViewSchedule)380     public static void startDetailsActivity(Activity activity, Object dvrItem,
381             @Nullable ImageView imageView, boolean hideViewSchedule) {
382         if (dvrItem == null) {
383             return;
384         }
385         Intent intent = new Intent(activity, DvrDetailsActivity.class);
386         long recordingId;
387         int viewType;
388         if (dvrItem instanceof ScheduledRecording) {
389             ScheduledRecording schedule = (ScheduledRecording) dvrItem;
390             recordingId = schedule.getId();
391             if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
392                 viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW;
393             } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
394                 viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW;
395             } else {
396                 return;
397             }
398         } else if (dvrItem instanceof RecordedProgram) {
399             recordingId = ((RecordedProgram) dvrItem).getId();
400             viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW;
401         } else if (dvrItem instanceof SeriesRecording) {
402             recordingId = ((SeriesRecording) dvrItem).getId();
403             viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW;
404         } else {
405             return;
406         }
407         intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId);
408         intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType);
409         intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule);
410         Bundle bundle = null;
411         if (imageView != null) {
412             bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView,
413                     DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
414         }
415         activity.startActivity(intent, bundle);
416     }
417 
418     /**
419      * Shows the cancel all dialog for series schedules list.
420      */
showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity, SeriesRecording seriesRecording)421     public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity,
422             SeriesRecording seriesRecording) {
423         DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment =
424                 new DvrStopSeriesRecordingDialogFragment();
425         Bundle arguments = new Bundle();
426         arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING,
427                 seriesRecording);
428         dvrStopSeriesRecordingDialogFragment.setArguments(arguments);
429         dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(),
430                 DvrStopSeriesRecordingDialogFragment.DIALOG_TAG);
431     }
432 
433     /**
434      * Shows the series deletion activity.
435      */
startSeriesDeletionActivity(Context context, long seriesRecordingId)436     public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) {
437         Intent intent = new Intent(context, DvrSeriesDeletionActivity.class);
438         intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId);
439         context.startActivity(intent);
440     }
441 
showAddScheduleToast(Context context, String title, long startTimeMs, long endTimeMs)442     public static void showAddScheduleToast(Context context,
443             String title, long startTimeMs, long endTimeMs) {
444         String msg = (startTimeMs > System.currentTimeMillis()) ?
445             context.getString(R.string.dvr_msg_program_scheduled, title)
446             : context.getString(R.string.dvr_msg_current_program_scheduled, title,
447                     Utils.toTimeString(endTimeMs, false));
448         Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
449     }
450 }
451