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; 2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Range; 28944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.TvSingletons; 2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.SoftPreconditions; 3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.ChannelDataManager; 3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.Program; 320cc0713c1bf8027642987b750b80217569d2932aLive Channels Teamimport com.android.tv.data.api.Channel; 3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; 3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; 35633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording; 36633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.data.SeriesRecording; 3795961816a768da387f0b5523cf4363ace2044089Nick Chalkoimport com.android.tv.dvr.recorder.InputTaskScheduler; 3895961816a768da387f0b5523cf4363ace2044089Nick Chalkoimport com.android.tv.util.CompositeComparator; 3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.util.Utils; 4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.ArrayList; 4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Collections; 42d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Comparator; 4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap; 4465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Iterator; 4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List; 4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map; 4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Set; 48d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.concurrent.CopyOnWriteArraySet; 4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 5095961816a768da387f0b5523cf4363ace2044089Nick Chalko/** A class to manage the schedules. */ 5165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@TargetApi(Build.VERSION_CODES.N) 5265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@MainThread 53944779887775bd950cf1abf348d2df461593f6abLive Channels Team@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated 5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class DvrScheduleManager { 5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final String TAG = "DvrScheduleManager"; 5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 5795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** The default priority of scheduled recording. */ 5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1; 5995961816a768da387f0b5523cf4363ace2044089Nick Chalko /** The default priority of series recording. */ 6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static final long DEFAULT_SERIES_PRIORITY = DEFAULT_PRIORITY >> 1; 6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // The new priority will have the offset from the existing one. 6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final long PRIORITY_OFFSET = 1024; 6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 64d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final Comparator<ScheduledRecording> RESULT_COMPARATOR = 65d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new CompositeComparator<>( 66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.PRIORITY_COMPARATOR.reversed(), 67d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.START_TIME_COMPARATOR, 68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.ID_COMPARATOR.reversed()); 69d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 70d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The candidate comparator should be the consistent with 71d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // InputTaskScheduler#CANDIDATE_COMPARATOR. 72d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final Comparator<ScheduledRecording> CANDIDATE_COMPARATOR = 73d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new CompositeComparator<>( 74d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.PRIORITY_COMPARATOR, 75d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.END_TIME_COMPARATOR, 76d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.ID_COMPARATOR); 77d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Context mContext; 7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final DvrDataManagerImpl mDataManager; 8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final ChannelDataManager mChannelDataManager; 8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>(); 83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The inner map is a hash map from scheduled recording to its conflicting status, i.e., 84d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // the boolean value true denotes the schedule is just partially conflicting, which means 85633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko // although there's conflict, it might still be recorded partially. 86633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private final Map<String, Map<Long, ConflictInfo>> mInputConflictInfoMap = new HashMap<>(); 8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private boolean mInitialized; 8965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 90d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final Set<OnInitializeListener> mOnInitializeListeners = new CopyOnWriteArraySet<>(); 9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>(); 9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Set<OnConflictStateChangeListener> mOnConflictStateChangeListeners = 9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko new ArraySet<>(); 9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public DvrScheduleManager(Context context) { 9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext = context; 97944779887775bd950cf1abf348d2df461593f6abLive Channels Team TvSingletons tvSingletons = TvSingletons.getSingletons(context); 98944779887775bd950cf1abf348d2df461593f6abLive Channels Team mDataManager = (DvrDataManagerImpl) tvSingletons.getDvrDataManager(); 99944779887775bd950cf1abf348d2df461593f6abLive Channels Team mChannelDataManager = tvSingletons.getChannelDataManager(); 10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) { 10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.addDvrScheduleLoadFinishedListener( 10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko new OnDvrScheduleLoadFinishedListener() { 10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onDvrScheduleLoadFinished() { 10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.removeDvrScheduleLoadFinishedListener(this); 10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (mChannelDataManager.isDbLoadFinished() && !mInitialized) { 10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko buildData(); 11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }); 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11495961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecordingListener scheduledRecordingListener = 11595961816a768da387f0b5523cf4363ace2044089Nick Chalko new ScheduledRecordingListener() { 11695961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 11795961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onScheduledRecordingAdded( 11895961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording... scheduledRecordings) { 11995961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mInitialized) { 12095961816a768da387f0b5523cf4363ace2044089Nick Chalko return; 12195961816a768da387f0b5523cf4363ace2044089Nick Chalko } 12295961816a768da387f0b5523cf4363ace2044089Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 12395961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!schedule.isNotStarted() && !schedule.isInProgress()) { 12495961816a768da387f0b5523cf4363ace2044089Nick Chalko continue; 12595961816a768da387f0b5523cf4363ace2044089Nick Chalko } 12695961816a768da387f0b5523cf4363ace2044089Nick Chalko TvInputInfo input = 12795961816a768da387f0b5523cf4363ace2044089Nick Chalko Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 12895961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!SoftPreconditions.checkArgument( 129944779887775bd950cf1abf348d2df461593f6abLive Channels Team input != null, TAG, "Input was removed for : %s", schedule)) { 13095961816a768da387f0b5523cf4363ace2044089Nick Chalko // Input removed. 13195961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 13295961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 13395961816a768da387f0b5523cf4363ace2044089Nick Chalko continue; 13495961816a768da387f0b5523cf4363ace2044089Nick Chalko } 13595961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId = input.getId(); 13695961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 13795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedules == null) { 13895961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules = new ArrayList<>(); 13995961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.put(inputId, schedules); 14095961816a768da387f0b5523cf4363ace2044089Nick Chalko } 14195961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules.add(schedule); 14295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 14395961816a768da387f0b5523cf4363ace2044089Nick Chalko onSchedulesChanged(); 14495961816a768da387f0b5523cf4363ace2044089Nick Chalko notifyScheduledRecordingAdded(scheduledRecordings); 14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 14795961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 14895961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onScheduledRecordingRemoved( 14995961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording... scheduledRecordings) { 15095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mInitialized) { 15195961816a768da387f0b5523cf4363ace2044089Nick Chalko return; 15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 15395961816a768da387f0b5523cf4363ace2044089Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 15495961816a768da387f0b5523cf4363ace2044089Nick Chalko TvInputInfo input = 15595961816a768da387f0b5523cf4363ace2044089Nick Chalko Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 15695961816a768da387f0b5523cf4363ace2044089Nick Chalko if (input == null) { 15795961816a768da387f0b5523cf4363ace2044089Nick Chalko // Input removed. 15895961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 15995961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 16095961816a768da387f0b5523cf4363ace2044089Nick Chalko continue; 16195961816a768da387f0b5523cf4363ace2044089Nick Chalko } 16295961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId = input.getId(); 16395961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 16495961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedules != null) { 16595961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules.remove(schedule); 16695961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedules.isEmpty()) { 16795961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.remove(inputId); 16895961816a768da387f0b5523cf4363ace2044089Nick Chalko } 16995961816a768da387f0b5523cf4363ace2044089Nick Chalko } 17095961816a768da387f0b5523cf4363ace2044089Nick Chalko Map<Long, ConflictInfo> conflictInfo = 17195961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.get(inputId); 17295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (conflictInfo != null) { 17395961816a768da387f0b5523cf4363ace2044089Nick Chalko conflictInfo.remove(schedule.getId()); 17495961816a768da387f0b5523cf4363ace2044089Nick Chalko if (conflictInfo.isEmpty()) { 17595961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.remove(inputId); 17695961816a768da387f0b5523cf4363ace2044089Nick Chalko } 17795961816a768da387f0b5523cf4363ace2044089Nick Chalko } 178d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 17995961816a768da387f0b5523cf4363ace2044089Nick Chalko onSchedulesChanged(); 18095961816a768da387f0b5523cf4363ace2044089Nick Chalko notifyScheduledRecordingRemoved(scheduledRecordings); 181d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 18395961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 18495961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onScheduledRecordingStatusChanged( 18595961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording... scheduledRecordings) { 18695961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mInitialized) { 18795961816a768da387f0b5523cf4363ace2044089Nick Chalko return; 18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 18995961816a768da387f0b5523cf4363ace2044089Nick Chalko for (ScheduledRecording schedule : scheduledRecordings) { 19095961816a768da387f0b5523cf4363ace2044089Nick Chalko TvInputInfo input = 19195961816a768da387f0b5523cf4363ace2044089Nick Chalko Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 19295961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!SoftPreconditions.checkArgument( 193944779887775bd950cf1abf348d2df461593f6abLive Channels Team input != null, TAG, "Input was removed for : %s", schedule)) { 19495961816a768da387f0b5523cf4363ace2044089Nick Chalko // Input removed. 19595961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.remove(schedule.getInputId()); 19695961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.remove(schedule.getInputId()); 19795961816a768da387f0b5523cf4363ace2044089Nick Chalko continue; 19895961816a768da387f0b5523cf4363ace2044089Nick Chalko } 19995961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId = input.getId(); 20095961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 20195961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedules == null) { 20295961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules = new ArrayList<>(); 20395961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.put(inputId, schedules); 20495961816a768da387f0b5523cf4363ace2044089Nick Chalko } 20595961816a768da387f0b5523cf4363ace2044089Nick Chalko // Compare ID because ScheduledRecording.equals() doesn't work if the 20695961816a768da387f0b5523cf4363ace2044089Nick Chalko // state 20795961816a768da387f0b5523cf4363ace2044089Nick Chalko // is changed. 20895961816a768da387f0b5523cf4363ace2044089Nick Chalko for (Iterator<ScheduledRecording> i = schedules.iterator(); 20995961816a768da387f0b5523cf4363ace2044089Nick Chalko i.hasNext(); ) { 21095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (i.next().getId() == schedule.getId()) { 21195961816a768da387f0b5523cf4363ace2044089Nick Chalko i.remove(); 21295961816a768da387f0b5523cf4363ace2044089Nick Chalko break; 21395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 21495961816a768da387f0b5523cf4363ace2044089Nick Chalko } 21595961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedule.isNotStarted() || schedule.isInProgress()) { 21695961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules.add(schedule); 21795961816a768da387f0b5523cf4363ace2044089Nick Chalko } 21895961816a768da387f0b5523cf4363ace2044089Nick Chalko if (schedules.isEmpty()) { 21995961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputScheduleMap.remove(inputId); 22095961816a768da387f0b5523cf4363ace2044089Nick Chalko } 22195961816a768da387f0b5523cf4363ace2044089Nick Chalko // Update conflict list as well 22295961816a768da387f0b5523cf4363ace2044089Nick Chalko Map<Long, ConflictInfo> conflictInfo = 22395961816a768da387f0b5523cf4363ace2044089Nick Chalko mInputConflictInfoMap.get(inputId); 22495961816a768da387f0b5523cf4363ace2044089Nick Chalko if (conflictInfo != null) { 22595961816a768da387f0b5523cf4363ace2044089Nick Chalko ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId()); 22695961816a768da387f0b5523cf4363ace2044089Nick Chalko if (oldConflictInfo != null) { 22795961816a768da387f0b5523cf4363ace2044089Nick Chalko oldConflictInfo.schedule = schedule; 22895961816a768da387f0b5523cf4363ace2044089Nick Chalko } 22995961816a768da387f0b5523cf4363ace2044089Nick Chalko } 230d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 23195961816a768da387f0b5523cf4363ace2044089Nick Chalko onSchedulesChanged(); 23295961816a768da387f0b5523cf4363ace2044089Nick Chalko notifyScheduledRecordingStatusChanged(scheduledRecordings); 233d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 23495961816a768da387f0b5523cf4363ace2044089Nick Chalko }; 23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.addScheduledRecordingListener(scheduledRecordingListener); 23695961816a768da387f0b5523cf4363ace2044089Nick Chalko ChannelDataManager.Listener channelDataManagerListener = 23795961816a768da387f0b5523cf4363ace2044089Nick Chalko new ChannelDataManager.Listener() { 23895961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 23995961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onLoadFinished() { 24095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) { 24195961816a768da387f0b5523cf4363ace2044089Nick Chalko buildData(); 24295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 24395961816a768da387f0b5523cf4363ace2044089Nick Chalko } 24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 24595961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 24695961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onChannelListUpdated() { 24795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (mDataManager.isDvrScheduleLoadFinished()) { 24895961816a768da387f0b5523cf4363ace2044089Nick Chalko buildData(); 24995961816a768da387f0b5523cf4363ace2044089Nick Chalko } 25095961816a768da387f0b5523cf4363ace2044089Nick Chalko } 25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 25295961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 25395961816a768da387f0b5523cf4363ace2044089Nick Chalko public void onChannelBrowsableChanged() {} 25495961816a768da387f0b5523cf4363ace2044089Nick Chalko }; 25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mChannelDataManager.addListener(channelDataManagerListener); 25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 25895961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Returns the started recordings for the given input. */ 25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private List<ScheduledRecording> getStartedRecordings(String inputId) { 26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> result = new ArrayList<>(); 26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules != null) { 26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) { 26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.add(schedule); 26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void buildData() { 27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.clear(); 27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : mDataManager.getAllScheduledRecordings()) { 27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!schedule.isNotStarted() && !schedule.isInProgress()) { 27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko continue; 28065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 28165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Channel channel = mChannelDataManager.getChannel(schedule.getChannelId()); 28265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (channel != null) { 28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko String inputId = channel.getInputId(); 28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Do not check whether the input is valid or not. The input might be temporarily 28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // invalid. 28665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 28765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null) { 28865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(); 28965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mInputScheduleMap.put(inputId, schedules); 29065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 29165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules.add(schedule); 29265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 294d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized) { 295d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInitialized = true; 296d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko notifyInitialize(); 297d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 29865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onSchedulesChanged(); 29965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 30065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 30165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void onSchedulesChanged() { 302d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // TODO: notify conflict state change when some conflicting recording becomes partially 303d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // conflicting, vice versa. 30465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> addedConflicts = new ArrayList<>(); 30565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> removedConflicts = new ArrayList<>(); 30665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (String inputId : mInputScheduleMap.keySet()) { 307633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Map<Long, ConflictInfo> oldConflictInfo = mInputConflictInfoMap.get(inputId); 30865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Map<Long, ScheduledRecording> oldConflictMap = new HashMap<>(); 309633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (oldConflictInfo != null) { 310633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (ConflictInfo conflictInfo : oldConflictInfo.values()) { 311633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko oldConflictMap.put(conflictInfo.schedule.getId(), conflictInfo.schedule); 31265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 31365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 314633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko List<ConflictInfo> conflicts = getConflictingSchedulesInfo(inputId); 315633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (conflicts.isEmpty()) { 316d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mInputConflictInfoMap.remove(inputId); 317d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 318633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Map<Long, ConflictInfo> conflictInfos = new HashMap<>(); 319633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (ConflictInfo conflictInfo : conflicts) { 320633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko conflictInfos.put(conflictInfo.schedule.getId(), conflictInfo); 321633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (oldConflictMap.remove(conflictInfo.schedule.getId()) == null) { 322633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko addedConflicts.add(conflictInfo.schedule); 323d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 32465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 325633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko mInputConflictInfoMap.put(inputId, conflictInfos); 32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 32765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko removedConflicts.addAll(oldConflictMap.values()); 32865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 32965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!removedConflicts.isEmpty()) { 33065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyConflictStateChange(false, ScheduledRecording.toArray(removedConflicts)); 33165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!addedConflicts.isEmpty()) { 33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko notifyConflictStateChange(true, ScheduledRecording.toArray(addedConflicts)); 33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 33795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Returns {@code true} if this class has been initialized. */ 33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean isInitialized() { 33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return mInitialized; 34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 34295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Adds a {@link ScheduledRecordingListener}. */ 34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void addScheduledRecordingListener(ScheduledRecordingListener listener) { 34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mScheduledRecordingListeners.add(listener); 34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 34795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Removes a {@link ScheduledRecordingListener}. */ 34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) { 34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mScheduledRecordingListeners.remove(listener); 35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */ 35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { 35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingAdded(scheduledRecordings); 35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35995961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */ 36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { 36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingRemoved(scheduledRecordings); 36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener. 36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void notifyScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { 37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecordingListener l : mScheduledRecordingListeners) { 37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onScheduledRecordingStatusChanged(scheduledRecordings); 37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 37595961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Adds a {@link OnInitializeListener}. */ 376d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public final void addOnInitializeListener(OnInitializeListener listener) { 377d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mOnInitializeListeners.add(listener); 378d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 379d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 38095961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Removes a {@link OnInitializeListener}. */ 381d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public final void removeOnInitializeListener(OnInitializeListener listener) { 382d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mOnInitializeListeners.remove(listener); 383d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 384d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 38595961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Calls {@link OnInitializeListener#onInitialize} for each listener. */ 386d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private void notifyInitialize() { 387d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (OnInitializeListener l : mOnInitializeListeners) { 388d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko l.onInitialize(); 389d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 390d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 391d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 39295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Adds a {@link OnConflictStateChangeListener}. */ 39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void addOnConflictStateChangeListener(OnConflictStateChangeListener listener) { 39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mOnConflictStateChangeListeners.add(listener); 39565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 39665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 39795961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Removes a {@link OnConflictStateChangeListener}. */ 39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public final void removeOnConflictStateChangeListener(OnConflictStateChangeListener listener) { 39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mOnConflictStateChangeListeners.remove(listener); 40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 40295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener. */ 40395961816a768da387f0b5523cf4363ace2044089Nick Chalko private void notifyConflictStateChange( 40495961816a768da387f0b5523cf4363ace2044089Nick Chalko boolean conflict, ScheduledRecording... scheduledRecordings) { 40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (OnConflictStateChangeListener l : mOnConflictStateChangeListeners) { 40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko l.onConflictStateChange(conflict, scheduledRecordings); 40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 40965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 41065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 41165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for the program if it is recorded. 41295961816a768da387f0b5523cf4363ace2044089Nick Chalko * 41395961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>The recording will have the higher priority than the existing ones. 41465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public long suggestNewPriority() { 41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_PRIORITY; 41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return suggestHighestPriority(); 42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long suggestHighestPriority() { 42365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long highestPriority = DEFAULT_PRIORITY - PRIORITY_OFFSET; 42465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : mDataManager.getAllScheduledRecordings()) { 42565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getPriority() > highestPriority) { 42665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko highestPriority = schedule.getPriority(); 42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return highestPriority + PRIORITY_OFFSET; 43065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 43295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */ 433d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public long suggestHighestPriority(ScheduledRecording schedule) { 434d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(schedule.getInputId()); 435d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedules == null) { 436d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DEFAULT_PRIORITY; 437d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 438d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long highestPriority = Long.MIN_VALUE; 439d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : schedules) { 44095961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!r.equals(schedule) 44195961816a768da387f0b5523cf4363ace2044089Nick Chalko && r.isOverLapping(schedule) 442d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko && r.getPriority() > highestPriority) { 443d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko highestPriority = r.getPriority(); 444d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 445d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 446d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (highestPriority == Long.MIN_VALUE || highestPriority < schedule.getPriority()) { 447d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return schedule.getPriority(); 448d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 449d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return highestPriority + PRIORITY_OFFSET; 450d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 451d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 45295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */ 453d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public long suggestHighestPriority(String inputId, Range<Long> peroid, long basePriority) { 454d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId); 455d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedules == null) { 456d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return DEFAULT_PRIORITY; 457d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 458d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long highestPriority = Long.MIN_VALUE; 459d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : schedules) { 460d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (r.isOverLapping(peroid) && r.getPriority() > highestPriority) { 461d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko highestPriority = r.getPriority(); 462d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 463d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 464d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (highestPriority == Long.MIN_VALUE || highestPriority < basePriority) { 465d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return basePriority; 466d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 467d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return highestPriority + PRIORITY_OFFSET; 468d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 469d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 470d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 47165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for a series recording. 47295961816a768da387f0b5523cf4363ace2044089Nick Chalko * 47395961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>The recording will have the higher priority than the existing series. 47465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 47565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public long suggestNewSeriesPriority() { 47665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) { 47765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_SERIES_PRIORITY; 47865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 47965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return suggestHighestSeriesPriority(); 48065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 48165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 48265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the priority for a series recording by order of series recording priority. 48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 48595961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Higher order will have higher priority. 48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static long suggestSeriesPriority(int order) { 48865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return DEFAULT_SERIES_PRIORITY + order * PRIORITY_OFFSET; 48965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 49065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 49165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private long suggestHighestSeriesPriority() { 49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long highestPriority = DEFAULT_SERIES_PRIORITY - PRIORITY_OFFSET; 49365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (SeriesRecording schedule : mDataManager.getSeriesRecordings()) { 49465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getPriority() > highestPriority) { 49565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko highestPriority = schedule.getPriority(); 49665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 49765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 49865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return highestPriority + PRIORITY_OFFSET; 49965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 50065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 50165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 50295961816a768da387f0b5523cf4363ace2044089Nick Chalko * Returns a sorted list of all scheduled recordings that will not be recorded if this program 50395961816a768da387f0b5523cf4363ace2044089Nick Chalko * is going to be recorded, with their priorities in decending order. 50495961816a768da387f0b5523cf4363ace2044089Nick Chalko * 50595961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>An empty list means there is no conflicts. If there is conflict, a priority higher than 506d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * the first recording in the returned list should be assigned to the new schedule of this 507d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * program to guarantee the program would be completely recorded. 50865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 50965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedules(Program program) { 51065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 51165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState( 5120cc0713c1bf8027642987b750b80217569d2932aLive Channels Team Program.isProgramValid(program), TAG, "Program is invalid: " + program); 51395961816a768da387f0b5523cf4363ace2044089Nick Chalko SoftPreconditions.checkState( 51495961816a768da387f0b5523cf4363ace2044089Nick Chalko program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), 51595961816a768da387f0b5523cf4363ace2044089Nick Chalko TAG, 51665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko "Program duration is empty: " + program); 51795961816a768da387f0b5523cf4363ace2044089Nick Chalko if (!mInitialized 5180cc0713c1bf8027642987b750b80217569d2932aLive Channels Team || !Program.isProgramValid(program) 51965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || program.getStartTimeUtcMillis() >= program.getEndTimeUtcMillis()) { 52065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 52165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForProgram(mContext, program); 52365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 52465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 52565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 52695961816a768da387f0b5523cf4363ace2044089Nick Chalko return getConflictingSchedules( 52795961816a768da387f0b5523cf4363ace2044089Nick Chalko input, 52895961816a768da387f0b5523cf4363ace2044089Nick Chalko Collections.singletonList( 52995961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording.builder(input.getId(), program) 53095961816a768da387f0b5523cf4363ace2044089Nick Chalko .setPriority(suggestHighestPriority()) 53195961816a768da387f0b5523cf4363ace2044089Nick Chalko .build())); 53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 535633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko * Returns list of all conflicting scheduled recordings for the given {@code seriesRecording} 536d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * recording. 53795961816a768da387f0b5523cf4363ace2044089Nick Chalko * 53895961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Any empty list means there is no conflicts. 539d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 540d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public List<ScheduledRecording> getConflictingSchedules(SeriesRecording seriesRecording) { 541d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 542d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(seriesRecording != null, TAG, "series recording is null"); 543d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized || seriesRecording == null) { 544d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyList(); 545d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 546d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, seriesRecording.getInputId()); 547d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 548d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return Collections.emptyList(); 549d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 55095961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> scheduledRecordingForSeries = 55195961816a768da387f0b5523cf4363ace2044089Nick Chalko mDataManager.getScheduledRecordings(seriesRecording.getId()); 552633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko List<ScheduledRecording> availableScheduledRecordingForSeries = new ArrayList<>(); 553633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) { 554633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) { 555633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko availableScheduledRecordingForSeries.add(scheduledRecording); 556633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 557633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 558633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (availableScheduledRecordingForSeries.isEmpty()) { 559633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return Collections.emptyList(); 560633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 561633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return getConflictingSchedules(input, availableScheduledRecordingForSeries); 562d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 563d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 564d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 56595961816a768da387f0b5523cf4363ace2044089Nick Chalko * Returns a sorted list of all scheduled recordings that will not be recorded if this channel 56695961816a768da387f0b5523cf4363ace2044089Nick Chalko * is going to be recorded, with their priority in decending order. 56795961816a768da387f0b5523cf4363ace2044089Nick Chalko * 56895961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>An empty list means there is no conflicts. If there is conflict, a priority higher than 569d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * the first recording in the returned list should be assigned to the new schedule of this 570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * channel to guarantee the channel would be completely recorded in the designated time range. 57165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 57295961816a768da387f0b5523cf4363ace2044089Nick Chalko public List<ScheduledRecording> getConflictingSchedules( 57395961816a768da387f0b5523cf4363ace2044089Nick Chalko long channelId, long startTimeMs, long endTimeMs) { 57465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 57565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 57665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(startTimeMs < endTimeMs, TAG, "Recording duration is empty."); 57765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || startTimeMs >= endTimeMs) { 57865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 57965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 58065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 58165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 58265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 58365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 58495961816a768da387f0b5523cf4363ace2044089Nick Chalko return getConflictingSchedules( 58595961816a768da387f0b5523cf4363ace2044089Nick Chalko input, 58695961816a768da387f0b5523cf4363ace2044089Nick Chalko Collections.singletonList( 58795961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs) 58895961816a768da387f0b5523cf4363ace2044089Nick Chalko .setPriority(suggestHighestPriority()) 58995961816a768da387f0b5523cf4363ace2044089Nick Chalko .build())); 59065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 59165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 59265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 59365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns all the scheduled recordings that conflicts and will not be recorded or clipped for 59465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * the given input. 59565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 59665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @NonNull 597633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private List<ConflictInfo> getConflictingSchedulesInfo(String inputId) { 59865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 59965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId); 60065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId); 60165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || input == null) { 602633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return Collections.emptyList(); 60365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 60465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId()); 60565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null || schedules.isEmpty()) { 606633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return Collections.emptyList(); 60765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 608d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedulesInfo(schedules, input.getTunerCount()); 60965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 61065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 61165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 61265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Checks if the schedule is conflicting. 61365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 61495961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Note that the {@code schedule} should be the existing one. If not, this returns {@code 61595961816a768da387f0b5523cf4363ace2044089Nick Chalko * false}. 61665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 61765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public boolean isConflicting(ScheduledRecording schedule) { 61865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 619d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 62095961816a768da387f0b5523cf4363ace2044089Nick Chalko SoftPreconditions.checkState( 62195961816a768da387f0b5523cf4363ace2044089Nick Chalko input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId()); 622d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mInitialized || input == null) { 623d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return false; 624d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 625633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId()); 626633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return conflicts != null && conflicts.containsKey(schedule.getId()); 627d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 628d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 629d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko /** 630d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * Checks if the schedule is partially conflicting, i.e., part of the scheduled program might be 631d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * recorded even if the priority of the schedule is not raised. 63295961816a768da387f0b5523cf4363ace2044089Nick Chalko * 63395961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>If the given schedule is not conflicting or is totally conflicting, i.e., cannot be 63495961816a768da387f0b5523cf4363ace2044089Nick Chalko * recorded at all, this method returns {@code false} in both cases. 635d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 636d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public boolean isPartiallyConflicting(@NonNull ScheduledRecording schedule) { 637d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 638d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); 63995961816a768da387f0b5523cf4363ace2044089Nick Chalko SoftPreconditions.checkState( 64095961816a768da387f0b5523cf4363ace2044089Nick Chalko input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId()); 64165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || input == null) { 64265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return false; 64365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 644633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId()); 645633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko if (conflicts != null) { 646633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ConflictInfo conflictInfo = conflicts.get(schedule.getId()); 647633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return conflictInfo != null && conflictInfo.partialConflict; 648633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 649633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return false; 65065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 65165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 65265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 65395961816a768da387f0b5523cf4363ace2044089Nick Chalko * Returns priority ordered list of all scheduled recordings that will not be recorded if this 65495961816a768da387f0b5523cf4363ace2044089Nick Chalko * channel is tuned to. 65565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 65665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedulesForTune(long channelId) { 65765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 65865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 65965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 66095961816a768da387f0b5523cf4363ace2044089Nick Chalko SoftPreconditions.checkState( 66195961816a768da387f0b5523cf4363ace2044089Nick Chalko input != null, TAG, "Can't find input for channel ID: " + channelId); 66265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { 66365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 66465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 66595961816a768da387f0b5523cf4363ace2044089Nick Chalko return getConflictingSchedulesForTune( 66695961816a768da387f0b5523cf4363ace2044089Nick Chalko input.getId(), 66795961816a768da387f0b5523cf4363ace2044089Nick Chalko channelId, 66895961816a768da387f0b5523cf4363ace2044089Nick Chalko System.currentTimeMillis(), 66995961816a768da387f0b5523cf4363ace2044089Nick Chalko suggestHighestPriority(), 67095961816a768da387f0b5523cf4363ace2044089Nick Chalko getStartedRecordings(input.getId()), 67165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko input.getTunerCount()); 67265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 67365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 67465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 67595961816a768da387f0b5523cf4363ace2044089Nick Chalko public static List<ScheduledRecording> getConflictingSchedulesForTune( 67695961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId, 67795961816a768da387f0b5523cf4363ace2044089Nick Chalko long channelId, 67895961816a768da387f0b5523cf4363ace2044089Nick Chalko long currentTimeMs, 67995961816a768da387f0b5523cf4363ace2044089Nick Chalko long newPriority, 68095961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> startedRecordings, 68195961816a768da387f0b5523cf4363ace2044089Nick Chalko int tunerCount) { 68265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko boolean channelFound = false; 68365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : startedRecordings) { 68465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getChannelId() == channelId) { 68565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko channelFound = true; 68665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 68765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 68865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 68965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules; 69065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!channelFound) { 69165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // The current channel is not being recorded. 69265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = new ArrayList<>(startedRecordings); 69395961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules.add( 69495961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording.builder(inputId, channelId, currentTimeMs, currentTimeMs + 1) 69595961816a768da387f0b5523cf4363ace2044089Nick Chalko .setPriority(newPriority) 69695961816a768da387f0b5523cf4363ace2044089Nick Chalko .build()); 69765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 69865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedules = startedRecordings; 69965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 70065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(schedules, tunerCount); 70165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 70265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 70365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 70495961816a768da387f0b5523cf4363ace2044089Nick Chalko * Returns priority ordered list of all scheduled recordings that will not be recorded if the 70595961816a768da387f0b5523cf4363ace2044089Nick Chalko * user keeps watching this channel. 70695961816a768da387f0b5523cf4363ace2044089Nick Chalko * 70795961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Note that if the user keeps watching the channel, the channel can be recorded. 70865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 70965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public List<ScheduledRecording> getConflictingSchedulesForWatching(long channelId) { 71065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); 71165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID"); 71265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId); 71395961816a768da387f0b5523cf4363ace2044089Nick Chalko SoftPreconditions.checkState( 71495961816a768da387f0b5523cf4363ace2044089Nick Chalko input != null, TAG, "Can't find input for channel ID: " + channelId); 71565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (!mInitialized || channelId == Channel.INVALID_ID || input == null) { 71665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 71765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 71865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId()); 71965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedules == null || schedules.isEmpty()) { 72065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 72165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 72295961816a768da387f0b5523cf4363ace2044089Nick Chalko return getConflictingSchedulesForWatching( 72395961816a768da387f0b5523cf4363ace2044089Nick Chalko input.getId(), 72495961816a768da387f0b5523cf4363ace2044089Nick Chalko channelId, 72595961816a768da387f0b5523cf4363ace2044089Nick Chalko System.currentTimeMillis(), 72695961816a768da387f0b5523cf4363ace2044089Nick Chalko suggestNewPriority(), 72795961816a768da387f0b5523cf4363ace2044089Nick Chalko schedules, 72895961816a768da387f0b5523cf4363ace2044089Nick Chalko input.getTunerCount()); 72965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 73065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 73195961816a768da387f0b5523cf4363ace2044089Nick Chalko private List<ScheduledRecording> getConflictingSchedules( 73295961816a768da387f0b5523cf4363ace2044089Nick Chalko TvInputInfo input, List<ScheduledRecording> schedulesToAdd) { 73365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko SoftPreconditions.checkNotNull(input); 73465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (input == null || !input.canRecord() || input.getTunerCount() <= 0) { 73565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 73665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 73765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> currentSchedules = mInputScheduleMap.get(input.getId()); 73865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (currentSchedules == null || currentSchedules.isEmpty()) { 73965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return Collections.emptyList(); 74065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 74165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return getConflictingSchedules(schedulesToAdd, currentSchedules, input.getTunerCount()); 74265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 74365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 74465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 74595961816a768da387f0b5523cf4363ace2044089Nick Chalko static List<ScheduledRecording> getConflictingSchedulesForWatching( 74695961816a768da387f0b5523cf4363ace2044089Nick Chalko String inputId, 74795961816a768da387f0b5523cf4363ace2044089Nick Chalko long channelId, 74895961816a768da387f0b5523cf4363ace2044089Nick Chalko long currentTimeMs, 74995961816a768da387f0b5523cf4363ace2044089Nick Chalko long newPriority, 75095961816a768da387f0b5523cf4363ace2044089Nick Chalko @NonNull List<ScheduledRecording> schedules, 75195961816a768da387f0b5523cf4363ace2044089Nick Chalko int tunerCount) { 75265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules); 75365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedulesSameChannel = new ArrayList<>(); 75465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 75565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule.getChannelId() == channelId) { 75665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesSameChannel.add(schedule); 75765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko schedulesToCheck.remove(schedule); 75865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 75965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 76065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko // Assume that the user will watch the current channel forever. 76195961816a768da387f0b5523cf4363ace2044089Nick Chalko schedulesToCheck.add( 76295961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording.builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE) 76395961816a768da387f0b5523cf4363ace2044089Nick Chalko .setPriority(newPriority) 76495961816a768da387f0b5523cf4363ace2044089Nick Chalko .build()); 76565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> result = new ArrayList<>(); 76665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.addAll(getConflictingSchedules(schedulesSameChannel, 1)); 76765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko result.addAll(getConflictingSchedules(schedulesToCheck, tunerCount)); 768d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Collections.sort(result, RESULT_COMPARATOR); 76965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return result; 77065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 77165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 77265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 77395961816a768da387f0b5523cf4363ace2044089Nick Chalko static List<ScheduledRecording> getConflictingSchedules( 77495961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> schedulesToAdd, 77595961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> currentSchedules, 77695961816a768da387f0b5523cf4363ace2044089Nick Chalko 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 80595961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Returns all conflicting scheduled recordings for the given schedules and count of tuner. */ 80665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static List<ScheduledRecording> getConflictingSchedules( 80765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<ScheduledRecording> schedules, int tunerCount) { 808d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return getConflictingSchedules(schedules, tunerCount, null); 80965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 81065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 81165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 812d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko static List<ScheduledRecording> getConflictingSchedules( 813d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) { 814633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko List<ScheduledRecording> result = new ArrayList<>(); 815633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko for (ConflictInfo conflictInfo : 816633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko getConflictingSchedulesInfo(schedules, tunerCount, periods)) { 817633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko result.add(conflictInfo.schedule); 818633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 819d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return result; 820d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 821d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 822d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @VisibleForTesting 82395961816a768da387f0b5523cf4363ace2044089Nick Chalko static List<ConflictInfo> getConflictingSchedulesInfo( 82495961816a768da387f0b5523cf4363ace2044089Nick 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). 83095961816a768da387f0b5523cf4363ace2044089Nick Chalko * 83195961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Note that this method will ignore duplicated schedules with a same hash code. (Please 83295961816a768da387f0b5523cf4363ace2044089Nick Chalko * refer to {@link ScheduledRecording#hashCode}.) 833d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * 834d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * @return A {@link HashMap} from {@link ScheduledRecording} to {@link Boolean}. The boolean 83595961816a768da387f0b5523cf4363ace2044089Nick Chalko * value denotes if the scheduled recording is partially conflicting, i.e., is possible to 83695961816a768da387f0b5523cf4363ace2044089Nick Chalko * be partially recorded under the given schedules and tuner count {@code true}, or not 83795961816a768da387f0b5523cf4363ace2044089Nick Chalko * {@code false}. 838d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */ 839633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko private static List<ConflictInfo> 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<>(); 845633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko Map<ScheduledRecording, ConflictInfo> 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. 856633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ScheduledRecording originalSchedule = modified2OriginalSchedules.get(schedule); 857633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true)); 85865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 859d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 860d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording candidate = findReplaceableRecording(recordings, schedule); 861d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (candidate != null) { 862d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!modified2OriginalSchedules.containsKey(candidate)) { 863633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko conflicts.put(candidate, new ConflictInfo(candidate, true)); 864d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 865d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko recordings.remove(candidate); 866d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko recordings.add(schedule); 867d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (modified2OriginalSchedules.containsKey(schedule)) { 868d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Schedule has been modified, which means it's already conflicted. 869d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Modify its state to partially conflicted. 870633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ScheduledRecording originalSchedule = 871633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko modified2OriginalSchedules.get(schedule); 872633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true)); 873d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 874d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 875d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!modified2OriginalSchedules.containsKey(schedule)) { 876d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // if schedule has been modified, it's already conflicted. 877d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // No need to add it again. 878633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko conflicts.put(schedule, new ConflictInfo(schedule, false)); 879d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 880d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long earliestEndTime = getEarliestEndTime(recordings); 881d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (earliestEndTime < schedule.getEndTimeMs()) { 882d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The schedule can starts when other recording ends even though it's 883d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // clipped. 88495961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording modifiedSchedule = 88595961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording.buildFrom(schedule) 88695961816a768da387f0b5523cf4363ace2044089Nick Chalko .setStartTimeMs(earliestEndTime) 88795961816a768da387f0b5523cf4363ace2044089Nick Chalko .build(); 888d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording originalSchedule = 889d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko modified2OriginalSchedules.getOrDefault(schedule, schedule); 890d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko modified2OriginalSchedules.put(modifiedSchedule, originalSchedule); 89195961816a768da387f0b5523cf4363ace2044089Nick Chalko int insertPosition = 89295961816a768da387f0b5523cf4363ace2044089Nick Chalko Collections.binarySearch( 89395961816a768da387f0b5523cf4363ace2044089Nick Chalko schedulesToCheck, 89495961816a768da387f0b5523cf4363ace2044089Nick Chalko modifiedSchedule, 89595961816a768da387f0b5523cf4363ace2044089Nick Chalko ScheduledRecording 89695961816a768da387f0b5523cf4363ace2044089Nick Chalko .START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR); 897d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (insertPosition >= 0) { 898d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko schedulesToCheck.add(insertPosition, modifiedSchedule); 899d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 900d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko schedulesToCheck.add(-insertPosition - 1, modifiedSchedule); 901d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 902d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 90365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 90465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 905d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 906d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Returns only the schedules with the given range. 907d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (periods != null && !periods.isEmpty()) { 908d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Iterator<ScheduledRecording> iter = conflicts.keySet().iterator(); 909d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko iter.hasNext(); ) { 910d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko boolean overlapping = false; 91165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording schedule = iter.next(); 912d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Range<Long> period : periods) { 913d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedule.isOverLapping(period)) { 914d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko overlapping = true; 915d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko break; 916d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 917d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 918d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!overlapping) { 91965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko iter.remove(); 92065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 92165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 922d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 923633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko List<ConflictInfo> result = new ArrayList<>(conflicts.values()); 92495961816a768da387f0b5523cf4363ace2044089Nick Chalko Collections.sort( 92595961816a768da387f0b5523cf4363ace2044089Nick Chalko result, 92695961816a768da387f0b5523cf4363ace2044089Nick Chalko new Comparator<ConflictInfo>() { 92795961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 92895961816a768da387f0b5523cf4363ace2044089Nick Chalko public int compare(ConflictInfo lhs, ConflictInfo rhs) { 92995961816a768da387f0b5523cf4363ace2044089Nick Chalko return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule); 93095961816a768da387f0b5523cf4363ace2044089Nick Chalko } 93195961816a768da387f0b5523cf4363ace2044089Nick Chalko }); 932633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return result; 933d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 934d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 93595961816a768da387f0b5523cf4363ace2044089Nick Chalko private static void removeFinishedRecordings( 93695961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> recordings, long currentTimeMs) { 937d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (Iterator<ScheduledRecording> iter = recordings.iterator(); iter.hasNext(); ) { 938d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (iter.next().getEndTimeMs() <= currentTimeMs) { 939d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko iter.remove(); 94065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 94165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 942d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 943d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 94495961816a768da387f0b5523cf4363ace2044089Nick Chalko /** @see InputTaskScheduler#getReplacableTask */ 94595961816a768da387f0b5523cf4363ace2044089Nick Chalko private static ScheduledRecording findReplaceableRecording( 94695961816a768da387f0b5523cf4363ace2044089Nick Chalko List<ScheduledRecording> recordings, ScheduledRecording schedule) { 947d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Returns the recording with the following priority. 948d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 1. The recording with the lowest priority is returned. 949d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 2. If the priorities are the same, the recording which finishes early is returned. 950d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // 3. If 1) and 2) are the same, the early created schedule is returned. 951d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording candidate = null; 952d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording recording : recordings) { 953d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedule.getPriority() > recording.getPriority()) { 954d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, recording) > 0) { 955d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko candidate = recording; 956d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 957d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 958d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 959d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return candidate; 960d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 961d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 962d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static long getEarliestEndTime(List<ScheduledRecording> recordings) { 963d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko long earliest = Long.MAX_VALUE; 964d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording recording : recordings) { 965d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (earliest > recording.getEndTimeMs()) { 966d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko earliest = recording.getEndTimeMs(); 967d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 968d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 969d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return earliest; 970d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 971d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 972633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko @VisibleForTesting 973633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko static class ConflictInfo { 974633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko public ScheduledRecording schedule; 975633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko public boolean partialConflict; 976633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 977633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko ConflictInfo(ScheduledRecording schedule, boolean partialConflict) { 978633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko this.schedule = schedule; 979633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko this.partialConflict = partialConflict; 980633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 981633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko } 982633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko 98395961816a768da387f0b5523cf4363ace2044089Nick Chalko /** A listener which is notified the initialization of schedule manager. */ 984d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public interface OnInitializeListener { 98595961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Called when the schedule manager has been initialized. */ 986d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko void onInitialize(); 98765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 98865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 98995961816a768da387f0b5523cf4363ace2044089Nick Chalko /** A listener which is notified the conflict state change of the schedules. */ 99065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public interface OnConflictStateChangeListener { 99165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 99265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Called when the conflicting schedules change. 99395961816a768da387f0b5523cf4363ace2044089Nick Chalko * 99495961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Note that this can be called before {@link 99595961816a768da387f0b5523cf4363ace2044089Nick Chalko * ScheduledRecordingListener#onScheduledRecordingAdded} is called. 99665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 99765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise 99895961816a768da387f0b5523cf4363ace2044089Nick Chalko * {@code false}. 99965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * @param schedules the schedules 100065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 100165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko void onConflictStateChange(boolean conflict, ScheduledRecording... schedules); 100265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 100395961816a768da387f0b5523cf4363ace2044089Nick Chalko} 1004