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