Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.dvr.ui;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.app.ProgressDialog;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.Intent;
     25 import android.media.tv.TvInputManager;
     26 import android.os.Build;
     27 import android.os.Bundle;
     28 import android.support.annotation.MainThread;
     29 import android.support.annotation.NonNull;
     30 import android.support.annotation.Nullable;
     31 import android.support.v4.app.ActivityOptionsCompat;
     32 import android.text.Html;
     33 import android.text.Spannable;
     34 import android.text.SpannableString;
     35 import android.text.SpannableStringBuilder;
     36 import android.text.TextUtils;
     37 import android.text.style.TextAppearanceSpan;
     38 import android.widget.ImageView;
     39 import android.widget.Toast;
     40 
     41 import com.android.tv.MainActivity;
     42 import com.android.tv.R;
     43 import com.android.tv.TvApplication;
     44 import com.android.tv.common.SoftPreconditions;
     45 import com.android.tv.data.BaseProgram;
     46 import com.android.tv.data.Channel;
     47 import com.android.tv.data.Program;
     48 import com.android.tv.dialog.HalfSizedDialogFragment;
     49 import com.android.tv.dvr.DvrManager;
     50 import com.android.tv.dvr.DvrStorageStatusManager;
     51 import com.android.tv.dvr.data.RecordedProgram;
     52 import com.android.tv.dvr.data.ScheduledRecording;
     53 import com.android.tv.dvr.data.SeriesRecording;
     54 import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
     55 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
     56 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
     57 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
     58 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
     59 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
     60 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
     61 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
     62 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
     63 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
     64 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
     65 import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
     66 import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
     67 import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
     68 import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
     69 import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
     70 import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
     71 import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
     72 import com.android.tv.util.ToastUtils;
     73 import com.android.tv.util.Utils;
     74 
     75 import java.util.ArrayList;
     76 import java.util.Collections;
     77 import java.util.List;
     78 import java.util.Set;
     79 
     80 /**
     81  * A helper class for DVR UI.
     82  */
     83 @MainThread
     84 @TargetApi(Build.VERSION_CODES.N)
     85 public class DvrUiHelper {
     86     private static final String TAG = "DvrUiHelper";
     87 
     88     private static ProgressDialog sProgressDialog = null;
     89 
     90     /**
     91      * Checks if the storage status is good for recording and shows error messages if needed.
     92      *
     93      * @param recordingRequestRunnable if the storage status is OK to record or users choose to
     94      *                                 perform the operation anyway, this Runnable will run.
     95      */
     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 }