/*
 * 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.Spanned;
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.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.api.BaseProgram;
import com.android.tv.data.api.Channel;
import com.android.tv.data.api.Program;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
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.DvrHalfSizedDialogFragment.DvrWriteStoragePermissionRationaleDialogFragment;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.dvr.ui.list.DvrHistoryActivity;
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.ui.DetailsActivity;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;

import com.google.common.collect.ImmutableList;
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 (CommonUtils.isBundledInput(inputId)) {
            switch (TvSingletons.getSingletons(activity)
                    .getRecordingStorageStatusManager()
                    .getDvrStorageStatus()) {
                case RecordingStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
                    showDvrSmallSizedStorageErrorDialog(activity);
                    return;
                case RecordingStorageStatusManager.STORAGE_STATUS_MISSING:
                    showDvrMissingStorageErrorDialog(activity);
                    return;
                case RecordingStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
                    showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable);
                    return;
                default: // fall out
            }
        }
        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.toParcelable());
        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.toParcelable());
        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.toParcelable());
        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.toParcelable());
        showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
    }

    /** Shows program information dialog. */
    public static void showWriteStoragePermissionRationaleDialog(Activity activity) {
        showDialogFragment(
                activity,
                new DvrWriteStoragePermissionRationaleDialogFragment(),
                new Bundle(),
                false,
                false);
    }

    /**
     * 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 = TvSingletons.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 =
                TvSingletons.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 startDvrHistoryActivity(Context context) {
        Intent intent = new Intent(context, DvrHistoryActivity.class);
        context.startActivity(intent);
    }

    /** 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,
            @Nullable Program currentProgram) {
        SeriesRecording series =
                TvSingletons.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) {
                            if (sProgressDialog != null) {
                                sProgressDialog.dismiss();
                                sProgressDialog = null;
                            }
                            startSeriesSettingsActivityInternal(
                                    context,
                                    seriesRecordingId,
                                    loadedPrograms == null
                                            ? ImmutableList.of()
                                            : 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();
        }
    }

    /**
     * Shows the episode recording settings activity.
     *
     * @param program Program to be recorded
     */
    public static void startRecordingSettingsActivity(
            Context context,
            Program program) {
        if (program != null) {
            Intent intent = new Intent(context, DvrRecordingSettingsActivity.class);
            intent.putExtra(DvrRecordingSettingsActivity.IS_WINDOW_TRANSLUCENT, true);
            intent.putExtra(DvrRecordingSettingsActivity.PROGRAM, program.toParcelable());
            context.startActivity(intent);
        }
    }

    private static void startSeriesSettingsActivityInternal(
            Context context,
            long seriesRecordingId,
            @NonNull List<Program> programs,
            boolean removeEmptySeriesSchedule,
            boolean isWindowTranslucent,
            boolean showViewScheduleOptionInDialog,
            @Nullable 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);
        if (currentProgram != null) {
            intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram.toParcelable());
        }
        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, DetailsActivity.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 = DetailsActivity.SCHEDULED_RECORDING_VIEW;
            } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
                viewType = DetailsActivity.CURRENT_RECORDING_VIEW;
            } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED
                    && schedule.getRecordedProgramId() != null) {
                recordingId = schedule.getRecordedProgramId();
                viewType = DetailsActivity.RECORDED_PROGRAM_VIEW;
            } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
                viewType = DetailsActivity.SCHEDULED_RECORDING_VIEW;
                hideViewSchedule = true;
            } else {
                return;
            }
        } else if (dvrItem instanceof RecordedProgram) {
            recordingId = ((RecordedProgram) dvrItem).getId();
            viewType = DetailsActivity.RECORDED_PROGRAM_VIEW;
        } else if (dvrItem instanceof SeriesRecording) {
            recordingId = ((SeriesRecording) dvrItem).getId();
            viewType = DetailsActivity.SERIES_RECORDING_VIEW;
        } else {
            return;
        }
        intent.putExtra(DetailsActivity.RECORDING_ID, recordingId);
        intent.putExtra(DetailsActivity.DETAILS_VIEW_TYPE, viewType);
        intent.putExtra(DetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule);
        Bundle bundle = null;
        if (imageView != null) {
            bundle =
                    ActivityOptionsCompat.makeSceneTransitionAnimation(
                                    activity, imageView, DetailsActivity.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")) {
            Spanned temp =
                    TextUtils.isEmpty(episodeNumber)
                            ? SpannableStringBuilder.valueOf(title)
                            : Html.fromHtml(
                                    context.getString(
                                            R.string.program_title_with_episode_number_no_season,
                                            title,
                                            episodeNumber));
            builder = SpannableStringBuilder.valueOf(temp);
        } else {
            builder =
                    SpannableStringBuilder.valueOf(
                            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);
    }
}
