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