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