165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/* 265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2015 The Android Open Source Project 365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License. 665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at 765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software 1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and 1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License. 1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.dvr; 1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.annotation.TargetApi; 2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context; 2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputInfo; 2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Build; 2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.MainThread; 2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.NonNull; 2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.VisibleForTesting; 2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.ArraySet; 27d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.util.LongSparseArray; 2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Range; 2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.ApplicationSingletons; 3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.TvApplication; 3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.SoftPreconditions; 3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.Channel; 3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.ChannelDataManager; 3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.Program; 3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; 3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; 38d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.util.CompositeComparator; 3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.util.Utils; 4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.ArrayList; 4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Collections; 43d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Comparator; 4465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap; 4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Iterator; 4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List; 4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map; 4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Set; 49d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.concurrent.CopyOnWriteArraySet; 5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 5165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/** 5265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * A class to manage the schedules. 5365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@TargetApi(Build.VERSION_CODES.N) 5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@MainThread 5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class DvrScheduleManager { 5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final String TAG = "DvrScheduleManager"; 5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * The default priority of scheduled recording. 6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; 6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 6465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * The default priority of series recording. 6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 6665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static final long DEFAULT_SERIES_PRIORITY = DEFAULT_PRIORITY >> 1; 6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // The new priority will have the offset from the existing one. 6865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long PRIORITY_OFFSET = 1024; 6965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 70d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final Comparator<ScheduledRecording> RESULT_COMPARATOR = 71d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new CompositeComparator<>( 72d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.PRIORITY_COMPARATOR.reversed(), 73d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.START_TIME_COMPARATOR, 74d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.ID_COMPARATOR.reversed()); 75d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 76d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The candidate comparator should be the consistent with 77d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // InputTaskScheduler#CANDIDATE_COMPARATOR. 78d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final Comparator<ScheduledRecording> CANDIDATE_COMPARATOR = 79d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new CompositeComparator<>( 80d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.PRIORITY_COMPARATOR, 81d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.END_TIME_COMPARATOR, 82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.ID_COMPARATOR); 83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Context mContext; 8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final DvrDataManagerImpl mDataManager; 8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final ChannelDataManager mChannelDataManager; 8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>(); 89d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The inner map is a hash map from scheduled recording to its conflicting status, i.e., 90d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // the boolean value true denotes the schedule is just partially conflicting, which means 91d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // although there's conflictit, it might still be recorded partially. 92d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final Map<String, Map<ScheduledRecording, Boolean>> mInputConflictInfoMap = 93d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new HashMap<>(); 9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean mInitialized; 9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 97d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final Set<OnInitializeListener> mOnInitializeListeners = new CopyOnWriteArraySet<>(); 9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>(); 9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Set<OnConflictStateChangeListener> mOnConflictStateChangeListeners = 10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko new ArraySet<>(); 10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public DvrScheduleManager(Context context) { 10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext = context; 10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ApplicationSingletons appSingletons = TvApplication.getSingletons(context); 10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager = (DvrDataManagerImpl) appSingletons.getDvrDataManager(); 10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager = appSingletons.getChannelDataManager(); 10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) { 10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.addDvrScheduleLoadFinishedListener( 11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko new OnDvrScheduleLoadFinishedListener() { 11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onDvrScheduleLoadFinished() { 11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.removeDvrScheduleLoadFinishedListener(this); 11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannelDataManager.isDbLoadFinished() && !mInitialized) { 11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 11765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }); 12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecordingListener scheduledRecordingListener = new ScheduledRecordingListener() { 12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { 12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized) { 12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!schedule.isNotStarted() && !schedule.isInProgress()) { 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 131d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils 132d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .getTvInputInfoForInputId(mContext, schedule.getInputId()); 133d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!SoftPreconditions.checkArgument(input != null, TAG, 134d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko "Input was removed for : " + schedule)) { 13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Input removed. 136d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 137d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko String inputId = input.getId(); 14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null) { 14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(); 14465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.put(inputId, schedules); 14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.add(schedule); 14765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onSchedulesChanged(); 14965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyScheduledRecordingAdded(scheduledRecordings); 15065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { 15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized) { 15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 15765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils 159d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .getTvInputInfoForInputId(mContext, schedule.getInputId()); 16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null) { 16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Input removed. 162d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 163d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko String inputId = input.getId(); 16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules != null) { 16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.remove(schedule); 17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules.isEmpty()) { 17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.remove(inputId); 17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 174d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflictInfo = 175d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.get(inputId); 176d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (conflictInfo != null) { 177d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflictInfo.remove(schedule); 178d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (conflictInfo.isEmpty()) { 179d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(inputId); 180d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 181d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onSchedulesChanged(); 18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyScheduledRecordingRemoved(scheduledRecordings); 18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onScheduledRecordingStatusChanged( 18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording... scheduledRecordings) { 19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized) { 19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils 195d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .getTvInputInfoForInputId(mContext, schedule.getInputId()); 196d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!SoftPreconditions.checkArgument(input != null, TAG, 197d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko "Input was removed for : " + schedule)) { 19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Input removed. 199d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 200d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko String inputId = input.getId(); 20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null) { 20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(); 20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.put(inputId, schedules); 20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Compare ID because ScheduledRecording.equals() doesn't work if the state 21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // is changed. 211d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Iterator<ScheduledRecording> i = schedules.iterator(); i.hasNext(); ) { 21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (i.next().getId() == schedule.getId()) { 21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko i.remove(); 21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.isNotStarted() || schedule.isInProgress()) { 21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.add(schedule); 21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules.isEmpty()) { 22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.remove(inputId); 22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 223d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Update conflict list as well 224d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflictInfo = 225d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.get(inputId); 226d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (conflictInfo != null) { 227d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Compare ID because ScheduledRecording.equals() doesn't work if the state 228d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // is changed. 229d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording oldSchedule = null; 230d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording s : conflictInfo.keySet()) { 231d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (s.getId() == schedule.getId()) { 232d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko oldSchedule = s; 233d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko break; 234d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 235d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 236d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (oldSchedule != null) { 237d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflictInfo.put(schedule, conflictInfo.get(oldSchedule)); 238d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflictInfo.remove(oldSchedule); 239d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 240d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 24165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onSchedulesChanged(); 24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyScheduledRecordingStatusChanged(scheduledRecordings); 24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }; 24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.addScheduledRecordingListener(scheduledRecordingListener); 24765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ChannelDataManager.Listener channelDataManagerListener = new ChannelDataManager.Listener() { 24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onLoadFinished() { 25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) { 25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 25465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelListUpdated() { 25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mDataManager.isDvrScheduleLoadFinished()) { 25865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChannelBrowsableChanged() { 26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }; 26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.addListener(channelDataManagerListener); 26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the started recordings for the given input. 27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private List<ScheduledRecording> getStartedRecordings(String inputId) { 27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> result = new ArrayList<>(); 27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules != null) { 27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 28065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { 28165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.add(schedule); 28265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 28665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 28865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void buildData() { 28965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.clear(); 29065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : mDataManager.getAllScheduledRecordings()) { 29165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!schedule.isNotStarted() && !schedule.isInProgress()) { 29265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Channel channel = mChannelDataManager.getChannel(schedule.getChannelId()); 29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (channel != null) { 29665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko String inputId = channel.getInputId(); 29765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Do not check whether the input is valid or not. The input might be temporarily 29865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // invalid. 29965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 30065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null) { 30165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(); 30265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.put(inputId, schedules); 30365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 30465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.add(schedule); 30565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 30665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 307d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized) { 308d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInitialized = true; 309d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko notifyInitialize(); 310d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 31165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onSchedulesChanged(); 31265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 31365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 31465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void onSchedulesChanged() { 315d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // TODO: notify conflict state change when some conflicting recording becomes partially 316d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // conflicting, vice versa. 31765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> addedConflicts = new ArrayList<>(); 31865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> removedConflicts = new ArrayList<>(); 31965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (String inputId : mInputScheduleMap.keySet()) { 320d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> oldConflictsInfo = mInputConflictInfoMap.get(inputId); 32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Map<Long, ScheduledRecording> oldConflictMap = new HashMap<>(); 322d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (oldConflictsInfo != null) { 323d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : oldConflictsInfo.keySet()) { 32465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko oldConflictMap.put(r.getId(), r); 32565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 327d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflictInfo = getConflictingSchedulesInfo(inputId); 328d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (conflictInfo.isEmpty()) { 329d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(inputId); 330d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 331d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.put(inputId, conflictInfo); 332d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> conflicts = new ArrayList<>(conflictInfo.keySet()); 333d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : conflicts) { 334d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (oldConflictMap.remove(r.getId()) == null) { 335d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko addedConflicts.add(r); 336d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko removedConflicts.addAll(oldConflictMap.values()); 34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!removedConflicts.isEmpty()) { 34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyConflictStateChange(false, ScheduledRecording.toArray(removedConflicts)); 34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!addedConflicts.isEmpty()) { 34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyConflictStateChange(true, ScheduledRecording.toArray(addedConflicts)); 34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns {@code true} if this class has been initialized. 35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean isInitialized() { 35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return mInitialized; 35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Adds a {@link ScheduledRecordingListener}. 35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void addScheduledRecordingListener(ScheduledRecordingListener listener) { 36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mScheduledRecordingListeners.add(listener); 36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Removes a {@link ScheduledRecordingListener}. 36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) { 36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mScheduledRecordingListeners.remove(listener); 36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. 37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { 37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingAdded(scheduledRecordings); 37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. 38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { 38365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 38465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingRemoved(scheduledRecordings); 38565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 38665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 38765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener. 39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { 39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingStatusChanged(scheduledRecordings); 39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 39765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 398d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Adds a {@link OnInitializeListener}. 399d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 400d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public final void addOnInitializeListener(OnInitializeListener listener) { 401d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mOnInitializeListeners.add(listener); 402d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 403d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 404d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 405d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Removes a {@link OnInitializeListener}. 406d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 407d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public final void removeOnInitializeListener(OnInitializeListener listener) { 408d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mOnInitializeListeners.remove(listener); 409d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 410d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 411d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 412d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Calls {@link OnInitializeListener#onInitialize} for each listener. 413d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 414d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private void notifyInitialize() { 415d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (OnInitializeListener l : mOnInitializeListeners) { 416d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko l.onInitialize(); 417d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 418d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 419d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 420d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Adds a {@link OnConflictStateChangeListener}. 42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 42365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void addOnConflictStateChangeListener(OnConflictStateChangeListener listener) { 42465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mOnConflictStateChangeListeners.add(listener); 42565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 42665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Removes a {@link OnConflictStateChangeListener}. 42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 43065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void removeOnConflictStateChangeListener(OnConflictStateChangeListener listener) { 43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mOnConflictStateChangeListeners.remove(listener); 43265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 43365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener. 43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyConflictStateChange(boolean conflict, 43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording... scheduledRecordings) { 43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (OnConflictStateChangeListener l : mOnConflictStateChangeListeners) { 44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onConflictStateChange(conflict, scheduledRecordings); 44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for the program if it is recorded. 44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p> 44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * The recording will have the higher priority than the existing ones. 44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 44965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public long suggestNewPriority() { 45065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 45165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_PRIORITY; 45265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 45365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return suggestHighestPriority(); 45465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 45565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 45665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long suggestHighestPriority() { 45765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long highestPriority = DEFAULT_PRIORITY - PRIORITY_OFFSET; 45865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : mDataManager.getAllScheduledRecordings()) { 45965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getPriority() > highestPriority) { 46065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko highestPriority = schedule.getPriority(); 46165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 46265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 46365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return highestPriority + PRIORITY_OFFSET; 46465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 46565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 46665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 467d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Suggests the higher priority than the schedules which overlap with {@code schedule}. 468d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 469d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public long suggestHighestPriority(ScheduledRecording schedule) { 470d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(schedule.getInputId()); 471d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedules == null) { 472d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DEFAULT_PRIORITY; 473d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 474d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long highestPriority = Long.MIN_VALUE; 475d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : schedules) { 476d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!r.equals(schedule) && r.isOverLapping(schedule) 477d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko && r.getPriority() > highestPriority) { 478d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko highestPriority = r.getPriority(); 479d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 480d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 481d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (highestPriority == Long.MIN_VALUE || highestPriority < schedule.getPriority()) { 482d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return schedule.getPriority(); 483d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 484d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return highestPriority + PRIORITY_OFFSET; 485d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 486d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 487d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 488d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Suggests the higher priority than the schedules which overlap with {@code schedule}. 489d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 490d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public long suggestHighestPriority(String inputId, Range<Long> peroid, long basePriority) { 491d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 492d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedules == null) { 493d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DEFAULT_PRIORITY; 494d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 495d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long highestPriority = Long.MIN_VALUE; 496d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : schedules) { 497d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (r.isOverLapping(peroid) && r.getPriority() > highestPriority) { 498d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko highestPriority = r.getPriority(); 499d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 500d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 501d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (highestPriority == Long.MIN_VALUE || highestPriority < basePriority) { 502d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return basePriority; 503d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 504d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return highestPriority + PRIORITY_OFFSET; 505d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 506d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 507d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 50865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for a series recording. 50965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p> 51065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * The recording will have the higher priority than the existing series. 51165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 51265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public long suggestNewSeriesPriority() { 51365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 51465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_SERIES_PRIORITY; 51565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 51665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return suggestHighestSeriesPriority(); 51765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 51865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 51965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 52065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for a series recording by order of series recording priority. 52165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 52265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Higher order will have higher priority. 52365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 52465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static long suggestSeriesPriority(int order) { 52565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_SERIES_PRIORITY + order * PRIORITY_OFFSET; 52665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long suggestHighestSeriesPriority() { 52965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long highestPriority = DEFAULT_SERIES_PRIORITY - PRIORITY_OFFSET; 53065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (SeriesRecording schedule : mDataManager.getSeriesRecordings()) { 53165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getPriority() > highestPriority) { 53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko highestPriority = schedule.getPriority(); 53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return highestPriority + PRIORITY_OFFSET; 53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 539d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Returns a sorted list of all scheduled recordings that will not be recorded if 540d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * this program is going to be recorded, with their priorities in decending order. 54165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p> 542d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * An empty list means there is no conflicts. If there is conflict, a priority higher than 543d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * the first recording in the returned list should be assigned to the new schedule of this 544d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * program to guarantee the program would be completely recorded. 54565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 54665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedules(Program program) { 54765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 54865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(Program.isValid(program), TAG, 54965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko "Program is invalid: " + program); 55065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState( 55165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), TAG, 55265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko "Program duration is empty: " + program); 55365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || !Program.isValid(program) 55465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || program.getStartTimeUtcMillis() >= program.getEndTimeUtcMillis()) { 55565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 55665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 55765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForProgram(mContext, program); 55865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 55965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 56065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 56165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(input, Collections.singletonList( 56265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording.builder(input.getId(), program) 56365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setPriority(suggestHighestPriority()) 56465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .build())); 56565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 56665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 56765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 568d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Returns list of all conflicting scheduled recordings with schedules belonging to {@code 569d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * seriesRecording} 570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * recording. 571d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * <p> 572d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Any empty list means there is no conflicts. 573d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 574d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public List<ScheduledRecording> getConflictingSchedules(SeriesRecording seriesRecording) { 575d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 576d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(seriesRecording != null, TAG, "series recording is null"); 577d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized || seriesRecording == null) { 578d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyList(); 579d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 580d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, seriesRecording.getInputId()); 581d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 582d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyList(); 583d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 584d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedulesForSeries = mDataManager.getScheduledRecordings( 585d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecording.getId()); 586d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedules(input, schedulesForSeries); 587d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 588d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 589d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 590d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Returns a sorted list of all scheduled recordings that will not be recorded if 591d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * this channel is going to be recorded, with their priority in decending order. 59265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p> 593d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * An empty list means there is no conflicts. If there is conflict, a priority higher than 594d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * the first recording in the returned list should be assigned to the new schedule of this 595d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * channel to guarantee the channel would be completely recorded in the designated time range. 59665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 59765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedules(long channelId, long startTimeMs, 59865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long endTimeMs) { 59965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 60065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 60165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(startTimeMs < endTimeMs, TAG, "Recording duration is empty."); 60265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || startTimeMs >= endTimeMs) { 60365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 60465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 60565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 60665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 60765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 60865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 60965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(input, Collections.singletonList( 61065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs) 61165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setPriority(suggestHighestPriority()) 61265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .build())); 61365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 61465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 61565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 61665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns all the scheduled recordings that conflicts and will not be recorded or clipped for 61765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * the given input. 61865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 61965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @NonNull 620d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(String inputId) { 62165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 62265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId); 62365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId); 62465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || input == null) { 625d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyMap(); 62665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 62765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId()); 62865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null || schedules.isEmpty()) { 629d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyMap(); 63065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 631d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedulesInfo(schedules, input.getTunerCount()); 63265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 63365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 63465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 63565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Checks if the schedule is conflicting. 63665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 63765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p>Note that the {@code schedule} should be the existing one. If not, this returns 63865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * {@code false}. 63965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 64065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean isConflicting(ScheduledRecording schedule) { 64165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 642d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 643d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : " 644d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko + schedule.getChannelId()); 645d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized || input == null) { 646d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return false; 647d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 648d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId()); 649d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return conflicts != null && conflicts.containsKey(schedule); 650d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 651d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 652d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 653d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Checks if the schedule is partially conflicting, i.e., part of the scheduled program might be 654d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * recorded even if the priority of the schedule is not raised. 655d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * <p> 656d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * If the given schedule is not conflicting or is totally conflicting, i.e., cannot be recorded 657d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * at all, this method returns {@code false} in both cases. 658d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 659d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public boolean isPartiallyConflicting(@NonNull ScheduledRecording schedule) { 660d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 661d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 66265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : " 66365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko + schedule.getChannelId()); 66465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || input == null) { 66565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 66665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 667d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId()); 668d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return conflicts != null && conflicts.getOrDefault(schedule, false); 66965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 67065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 67165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 67265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns priority ordered list of all scheduled recordings that will not be recorded if 67365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * this channel is tuned to. 67465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 67565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedulesForTune(long channelId) { 67665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 67765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 67865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 67965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: " 68065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko + channelId); 68165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { 68265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 68365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 68465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedulesForTune(input.getId(), channelId, System.currentTimeMillis(), 68565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko suggestHighestPriority(), getStartedRecordings(input.getId()), 68665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko input.getTunerCount()); 68765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 68865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 68965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 69065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static List<ScheduledRecording> getConflictingSchedulesForTune(String inputId, 69165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long channelId, long currentTimeMs, long newPriority, 69265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> startedRecordings, int tunerCount) { 69365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko boolean channelFound = false; 69465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : startedRecordings) { 69565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getChannelId() == channelId) { 69665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelFound = true; 69765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 69865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 69965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 70065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules; 70165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!channelFound) { 70265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // The current channel is not being recorded. 70365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(startedRecordings); 70465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.add(ScheduledRecording 70565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .builder(inputId, channelId, currentTimeMs, currentTimeMs + 1) 70665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setPriority(newPriority) 70765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .build()); 70865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 70965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = startedRecordings; 71065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 71165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(schedules, tunerCount); 71265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 71365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 71465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 71565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns priority ordered list of all scheduled recordings that will not be recorded if 71665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * the user keeps watching this channel. 71765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p> 71865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Note that if the user keeps watching the channel, the channel can be recorded. 71965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 72065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedulesForWatching(long channelId) { 72165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 72265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 72365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 72465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: " 72565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko + channelId); 72665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { 72765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 72865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 72965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId()); 73065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null || schedules.isEmpty()) { 73165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 73265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 73365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedulesForWatching(input.getId(), channelId, 73465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko System.currentTimeMillis(), suggestNewPriority(), schedules, input.getTunerCount()); 73565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 73665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 73765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private List<ScheduledRecording> getConflictingSchedules(TvInputInfo input, 73865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesToAdd) { 73965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkNotNull(input); 74065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 74165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 74265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 74365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> currentSchedules = mInputScheduleMap.get(input.getId()); 74465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (currentSchedules == null || currentSchedules.isEmpty()) { 74565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 74665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 74765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(schedulesToAdd, currentSchedules, input.getTunerCount()); 74865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 74965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 75065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 75165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko static List<ScheduledRecording> getConflictingSchedulesForWatching(String inputId, 75265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long channelId, long currentTimeMs, long newPriority, 75365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @NonNull List<ScheduledRecording> schedules, int tunerCount) { 75465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules); 75565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesSameChannel = new ArrayList<>(); 75665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 75765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getChannelId() == channelId) { 75865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesSameChannel.add(schedule); 75965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesToCheck.remove(schedule); 76065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 76165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 76265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Assume that the user will watch the current channel forever. 76365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesToCheck.add(ScheduledRecording 76465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE) 76565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setPriority(newPriority) 76665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .build()); 76765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> result = new ArrayList<>(); 76865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.addAll(getConflictingSchedules(schedulesSameChannel, 1)); 76965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.addAll(getConflictingSchedules(schedulesToCheck, tunerCount)); 770d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Collections.sort(result, RESULT_COMPARATOR); 77165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 77265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 77365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 77465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 77565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko static List<ScheduledRecording> getConflictingSchedules(List<ScheduledRecording> schedulesToAdd, 77665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> currentSchedules, int tunerCount) { 77765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesToCheck = new ArrayList<>(currentSchedules); 77865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // When the duplicate schedule is to be added, remove the current duplicate recording. 77965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (Iterator<ScheduledRecording> iter = schedulesToCheck.iterator(); iter.hasNext(); ) { 78065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording schedule = iter.next(); 78165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording toAdd : schedulesToAdd) { 78265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getType() == ScheduledRecording.TYPE_PROGRAM) { 78365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (toAdd.getProgramId() == schedule.getProgramId()) { 78465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko iter.remove(); 78565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 78665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 78765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 78865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (toAdd.getChannelId() == schedule.getChannelId() 78965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko && toAdd.getStartTimeMs() == schedule.getStartTimeMs() 79065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko && toAdd.getEndTimeMs() == schedule.getEndTimeMs()) { 79165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko iter.remove(); 79265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 79365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 79465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 79565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 79665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 79765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesToCheck.addAll(schedulesToAdd); 79865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<Range<Long>> ranges = new ArrayList<>(); 79965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedulesToAdd) { 80065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ranges.add(new Range<>(schedule.getStartTimeMs(), schedule.getEndTimeMs())); 80165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 80265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(schedulesToCheck, tunerCount, ranges); 80365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 80465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 80565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 80665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns all conflicting scheduled recordings for the given schedules and count of tuner. 80765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 80865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static List<ScheduledRecording> getConflictingSchedules( 80965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules, int tunerCount) { 810d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedules(schedules, tunerCount, null); 81165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 81265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 81365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 814d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko static List<ScheduledRecording> getConflictingSchedules( 815d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) { 816d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> result = new ArrayList<>( 817d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko getConflictingSchedulesInfo(schedules, tunerCount, periods).keySet()); 818d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Collections.sort(result, RESULT_COMPARATOR); 819d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return result; 820d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 821d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 822d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @VisibleForTesting 823d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo( 824d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules, int tunerCount) { 825d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedulesInfo(schedules, tunerCount, null); 826d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 827d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 828d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 829d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * This is the core method to calculate all the conflicting schedules (in given periods). 830d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * <p> 831d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Note that this method will ignore duplicated schedules with a same hash code. (Please refer 832d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * to {@link ScheduledRecording#hashCode}.) 833d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * 834d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * @return A {@link HashMap} from {@link ScheduledRecording} to {@link Boolean}. The boolean 835d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * value denotes if the scheduled recording is partially conflicting, i.e., is possible 836d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * to be partially recorded under the given schedules and tuner count {@code true}, 837d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * or not {@code false}. 838d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 839d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo( 840d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) { 841d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules); 842d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Sort by the same order as that in InputTaskScheduler. 843d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Collections.sort(schedulesToCheck, InputTaskScheduler.getRecordingOrderComparator()); 844d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> recordings = new ArrayList<>(); 845d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, Boolean> conflicts = new HashMap<>(); 846d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Map<ScheduledRecording, ScheduledRecording> modified2OriginalSchedules = new HashMap<>(); 847d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Simulate InputTaskScheduler. 848d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko while (!schedulesToCheck.isEmpty()) { 849d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording schedule = schedulesToCheck.remove(0); 850d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko removeFinishedRecordings(recordings, schedule.getStartTimeMs()); 851d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (recordings.size() < tunerCount) { 852d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko recordings.add(schedule); 853d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (modified2OriginalSchedules.containsKey(schedule)) { 854d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Schedule has been modified, which means it's already conflicted. 855d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Modify its state to partially conflicted. 856d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflicts.put(modified2OriginalSchedules.get(schedule), true); 85765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 858d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 859d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording candidate = findReplaceableRecording(recordings, schedule); 860d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (candidate != null) { 861d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!modified2OriginalSchedules.containsKey(candidate)) { 862d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflicts.put(candidate, true); 863d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 864d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko recordings.remove(candidate); 865d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko recordings.add(schedule); 866d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (modified2OriginalSchedules.containsKey(schedule)) { 867d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Schedule has been modified, which means it's already conflicted. 868d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Modify its state to partially conflicted. 869d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflicts.put(modified2OriginalSchedules.get(schedule), true); 870d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 871d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 872d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!modified2OriginalSchedules.containsKey(schedule)) { 873d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // if schedule has been modified, it's already conflicted. 874d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // No need to add it again. 875d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko conflicts.put(schedule, false); 876d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 877d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long earliestEndTime = getEarliestEndTime(recordings); 878d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (earliestEndTime < schedule.getEndTimeMs()) { 879d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The schedule can starts when other recording ends even though it's 880d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // clipped. 881d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording modifiedSchedule = ScheduledRecording.buildFrom(schedule) 882d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .setStartTimeMs(earliestEndTime).build(); 883d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording originalSchedule = 884d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko modified2OriginalSchedules.getOrDefault(schedule, schedule); 885d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko modified2OriginalSchedules.put(modifiedSchedule, originalSchedule); 886d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko int insertPosition = Collections.binarySearch(schedulesToCheck, 887d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko modifiedSchedule, 888d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); 889d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (insertPosition >= 0) { 890d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko schedulesToCheck.add(insertPosition, modifiedSchedule); 891d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 892d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko schedulesToCheck.add(-insertPosition - 1, modifiedSchedule); 893d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 894d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 89565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 89665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 897d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 898d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Returns only the schedules with the given range. 899d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (periods != null && !periods.isEmpty()) { 900d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Iterator<ScheduledRecording> iter = conflicts.keySet().iterator(); 901d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko iter.hasNext(); ) { 902d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko boolean overlapping = false; 90365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording schedule = iter.next(); 904d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Range<Long> period : periods) { 905d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedule.isOverLapping(period)) { 906d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko overlapping = true; 907d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko break; 908d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 909d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 910d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!overlapping) { 91165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko iter.remove(); 91265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 91365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 914d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 915d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return conflicts; 916d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 917d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 918d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static void removeFinishedRecordings(List<ScheduledRecording> recordings, 919d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long currentTimeMs) { 920d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Iterator<ScheduledRecording> iter = recordings.iterator(); iter.hasNext(); ) { 921d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (iter.next().getEndTimeMs() <= currentTimeMs) { 922d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko iter.remove(); 92365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 92465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 925d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 926d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 927d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 928d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * @see InputTaskScheduler#getReplacableTask 929d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 930d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static ScheduledRecording findReplaceableRecording(List<ScheduledRecording> recordings, 931d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording schedule) { 932d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Returns the recording with the following priority. 933d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 1. The recording with the lowest priority is returned. 934d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 2. If the priorities are the same, the recording which finishes early is returned. 935d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 3. If 1) and 2) are the same, the early created schedule is returned. 936d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording candidate = null; 937d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording recording : recordings) { 938d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedule.getPriority() > recording.getPriority()) { 939d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, recording) > 0) { 940d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko candidate = recording; 941d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 942d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 943d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 944d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return candidate; 945d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 946d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 947d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static long getEarliestEndTime(List<ScheduledRecording> recordings) { 948d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long earliest = Long.MAX_VALUE; 949d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording recording : recordings) { 950d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (earliest > recording.getEndTimeMs()) { 951d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko earliest = recording.getEndTimeMs(); 952d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 953d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 954d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return earliest; 955d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 956d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 957d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 958d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * A listener which is notified the initialization of schedule manager. 959d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 960d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public interface OnInitializeListener { 961d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 962d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Called when the schedule manager has been initialized. 963d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 964d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko void onInitialize(); 96565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 96665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 96765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 96865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * A listener which is notified the conflict state change of the schedules. 96965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 97065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public interface OnConflictStateChangeListener { 97165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 97265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Called when the conflicting schedules change. 97365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 97465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise 97565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * {@code false}. 97665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * @param schedules the schedules 97765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 97865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko void onConflictStateChange(boolean conflict, ScheduledRecording... schedules); 97965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 980d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko}