Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2016 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.graphics.drawable.Drawable;
     20 import android.media.tv.TvInputInfo;
     21 import android.os.Bundle;
     22 import android.support.annotation.NonNull;
     23 import android.support.annotation.Nullable;
     24 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
     25 import android.support.v17.leanback.widget.GuidedAction;
     26 import android.util.Log;
     27 import android.view.LayoutInflater;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 import com.android.tv.MainActivity;
     31 import com.android.tv.R;
     32 import com.android.tv.TvSingletons;
     33 import com.android.tv.common.SoftPreconditions;
     34 import com.android.tv.data.Program;
     35 import com.android.tv.data.api.Channel;
     36 import com.android.tv.dvr.data.ScheduledRecording;
     37 import com.android.tv.dvr.recorder.ConflictChecker;
     38 import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener;
     39 import com.android.tv.util.Utils;
     40 import java.util.ArrayList;
     41 import java.util.Collections;
     42 import java.util.HashSet;
     43 import java.util.List;
     44 
     45 public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
     46     private static final String TAG = "DvrConflictFragment";
     47     private static final boolean DEBUG = false;
     48 
     49     private static final int ACTION_DELETE_CONFLICT = 1;
     50     private static final int ACTION_CANCEL = 2;
     51     private static final int ACTION_VIEW_SCHEDULES = 3;
     52 
     53     // The program count which will be listed in the description. This is the number of the
     54     // program strings in R.plurals.dvr_program_conflict_dialog_description_many.
     55     private static final int LISTED_PROGRAM_COUNT = 2;
     56 
     57     protected List<ScheduledRecording> mConflicts;
     58 
     59     void setConflicts(List<ScheduledRecording> conflicts) {
     60         mConflicts = conflicts;
     61     }
     62 
     63     List<ScheduledRecording> getConflicts() {
     64         return mConflicts;
     65     }
     66 
     67     @Override
     68     public int onProvideTheme() {
     69         return R.style.Theme_TV_Dvr_Conflict_GuidedStep;
     70     }
     71 
     72     @Override
     73     public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
     74         actions.add(
     75                 new GuidedAction.Builder(getContext())
     76                         .clickAction(GuidedAction.ACTION_ID_OK)
     77                         .build());
     78         actions.add(
     79                 new GuidedAction.Builder(getContext())
     80                         .id(ACTION_VIEW_SCHEDULES)
     81                         .title(R.string.dvr_action_view_schedules)
     82                         .build());
     83     }
     84 
     85     @Override
     86     public void onTrackedGuidedActionClicked(GuidedAction action) {
     87         if (action.getId() == ACTION_VIEW_SCHEDULES) {
     88             DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict(
     89                     getContext(), getConflicts());
     90         }
     91         dismissDialog();
     92     }
     93 
     94     @Override
     95     public String getTrackerLabelForGuidedAction(GuidedAction action) {
     96         long actionId = getId();
     97         if (actionId == ACTION_VIEW_SCHEDULES) {
     98             return "view-schedules";
     99         } else {
    100             return super.getTrackerLabelForGuidedAction(action);
    101         }
    102     }
    103 
    104     String getConflictDescription() {
    105         List<String> titles = new ArrayList<>();
    106         HashSet<String> titleSet = new HashSet<>();
    107         for (ScheduledRecording schedule : getConflicts()) {
    108             String scheduleTitle = getScheduleTitle(schedule);
    109             if (scheduleTitle != null && !titleSet.contains(scheduleTitle)) {
    110                 titles.add(scheduleTitle);
    111                 titleSet.add(scheduleTitle);
    112             }
    113         }
    114         switch (titles.size()) {
    115             case 0:
    116                 Log.i(
    117                         TAG,
    118                         "Conflict has been resolved by any reason. Maybe input might have"
    119                                 + " been deleted.");
    120                 return null;
    121             case 1:
    122                 return getResources()
    123                         .getString(
    124                                 R.string.dvr_program_conflict_dialog_description_1, titles.get(0));
    125             case 2:
    126                 return getResources()
    127                         .getString(
    128                                 R.string.dvr_program_conflict_dialog_description_2,
    129                                 titles.get(0),
    130                                 titles.get(1));
    131             case 3:
    132                 return getResources()
    133                         .getString(
    134                                 R.string.dvr_program_conflict_dialog_description_3,
    135                                 titles.get(0),
    136                                 titles.get(1));
    137             default:
    138                 return getResources()
    139                         .getQuantityString(
    140                                 R.plurals.dvr_program_conflict_dialog_description_many,
    141                                 titles.size() - LISTED_PROGRAM_COUNT,
    142                                 titles.get(0),
    143                                 titles.get(1),
    144                                 titles.size() - LISTED_PROGRAM_COUNT);
    145         }
    146     }
    147 
    148     @Nullable
    149     private String getScheduleTitle(ScheduledRecording schedule) {
    150         if (schedule.getType() == ScheduledRecording.TYPE_TIMED) {
    151             Channel channel =
    152                     TvSingletons.getSingletons(getContext())
    153                             .getChannelDataManager()
    154                             .getChannel(schedule.getChannelId());
    155             if (channel != null) {
    156                 return channel.getDisplayName();
    157             } else {
    158                 return null;
    159             }
    160         } else {
    161             return schedule.getProgramTitle();
    162         }
    163     }
    164 
    165     /** A fragment to show the program conflict. */
    166     public static class DvrProgramConflictFragment extends DvrConflictFragment {
    167         private Program mProgram;
    168 
    169         @Override
    170         public View onCreateView(
    171                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    172             Bundle args = getArguments();
    173             if (args != null) {
    174                 mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
    175             }
    176             SoftPreconditions.checkArgument(mProgram != null);
    177             TvInputInfo input = Utils.getTvInputInfoForProgram(getContext(), mProgram);
    178             SoftPreconditions.checkNotNull(input);
    179             List<ScheduledRecording> conflicts = null;
    180             if (input != null) {
    181                 conflicts =
    182                         TvSingletons.getSingletons(getContext())
    183                                 .getDvrManager()
    184                                 .getConflictingSchedules(mProgram);
    185             }
    186             if (conflicts == null) {
    187                 conflicts = Collections.emptyList();
    188             }
    189             if (conflicts.isEmpty()) {
    190                 dismissDialog();
    191             }
    192             setConflicts(conflicts);
    193             return super.onCreateView(inflater, container, savedInstanceState);
    194         }
    195 
    196         @NonNull
    197         @Override
    198         public Guidance onCreateGuidance(Bundle savedInstanceState) {
    199             String title = getResources().getString(R.string.dvr_program_conflict_dialog_title);
    200             String descriptionPrefix =
    201                     getString(
    202                             R.string.dvr_program_conflict_dialog_description_prefix,
    203                             mProgram.getTitle());
    204             String description = getConflictDescription();
    205             if (description == null) {
    206                 dismissDialog();
    207             }
    208             Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
    209             return new Guidance(title, descriptionPrefix + " " + description, null, icon);
    210         }
    211 
    212         @Override
    213         public String getTrackerPrefix() {
    214             return "DvrProgramConflictFragment";
    215         }
    216     }
    217 
    218     /** A fragment to show the channel recording conflict. */
    219     public static class DvrChannelRecordConflictFragment extends DvrConflictFragment {
    220         private Channel mChannel;
    221         private long mStartTimeMs;
    222         private long mEndTimeMs;
    223 
    224         @Override
    225         public View onCreateView(
    226                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    227             Bundle args = getArguments();
    228             long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
    229             mChannel =
    230                     TvSingletons.getSingletons(getContext())
    231                             .getChannelDataManager()
    232                             .getChannel(channelId);
    233             SoftPreconditions.checkArgument(mChannel != null);
    234             TvInputInfo input = Utils.getTvInputInfoForChannelId(getContext(), mChannel.getId());
    235             SoftPreconditions.checkNotNull(input);
    236             List<ScheduledRecording> conflicts = null;
    237             if (input != null) {
    238                 mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS);
    239                 mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS);
    240                 conflicts =
    241                         TvSingletons.getSingletons(getContext())
    242                                 .getDvrManager()
    243                                 .getConflictingSchedules(
    244                                         mChannel.getId(), mStartTimeMs, mEndTimeMs);
    245             }
    246             if (conflicts == null) {
    247                 conflicts = Collections.emptyList();
    248             }
    249             if (conflicts.isEmpty()) {
    250                 dismissDialog();
    251             }
    252             setConflicts(conflicts);
    253             return super.onCreateView(inflater, container, savedInstanceState);
    254         }
    255 
    256         @NonNull
    257         @Override
    258         public Guidance onCreateGuidance(Bundle savedInstanceState) {
    259             String title = getResources().getString(R.string.dvr_channel_conflict_dialog_title);
    260             String descriptionPrefix =
    261                     getString(
    262                             R.string.dvr_channel_conflict_dialog_description_prefix,
    263                             mChannel.getDisplayName());
    264             String description = getConflictDescription();
    265             if (description == null) {
    266                 dismissDialog();
    267             }
    268             Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
    269             return new Guidance(title, descriptionPrefix + " " + description, null, icon);
    270         }
    271 
    272         @Override
    273         public String getTrackerPrefix() {
    274             return "DvrChannelRecordConflictFragment";
    275         }
    276     }
    277 
    278     /**
    279      * A fragment to show the channel watching conflict.
    280      *
    281      * <p>This fragment is automatically closed when there are no upcoming conflicts.
    282      */
    283     public static class DvrChannelWatchConflictFragment extends DvrConflictFragment
    284             implements OnUpcomingConflictChangeListener {
    285         private long mChannelId;
    286 
    287         @Override
    288         public View onCreateView(
    289                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    290             Bundle args = getArguments();
    291             if (args != null) {
    292                 mChannelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
    293             }
    294             SoftPreconditions.checkArgument(mChannelId != Channel.INVALID_ID);
    295             ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
    296             List<ScheduledRecording> conflicts = null;
    297             if (checker != null) {
    298                 checker.addOnUpcomingConflictChangeListener(this);
    299                 conflicts = checker.getUpcomingConflicts();
    300                 if (DEBUG) Log.d(TAG, "onCreateView: upcoming conflicts: " + conflicts);
    301                 if (conflicts.isEmpty()) {
    302                     dismissDialog();
    303                 }
    304             }
    305             if (conflicts == null) {
    306                 if (DEBUG) Log.d(TAG, "onCreateView: There's no conflict.");
    307                 conflicts = Collections.emptyList();
    308             }
    309             if (conflicts.isEmpty()) {
    310                 dismissDialog();
    311             }
    312             setConflicts(conflicts);
    313             return super.onCreateView(inflater, container, savedInstanceState);
    314         }
    315 
    316         @NonNull
    317         @Override
    318         public Guidance onCreateGuidance(Bundle savedInstanceState) {
    319             String title =
    320                     getResources().getString(R.string.dvr_epg_channel_watch_conflict_dialog_title);
    321             String description =
    322                     getResources()
    323                             .getString(R.string.dvr_epg_channel_watch_conflict_dialog_description);
    324             return new Guidance(title, description, null, null);
    325         }
    326 
    327         @Override
    328         public void onCreateActions(
    329                 @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
    330             actions.add(
    331                     new GuidedAction.Builder(getContext())
    332                             .id(ACTION_DELETE_CONFLICT)
    333                             .title(R.string.dvr_action_delete_schedule)
    334                             .build());
    335             actions.add(
    336                     new GuidedAction.Builder(getContext())
    337                             .id(ACTION_CANCEL)
    338                             .title(R.string.dvr_action_record_program)
    339                             .build());
    340         }
    341 
    342         @Override
    343         public void onTrackedGuidedActionClicked(GuidedAction action) {
    344             if (action.getId() == ACTION_CANCEL) {
    345                 ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
    346                 if (checker != null) {
    347                     checker.setCheckedConflictsForChannel(mChannelId, getConflicts());
    348                 }
    349             } else if (action.getId() == ACTION_DELETE_CONFLICT) {
    350                 for (ScheduledRecording schedule : mConflicts) {
    351                     if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
    352                         getDvrManager().stopRecording(schedule);
    353                     } else {
    354                         getDvrManager().removeScheduledRecording(schedule);
    355                     }
    356                 }
    357             }
    358             super.onGuidedActionClicked(action);
    359         }
    360 
    361         @Override
    362         public String getTrackerPrefix() {
    363             return "DvrChannelWatchConflictFragment";
    364         }
    365 
    366         @Override
    367         public String getTrackerLabelForGuidedAction(GuidedAction action) {
    368             long actionId = action.getId();
    369             if (actionId == ACTION_CANCEL) {
    370                 return "cancel";
    371             } else if (actionId == ACTION_DELETE_CONFLICT) {
    372                 return "delete";
    373             } else {
    374                 return super.getTrackerLabelForGuidedAction(action);
    375             }
    376         }
    377 
    378         @Override
    379         public void onDetach() {
    380             ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
    381             if (checker != null) {
    382                 checker.removeOnUpcomingConflictChangeListener(this);
    383             }
    384             super.onDetach();
    385         }
    386 
    387         @Override
    388         public void onUpcomingConflictChange() {
    389             ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
    390             if (checker == null || checker.getUpcomingConflicts().isEmpty()) {
    391                 if (DEBUG) Log.d(TAG, "onUpcomingConflictChange: There's no conflict.");
    392                 dismissDialog();
    393             }
    394         }
    395     }
    396 }
    397