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
17package com.android.tv.dvr.ui;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.app.ProgressDialog;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.media.tv.TvInputManager;
26import android.os.Build;
27import android.os.Bundle;
28import android.support.annotation.MainThread;
29import android.support.annotation.NonNull;
30import android.support.annotation.Nullable;
31import android.support.v4.app.ActivityOptionsCompat;
32import android.text.Html;
33import android.text.Spannable;
34import android.text.SpannableString;
35import android.text.SpannableStringBuilder;
36import android.text.TextUtils;
37import android.text.style.TextAppearanceSpan;
38import android.widget.ImageView;
39import android.widget.Toast;
40
41import com.android.tv.MainActivity;
42import com.android.tv.R;
43import com.android.tv.TvApplication;
44import com.android.tv.common.SoftPreconditions;
45import com.android.tv.data.BaseProgram;
46import com.android.tv.data.Channel;
47import com.android.tv.data.Program;
48import com.android.tv.dialog.HalfSizedDialogFragment;
49import com.android.tv.dvr.DvrManager;
50import com.android.tv.dvr.DvrStorageStatusManager;
51import com.android.tv.dvr.data.RecordedProgram;
52import com.android.tv.dvr.data.ScheduledRecording;
53import com.android.tv.dvr.data.SeriesRecording;
54import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
55import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
56import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
57import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
58import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
59import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
60import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
61import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
62import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
63import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
64import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
65import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
66import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
67import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
68import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
69import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
70import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
71import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
72import com.android.tv.util.ToastUtils;
73import com.android.tv.util.Utils;
74
75import java.util.ArrayList;
76import java.util.Collections;
77import java.util.List;
78import java.util.Set;
79
80/**
81 * A helper class for DVR UI.
82 */
83@MainThread
84@TargetApi(Build.VERSION_CODES.N)
85public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
215    public static void showDvrSmallSizedStorageErrorDialog(Activity activity) {
216        showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null);
217    }
218
219    /**
220     * Shows stop recording dialog.
221     */
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     */
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     */
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     */
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     */
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     */
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
337    private static void showDialogFragment(Activity activity,
338            DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
339        showDialogFragment(activity, dialogFragment, args, false, false);
340    }
341
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     */
359    public static boolean isChannelWatchConflictDialogShown(MainActivity activity) {
360        return activity.getOverlayManager().getCurrentDialog() instanceof
361                DvrChannelWatchConflictDialogFragment;
362    }
363
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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
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     */
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     */
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
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}