com.android.tv.dvr.ui.DvrUiHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tv.dvr.ui.DvrUiHelper.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tv.dvr.ui;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.tv.TvInputManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.widget.ImageView;
import android.widget.Toast;

import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * A helper class for DVR UI.
 */
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public class DvrUiHelper {
    private static final String TAG = "DvrUiHelper";

    private static ProgressDialog sProgressDialog = null;

    /**
     * Checks if the storage status is good for recording and shows error messages if needed.
     *
     * @param recordingRequestRunnable if the storage status is OK to record or users choose to
     *                                 perform the operation anyway, this Runnable will run.
     */
    public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId,
            Runnable recordingRequestRunnable) {
        if (Utils.isBundledInput(inputId)) {
            switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager().getDvrStorageStatus()) {
            case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
                showDvrSmallSizedStorageErrorDialog(activity);
                return;
            case DvrStorageStatusManager.STORAGE_STATUS_MISSING:
                showDvrMissingStorageErrorDialog(activity);
                return;
            case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
                showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable);
                return;
            }
        }
        recordingRequestRunnable.run();
    }

    /**
     * Shows the schedule dialog.
     */
    public static void showScheduleDialog(Activity activity, Program program, boolean addCurrentProgramToSeries) {
        if (SoftPreconditions.checkNotNull(program) == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
        args.putBoolean(DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, addCurrentProgramToSeries);
        showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
    }

    /**
     * Shows the recording duration options dialog.
     */
    public static void showChannelRecordDurationOptions(Activity activity, Channel channel) {
        if (SoftPreconditions.checkNotNull(channel) == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
        showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args);
    }

    /**
     * Shows the dialog which says that the new schedule conflicts with others.
     */
    public static void showScheduleConflictDialog(Activity activity, Program program) {
        if (program == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
        showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true);
    }

    /**
     * Shows the conflict dialog for the channel watching.
     */
    public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) {
        if (channel == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
        showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args);
    }

    /**
     * Shows DVR insufficient space error dialog.
     */
    public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity,
            Set<String> failedScheduledRecordingInfoSet) {
        Bundle args = new Bundle();
        ArrayList<String> failedScheduledRecordingInfoArray = new ArrayList<>(failedScheduledRecordingInfoSet);
        args.putStringArrayList(DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS,
                failedScheduledRecordingInfoArray);
        showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args);
        Utils.clearRecordingFailedReason(activity, TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
        Utils.clearFailedScheduledRecordingInfoSet(activity);
    }

    /**
     * Shows DVR no free space error dialog.
     *
     * @param recordingRequestRunnable the recording request to be executed when users choose
     *                                 {@link DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}.
     */
    public static void showDvrNoFreeSpaceErrorDialog(Activity activity, Runnable recordingRequestRunnable) {
        DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment();
        fragment.setOnActionClickListener(new HalfSizedDialogFragment.OnActionClickListener() {
            @Override
            public void onActionClick(long actionId) {
                if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) {
                    recordingRequestRunnable.run();
                } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) {
                    Intent intent = new Intent(activity, DvrBrowseActivity.class);
                    activity.startActivity(intent);
                }
            }
        });
        showDialogFragment(activity, fragment, null);
    }

    /**
     * Shows DVR missing storage error dialog.
     */
    private static void showDvrMissingStorageErrorDialog(Activity activity) {
        showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null);
    }

    /**
     * Shows DVR small sized storage error dialog.
     */
    public static void showDvrSmallSizedStorageErrorDialog(Activity activity) {
        showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null);
    }

    /**
     * Shows stop recording dialog.
     */
    public static void showStopRecordingDialog(Activity activity, long channelId, int reason,
            HalfSizedDialogFragment.OnActionClickListener listener) {
        Bundle args = new Bundle();
        args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId);
        args.putInt(DvrStopRecordingFragment.KEY_REASON, reason);
        DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment();
        fragment.setOnActionClickListener(listener);
        showDialogFragment(activity, fragment, args);
    }

    /**
     * Shows "already scheduled" dialog.
     */
    public static void showAlreadyScheduleDialog(Activity activity, Program program) {
        if (program == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
        showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true);
    }

    /**
     * Shows "already recorded" dialog.
     */
    public static void showAlreadyRecordedDialog(Activity activity, Program program) {
        if (program == null) {
            return;
        }
        Bundle args = new Bundle();
        args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
        showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
    }

    /**
     * Handle the request of recording a current program. It will handle creating schedules and
     * shows the proper dialog and toast message respectively for timed-recording and program
     * recording cases.
     *
     * @param addProgramToSeries denotes whether the program to be recorded should be added into
     *                           the series recording when users choose to record the entire series.
     */
    public static void requestRecordingCurrentProgram(Activity activity, Channel channel, Program program,
            boolean addProgramToSeries) {
        if (program == null) {
            DvrUiHelper.showChannelRecordDurationOptions(activity, channel);
        } else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
            String msg = activity.getString(R.string.dvr_msg_current_program_scheduled, program.getTitle(),
                    Utils.toTimeString(program.getEndTimeUtcMillis(), false));
            Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Handle the request of recording a future program. It will handle creating schedules and
     * shows the proper toast message.
     *
     * @param addProgramToSeries denotes whether the program to be recorded should be added into
     *                           the series recording when users choose to record the entire series.
     */
    public static void requestRecordingFutureProgram(Activity activity, Program program,
            boolean addProgramToSeries) {
        if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
            String msg = activity.getString(R.string.dvr_msg_program_scheduled, program.getTitle());
            ToastUtils.show(activity, msg, Toast.LENGTH_SHORT);
        }
    }

    /**
     * Handles the action to create the new schedule. It returns {@code true} if the schedule is
     * added and there's no additional UI, otherwise {@code false}.
     */
    private static boolean handleCreateSchedule(Activity activity, Program program, boolean addProgramToSeries) {
        if (program == null) {
            return false;
        }
        DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
        if (!program.isEpisodic()) {
            // One time recording.
            dvrManager.addSchedule(program);
            if (!dvrManager.getConflictingSchedules(program).isEmpty()) {
                DvrUiHelper.showScheduleConflictDialog(activity, program);
                return false;
            }
        } else {
            // Show recorded program rather than the schedule.
            RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(),
                    program.getSeasonNumber(), program.getEpisodeNumber());
            if (recordedProgram != null) {
                DvrUiHelper.showAlreadyRecordedDialog(activity, program);
                return false;
            }
            ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(),
                    program.getSeasonNumber(), program.getEpisodeNumber());
            if (duplicate != null && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
                    || duplicate.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
                DvrUiHelper.showAlreadyScheduleDialog(activity, program);
                return false;
            }
            SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
            if (seriesRecording == null || seriesRecording.isStopped()) {
                DvrUiHelper.showScheduleDialog(activity, program, addProgramToSeries);
                return false;
            } else {
                // Just add the schedule.
                dvrManager.addSchedule(program);
            }
        }
        return true;
    }

    private static void showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment,
            Bundle args) {
        showDialogFragment(activity, dialogFragment, args, false, false);
    }

    private static void showDialogFragment(Activity activity, DvrHalfSizedDialogFragment dialogFragment,
            Bundle args, boolean keepSidePanelHistory, boolean keepProgramGuide) {
        dialogFragment.setArguments(args);
        if (activity instanceof MainActivity) {
            ((MainActivity) activity).getOverlayManager().showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG,
                    dialogFragment, keepSidePanelHistory, keepProgramGuide);
        } else {
            dialogFragment.show(activity.getFragmentManager(), DvrHalfSizedDialogFragment.DIALOG_TAG);
        }
    }

    /**
     * Checks whether channel watch conflict dialog is open or not.
     */
    public static boolean isChannelWatchConflictDialogShown(MainActivity activity) {
        return activity.getOverlayManager().getCurrentDialog() instanceof DvrChannelWatchConflictDialogFragment;
    }

    private static ScheduledRecording getEarliestScheduledRecording(List<ScheduledRecording> recordings) {
        ScheduledRecording earlistScheduledRecording = null;
        if (!recordings.isEmpty()) {
            Collections.sort(recordings, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
            earlistScheduledRecording = recordings.get(0);
        }
        return earlistScheduledRecording;
    }

    /**
     * Launches DVR playback activity for the give recorded program.
     *
     * @param programId the ID of the recorded program going to be played.
     * @param seekTimeMs the seek position to initial playback.
     * @param pinChecked {@code true} if the pin code for parental controls has already been
     *                   verified, otherwise {@code false}.
     */
    public static void startPlaybackActivity(Context context, long programId, long seekTimeMs, boolean pinChecked) {
        Intent intent = new Intent(context, DvrPlaybackActivity.class);
        intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
        if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
            intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs);
        }
        intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked);
        context.startActivity(intent);
    }

    /**
     * Shows the schedules activity to resolve the tune conflict.
     */
    public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) {
        if (channel == null) {
            return;
        }
        List<ScheduledRecording> conflicts = TvApplication.getSingletons(context).getDvrManager()
                .getConflictingSchedulesForTune(channel.getId());
        startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
    }

    /**
     * Shows the schedules activity to resolve the one time recording conflict.
     */
    public static void startSchedulesActivityForOneTimeRecordingConflict(Context context,
            List<ScheduledRecording> conflicts) {
        startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
    }

    /**
     * Shows the schedules activity with full schedule.
     */
    public static void startSchedulesActivity(Context context, ScheduledRecording focusedScheduledRecording) {
        Intent intent = new Intent(context, DvrSchedulesActivity.class);
        intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_FULL_SCHEDULE);
        if (focusedScheduledRecording != null) {
            intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING, focusedScheduledRecording);
        }
        context.startActivity(intent);
    }

    /**
     * Shows the schedules activity for series recording.
     */
    public static void startSchedulesActivityForSeries(Context context, SeriesRecording seriesRecording) {
        Intent intent = new Intent(context, DvrSchedulesActivity.class);
        intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
        intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, seriesRecording);
        context.startActivity(intent);
    }

    /**
     * Shows the series settings activity.
     *
     * @param programs list of programs which belong to the series.
     */
    public static void startSeriesSettingsActivity(Context context, long seriesRecordingId,
            @Nullable List<Program> programs, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent,
            boolean showViewScheduleOptionInDialog, Program currentProgram) {
        SeriesRecording series = TvApplication.getSingletons(context).getDvrDataManager()
                .getSeriesRecording(seriesRecordingId);
        if (series == null) {
            return;
        }
        if (programs != null) {
            startSeriesSettingsActivityInternal(context, seriesRecordingId, programs, removeEmptySeriesSchedule,
                    isWindowTranslucent, showViewScheduleOptionInDialog, currentProgram);
        } else {
            EpisodicProgramLoadTask episodicProgramLoadTask = new EpisodicProgramLoadTask(context, series) {
                @Override
                protected void onPostExecute(List<Program> loadedPrograms) {
                    sProgressDialog.dismiss();
                    sProgressDialog = null;
                    startSeriesSettingsActivityInternal(context, seriesRecordingId,
                            loadedPrograms == null ? Collections.EMPTY_LIST : loadedPrograms,
                            removeEmptySeriesSchedule, isWindowTranslucent, showViewScheduleOptionInDialog,
                            currentProgram);
                }
            }.setLoadCurrentProgram(true).setLoadDisallowedProgram(true).setLoadScheduledEpisode(true)
                    .setIgnoreChannelOption(true);
            sProgressDialog = ProgressDialog.show(context, null,
                    context.getString(R.string.dvr_series_progress_message_reading_programs), true, true,
                    new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            episodicProgramLoadTask.cancel(true);
                            sProgressDialog = null;
                        }
                    });
            episodicProgramLoadTask.execute();
        }
    }

    private static void startSeriesSettingsActivityInternal(Context context, long seriesRecordingId,
            @NonNull List<Program> programs, boolean removeEmptySeriesSchedule, boolean isWindowTranslucent,
            boolean showViewScheduleOptionInDialog, Program currentProgram) {
        SoftPreconditions.checkState(programs != null, TAG, "Start series settings activity but programs is null");
        Intent intent = new Intent(context, DvrSeriesSettingsActivity.class);
        intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId);
        BigArguments.reset();
        BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs);
        intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, removeEmptySeriesSchedule);
        intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent);
        intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
                showViewScheduleOptionInDialog);
        intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram);
        context.startActivity(intent);
    }

    /**
     * Shows "series recording scheduled" dialog activity.
     */
    public static void StartSeriesScheduledDialogActivity(Context context, SeriesRecording seriesRecording,
            boolean showViewScheduleOptionInDialog, List<Program> programs) {
        if (seriesRecording == null) {
            return;
        }
        Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class);
        intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, seriesRecording.getId());
        intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION, showViewScheduleOptionInDialog);
        BigArguments.reset();
        BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, programs);
        context.startActivity(intent);
    }

    /**
     * Shows the details activity for the DVR items. The type of DVR items may be
     * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}.
     */
    public static void startDetailsActivity(Activity activity, Object dvrItem, @Nullable ImageView imageView,
            boolean hideViewSchedule) {
        if (dvrItem == null) {
            return;
        }
        Intent intent = new Intent(activity, DvrDetailsActivity.class);
        long recordingId;
        int viewType;
        if (dvrItem instanceof ScheduledRecording) {
            ScheduledRecording schedule = (ScheduledRecording) dvrItem;
            recordingId = schedule.getId();
            if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
                viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW;
            } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
                viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW;
            } else {
                return;
            }
        } else if (dvrItem instanceof RecordedProgram) {
            recordingId = ((RecordedProgram) dvrItem).getId();
            viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW;
        } else if (dvrItem instanceof SeriesRecording) {
            recordingId = ((SeriesRecording) dvrItem).getId();
            viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW;
        } else {
            return;
        }
        intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId);
        intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType);
        intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule);
        Bundle bundle = null;
        if (imageView != null) {
            bundle = ActivityOptionsCompat
                    .makeSceneTransitionAnimation(activity, imageView, DvrDetailsActivity.SHARED_ELEMENT_NAME)
                    .toBundle();
        }
        activity.startActivity(intent, bundle);
    }

    /**
     * Shows the cancel all dialog for series schedules list.
     */
    public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity,
            SeriesRecording seriesRecording) {
        DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment = new DvrStopSeriesRecordingDialogFragment();
        Bundle arguments = new Bundle();
        arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, seriesRecording);
        dvrStopSeriesRecordingDialogFragment.setArguments(arguments);
        dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(),
                DvrStopSeriesRecordingDialogFragment.DIALOG_TAG);
    }

    /**
     * Shows the series deletion activity.
     */
    public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) {
        Intent intent = new Intent(context, DvrSeriesDeletionActivity.class);
        intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId);
        context.startActivity(intent);
    }

    public static void showAddScheduleToast(Context context, String title, long startTimeMs, long endTimeMs) {
        String msg = (startTimeMs > System.currentTimeMillis())
                ? context.getString(R.string.dvr_msg_program_scheduled, title)
                : context.getString(R.string.dvr_msg_current_program_scheduled, title,
                        Utils.toTimeString(endTimeMs, false));
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    }

    /**
     * Returns the styled schedule's title with its season and episode number.
     */
    public static CharSequence getStyledTitleWithEpisodeNumber(Context context, ScheduledRecording schedule,
            int episodeNumberStyleResId) {
        return getStyledTitleWithEpisodeNumber(context, schedule.getProgramTitle(), schedule.getSeasonNumber(),
                schedule.getEpisodeNumber(), episodeNumberStyleResId);
    }

    /**
     * Returns the styled program's title with its season and episode number.
     */
    public static CharSequence getStyledTitleWithEpisodeNumber(Context context, BaseProgram program,
            int episodeNumberStyleResId) {
        return getStyledTitleWithEpisodeNumber(context, program.getTitle(), program.getSeasonNumber(),
                program.getEpisodeNumber(), episodeNumberStyleResId);
    }

    @NonNull
    public static CharSequence getStyledTitleWithEpisodeNumber(Context context, String title, String seasonNumber,
            String episodeNumber, int episodeNumberStyleResId) {
        if (TextUtils.isEmpty(title)) {
            return "";
        }
        SpannableStringBuilder builder;
        if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) {
            builder = TextUtils.isEmpty(episodeNumber) ? new SpannableStringBuilder(title)
                    : new SpannableStringBuilder(Html.fromHtml(context.getString(
                            R.string.program_title_with_episode_number_no_season, title, episodeNumber)));
        } else {
            builder = new SpannableStringBuilder(Html.fromHtml(context
                    .getString(R.string.program_title_with_episode_number, title, seasonNumber, episodeNumber)));
        }
        Object[] spans = builder.getSpans(0, builder.length(), Object.class);
        if (spans.length > 0) {
            if (episodeNumberStyleResId != 0) {
                builder.setSpan(new TextAppearanceSpan(context, episodeNumberStyleResId),
                        builder.getSpanStart(spans[0]), builder.getSpanEnd(spans[0]),
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            builder.removeSpan(spans[0]);
        }
        return new SpannableString(builder);
    }
}