aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/DvrScheduleManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr/DvrScheduleManager.java')
-rw-r--r--src/com/android/tv/dvr/DvrScheduleManager.java439
1 files changed, 351 insertions, 88 deletions
diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java
index aa77c400..a5851a75 100644
--- a/src/com/android/tv/dvr/DvrScheduleManager.java
+++ b/src/com/android/tv/dvr/DvrScheduleManager.java
@@ -24,6 +24,7 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
+import android.util.LongSparseArray;
import android.util.Range;
import com.android.tv.ApplicationSingletons;
@@ -34,16 +35,18 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
+import com.android.tv.util.CompositeComparator;
import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* A class to manage the schedules.
@@ -64,15 +67,34 @@ public class DvrScheduleManager {
// The new priority will have the offset from the existing one.
private static final long PRIORITY_OFFSET = 1024;
+ private static final Comparator<ScheduledRecording> RESULT_COMPARATOR =
+ new CompositeComparator<>(
+ ScheduledRecording.PRIORITY_COMPARATOR.reversed(),
+ ScheduledRecording.START_TIME_COMPARATOR,
+ ScheduledRecording.ID_COMPARATOR.reversed());
+
+ // The candidate comparator should be the consistent with
+ // InputTaskScheduler#CANDIDATE_COMPARATOR.
+ private static final Comparator<ScheduledRecording> CANDIDATE_COMPARATOR =
+ new CompositeComparator<>(
+ ScheduledRecording.PRIORITY_COMPARATOR,
+ ScheduledRecording.END_TIME_COMPARATOR,
+ ScheduledRecording.ID_COMPARATOR);
+
private final Context mContext;
private final DvrDataManagerImpl mDataManager;
private final ChannelDataManager mChannelDataManager;
private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>();
- private final Map<String, List<ScheduledRecording>> mInputConflictMap = new HashMap<>();
+ // The inner map is a hash map from scheduled recording to its conflicting status, i.e.,
+ // the boolean value true denotes the schedule is just partially conflicting, which means
+ // although there's conflictit, it might still be recorded partially.
+ private final Map<String, Map<ScheduledRecording, Boolean>> mInputConflictInfoMap =
+ new HashMap<>();
private boolean mInitialized;
+ private final Set<OnInitializeListener> mOnInitializeListeners = new CopyOnWriteArraySet<>();
private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>();
private final Set<OnConflictStateChangeListener> mOnConflictStateChangeListeners =
new ArraySet<>();
@@ -106,10 +128,13 @@ public class DvrScheduleManager {
if (!schedule.isNotStarted() && !schedule.isInProgress()) {
continue;
}
- TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext,
- schedule.getChannelId());
- if (input == null) {
+ TvInputInfo input = Utils
+ .getTvInputInfoForInputId(mContext, schedule.getInputId());
+ if (!SoftPreconditions.checkArgument(input != null, TAG,
+ "Input was removed for : " + schedule)) {
// Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
continue;
}
String inputId = input.getId();
@@ -131,9 +156,11 @@ public class DvrScheduleManager {
}
for (ScheduledRecording schedule : scheduledRecordings) {
TvInputInfo input = Utils
- .getTvInputInfoForChannelId(mContext, schedule.getChannelId());
+ .getTvInputInfoForInputId(mContext, schedule.getInputId());
if (input == null) {
// Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
continue;
}
String inputId = input.getId();
@@ -144,6 +171,14 @@ public class DvrScheduleManager {
mInputScheduleMap.remove(inputId);
}
}
+ Map<ScheduledRecording, Boolean> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
+ if (conflictInfo != null) {
+ conflictInfo.remove(schedule);
+ if (conflictInfo.isEmpty()) {
+ mInputConflictInfoMap.remove(inputId);
+ }
+ }
}
onSchedulesChanged();
notifyScheduledRecordingRemoved(scheduledRecordings);
@@ -157,9 +192,12 @@ public class DvrScheduleManager {
}
for (ScheduledRecording schedule : scheduledRecordings) {
TvInputInfo input = Utils
- .getTvInputInfoForChannelId(mContext, schedule.getChannelId());
- if (input == null) {
+ .getTvInputInfoForInputId(mContext, schedule.getInputId());
+ if (!SoftPreconditions.checkArgument(input != null, TAG,
+ "Input was removed for : " + schedule)) {
// Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
continue;
}
String inputId = input.getId();
@@ -170,8 +208,7 @@ public class DvrScheduleManager {
}
// Compare ID because ScheduledRecording.equals() doesn't work if the state
// is changed.
- Iterator<ScheduledRecording> i = schedules.iterator();
- while (i.hasNext()) {
+ for (Iterator<ScheduledRecording> i = schedules.iterator(); i.hasNext(); ) {
if (i.next().getId() == schedule.getId()) {
i.remove();
break;
@@ -183,6 +220,24 @@ public class DvrScheduleManager {
if (schedules.isEmpty()) {
mInputScheduleMap.remove(inputId);
}
+ // Update conflict list as well
+ Map<ScheduledRecording, Boolean> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
+ if (conflictInfo != null) {
+ // Compare ID because ScheduledRecording.equals() doesn't work if the state
+ // is changed.
+ ScheduledRecording oldSchedule = null;
+ for (ScheduledRecording s : conflictInfo.keySet()) {
+ if (s.getId() == schedule.getId()) {
+ oldSchedule = s;
+ break;
+ }
+ }
+ if (oldSchedule != null) {
+ conflictInfo.put(schedule, conflictInfo.get(oldSchedule));
+ conflictInfo.remove(oldSchedule);
+ }
+ }
}
onSchedulesChanged();
notifyScheduledRecordingStatusChanged(scheduledRecordings);
@@ -249,33 +304,39 @@ public class DvrScheduleManager {
schedules.add(schedule);
}
}
- mInitialized = true;
+ if (!mInitialized) {
+ mInitialized = true;
+ notifyInitialize();
+ }
onSchedulesChanged();
}
private void onSchedulesChanged() {
+ // TODO: notify conflict state change when some conflicting recording becomes partially
+ // conflicting, vice versa.
List<ScheduledRecording> addedConflicts = new ArrayList<>();
List<ScheduledRecording> removedConflicts = new ArrayList<>();
for (String inputId : mInputScheduleMap.keySet()) {
- List<ScheduledRecording> oldConflicts = mInputConflictMap.get(inputId);
+ Map<ScheduledRecording, Boolean> oldConflictsInfo = mInputConflictInfoMap.get(inputId);
Map<Long, ScheduledRecording> oldConflictMap = new HashMap<>();
- if (oldConflicts != null) {
- for (ScheduledRecording r : oldConflicts) {
+ if (oldConflictsInfo != null) {
+ for (ScheduledRecording r : oldConflictsInfo.keySet()) {
oldConflictMap.put(r.getId(), r);
}
}
- List<ScheduledRecording> conflicts = getConflictingSchedules(inputId);
- for (ScheduledRecording r : conflicts) {
- if (oldConflictMap.remove(r.getId()) == null) {
- addedConflicts.add(r);
+ Map<ScheduledRecording, Boolean> conflictInfo = getConflictingSchedulesInfo(inputId);
+ if (conflictInfo.isEmpty()) {
+ mInputConflictInfoMap.remove(inputId);
+ } else {
+ mInputConflictInfoMap.put(inputId, conflictInfo);
+ List<ScheduledRecording> conflicts = new ArrayList<>(conflictInfo.keySet());
+ for (ScheduledRecording r : conflicts) {
+ if (oldConflictMap.remove(r.getId()) == null) {
+ addedConflicts.add(r);
+ }
}
}
removedConflicts.addAll(oldConflictMap.values());
- if (conflicts.isEmpty()) {
- mInputConflictMap.remove(inputId);
- } else {
- mInputConflictMap.put(inputId, conflicts);
- }
}
if (!removedConflicts.isEmpty()) {
notifyConflictStateChange(false, ScheduledRecording.toArray(removedConflicts));
@@ -334,6 +395,29 @@ public class DvrScheduleManager {
}
/**
+ * Adds a {@link OnInitializeListener}.
+ */
+ public final void addOnInitializeListener(OnInitializeListener listener) {
+ mOnInitializeListeners.add(listener);
+ }
+
+ /**
+ * Removes a {@link OnInitializeListener}.
+ */
+ public final void removeOnInitializeListener(OnInitializeListener listener) {
+ mOnInitializeListeners.remove(listener);
+ }
+
+ /**
+ * Calls {@link OnInitializeListener#onInitialize} for each listener.
+ */
+ private void notifyInitialize() {
+ for (OnInitializeListener l : mOnInitializeListeners) {
+ l.onInitialize();
+ }
+ }
+
+ /**
* Adds a {@link OnConflictStateChangeListener}.
*/
public final void addOnConflictStateChangeListener(OnConflictStateChangeListener listener) {
@@ -380,6 +464,47 @@ public class DvrScheduleManager {
}
/**
+ * Suggests the higher priority than the schedules which overlap with {@code schedule}.
+ */
+ public long suggestHighestPriority(ScheduledRecording schedule) {
+ List<ScheduledRecording> schedules = mInputScheduleMap.get(schedule.getInputId());
+ if (schedules == null) {
+ return DEFAULT_PRIORITY;
+ }
+ long highestPriority = Long.MIN_VALUE;
+ for (ScheduledRecording r : schedules) {
+ if (!r.equals(schedule) && r.isOverLapping(schedule)
+ && r.getPriority() > highestPriority) {
+ highestPriority = r.getPriority();
+ }
+ }
+ if (highestPriority == Long.MIN_VALUE || highestPriority < schedule.getPriority()) {
+ return schedule.getPriority();
+ }
+ return highestPriority + PRIORITY_OFFSET;
+ }
+
+ /**
+ * Suggests the higher priority than the schedules which overlap with {@code schedule}.
+ */
+ public long suggestHighestPriority(String inputId, Range<Long> peroid, long basePriority) {
+ List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
+ if (schedules == null) {
+ return DEFAULT_PRIORITY;
+ }
+ long highestPriority = Long.MIN_VALUE;
+ for (ScheduledRecording r : schedules) {
+ if (r.isOverLapping(peroid) && r.getPriority() > highestPriority) {
+ highestPriority = r.getPriority();
+ }
+ }
+ if (highestPriority == Long.MIN_VALUE || highestPriority < basePriority) {
+ return basePriority;
+ }
+ return highestPriority + PRIORITY_OFFSET;
+ }
+
+ /**
* Returns the priority for a series recording.
* <p>
* The recording will have the higher priority than the existing series.
@@ -411,11 +536,12 @@ public class DvrScheduleManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * this program is.
+ * Returns a sorted list of all scheduled recordings that will not be recorded if
+ * this program is going to be recorded, with their priorities in decending order.
* <p>
- * Any empty list means there is no conflicts. If there is conflict the program must be
- * scheduled to record with a priority higher than the first recording in the list returned.
+ * An empty list means there is no conflicts. If there is conflict, a priority higher than
+ * the first recording in the returned list should be assigned to the new schedule of this
+ * program to guarantee the program would be completely recorded.
*/
public List<ScheduledRecording> getConflictingSchedules(Program program) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
@@ -439,11 +565,34 @@ public class DvrScheduleManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * this channel is.
+ * Returns list of all conflicting scheduled recordings with schedules belonging to {@code
+ * seriesRecording}
+ * recording.
+ * <p>
+ * Any empty list means there is no conflicts.
+ */
+ public List<ScheduledRecording> getConflictingSchedules(SeriesRecording seriesRecording) {
+ SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
+ SoftPreconditions.checkState(seriesRecording != null, TAG, "series recording is null");
+ if (!mInitialized || seriesRecording == null) {
+ return Collections.emptyList();
+ }
+ TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, seriesRecording.getInputId());
+ if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
+ return Collections.emptyList();
+ }
+ List<ScheduledRecording> schedulesForSeries = mDataManager.getScheduledRecordings(
+ seriesRecording.getId());
+ return getConflictingSchedules(input, schedulesForSeries);
+ }
+
+ /**
+ * Returns a sorted list of all scheduled recordings that will not be recorded if
+ * this channel is going to be recorded, with their priority in decending order.
* <p>
- * Any empty list means there is no conflicts. If there is conflict the channel must be
- * scheduled to record with a priority higher than the first recording in the list returned.
+ * An empty list means there is no conflicts. If there is conflict, a priority higher than
+ * the first recording in the returned list should be assigned to the new schedule of this
+ * channel to guarantee the channel would be completely recorded in the designated time range.
*/
public List<ScheduledRecording> getConflictingSchedules(long channelId, long startTimeMs,
long endTimeMs) {
@@ -468,18 +617,18 @@ public class DvrScheduleManager {
* the given input.
*/
@NonNull
- private List<ScheduledRecording> getConflictingSchedules(String inputId) {
+ private Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(String inputId) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId);
SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId);
if (!mInitialized || input == null) {
- return Collections.emptyList();
+ return Collections.emptyMap();
}
List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId());
if (schedules == null || schedules.isEmpty()) {
- return Collections.emptyList();
+ return Collections.emptyMap();
}
- return getConflictingSchedules(schedules, input.getTunerCount());
+ return getConflictingSchedulesInfo(schedules, input.getTunerCount());
}
/**
@@ -490,14 +639,33 @@ public class DvrScheduleManager {
*/
public boolean isConflicting(ScheduledRecording schedule) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
- TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, schedule.getChannelId());
+ TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
+ SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : "
+ + schedule.getChannelId());
+ if (!mInitialized || input == null) {
+ return false;
+ }
+ Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
+ return conflicts != null && conflicts.containsKey(schedule);
+ }
+
+ /**
+ * Checks if the schedule is partially conflicting, i.e., part of the scheduled program might be
+ * recorded even if the priority of the schedule is not raised.
+ * <p>
+ * If the given schedule is not conflicting or is totally conflicting, i.e., cannot be recorded
+ * at all, this method returns {@code false} in both cases.
+ */
+ public boolean isPartiallyConflicting(@NonNull ScheduledRecording schedule) {
+ SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
+ TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : "
+ schedule.getChannelId());
if (!mInitialized || input == null) {
return false;
}
- List<ScheduledRecording> conflicts = mInputConflictMap.get(input.getId());
- return conflicts != null && conflicts.contains(schedule);
+ Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
+ return conflicts != null && conflicts.getOrDefault(schedule, false);
}
/**
@@ -599,7 +767,7 @@ public class DvrScheduleManager {
List<ScheduledRecording> result = new ArrayList<>();
result.addAll(getConflictingSchedules(schedulesSameChannel, 1));
result.addAll(getConflictingSchedules(schedulesToCheck, tunerCount));
- Collections.sort(result, ScheduledRecording.PRIORITY_COMPARATOR);
+ Collections.sort(result, RESULT_COMPARATOR);
return result;
}
@@ -639,66 +807,161 @@ public class DvrScheduleManager {
*/
public static List<ScheduledRecording> getConflictingSchedules(
List<ScheduledRecording> schedules, int tunerCount) {
- return getConflictingSchedules(schedules, tunerCount,
- Collections.singletonList(new Range<>(Long.MIN_VALUE, Long.MAX_VALUE)));
+ return getConflictingSchedules(schedules, tunerCount, null);
}
@VisibleForTesting
- static List<ScheduledRecording> getConflictingSchedules(List<ScheduledRecording> schedules,
- int tunerCount, List<Range<Long>> periods) {
- List<ScheduledRecording> schedulesToCheck = new ArrayList<>();
- // Filter out non-overlapping or empty duration of schedules.
- for (ScheduledRecording schedule : schedules) {
- for (Range<Long> period : periods) {
- if (schedule.isOverLapping(period)
- && schedule.getStartTimeMs() < schedule.getEndTimeMs()) {
- schedulesToCheck.add(schedule);
- break;
+ static List<ScheduledRecording> getConflictingSchedules(
+ List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
+ List<ScheduledRecording> result = new ArrayList<>(
+ getConflictingSchedulesInfo(schedules, tunerCount, periods).keySet());
+ Collections.sort(result, RESULT_COMPARATOR);
+ return result;
+ }
+
+ @VisibleForTesting
+ static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
+ List<ScheduledRecording> schedules, int tunerCount) {
+ return getConflictingSchedulesInfo(schedules, tunerCount, null);
+ }
+
+ /**
+ * This is the core method to calculate all the conflicting schedules (in given periods).
+ * <p>
+ * Note that this method will ignore duplicated schedules with a same hash code. (Please refer
+ * to {@link ScheduledRecording#hashCode}.)
+ *
+ * @return A {@link HashMap} from {@link ScheduledRecording} to {@link Boolean}. The boolean
+ * value denotes if the scheduled recording is partially conflicting, i.e., is possible
+ * to be partially recorded under the given schedules and tuner count {@code true},
+ * or not {@code false}.
+ */
+ private static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
+ List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
+ List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules);
+ // Sort by the same order as that in InputTaskScheduler.
+ Collections.sort(schedulesToCheck, InputTaskScheduler.getRecordingOrderComparator());
+ List<ScheduledRecording> recordings = new ArrayList<>();
+ Map<ScheduledRecording, Boolean> conflicts = new HashMap<>();
+ Map<ScheduledRecording, ScheduledRecording> modified2OriginalSchedules = new HashMap<>();
+ // Simulate InputTaskScheduler.
+ while (!schedulesToCheck.isEmpty()) {
+ ScheduledRecording schedule = schedulesToCheck.remove(0);
+ removeFinishedRecordings(recordings, schedule.getStartTimeMs());
+ if (recordings.size() < tunerCount) {
+ recordings.add(schedule);
+ if (modified2OriginalSchedules.containsKey(schedule)) {
+ // Schedule has been modified, which means it's already conflicted.
+ // Modify its state to partially conflicted.
+ conflicts.put(modified2OriginalSchedules.get(schedule), true);
}
- }
- }
- // Sort by the end time.
- // If a.end <= b.end <= c.end and a overlaps with b and c, then b overlaps with c.
- // Likewise, if a1.end <= a2.end <= ... , all the schedules which overlap with a1 overlap
- // with each other.
- Collections.sort(schedulesToCheck, ScheduledRecording.END_TIME_COMPARATOR);
- Set<ScheduledRecording> conflicts = new ArraySet<>();
- List<ScheduledRecording> overlaps = new ArrayList<>();
- for (int i = 0; i < schedulesToCheck.size(); ++i) {
- ScheduledRecording r1 = schedulesToCheck.get(i);
- if (conflicts.contains(r1)) {
- // No need to check r1 because it's a conflicting schedule already.
- continue;
- }
- overlaps.clear();
- overlaps.add(r1);
- // Find schedules which overlap with r1.
- for (int j = i + 1; j < schedulesToCheck.size(); ++j) {
- ScheduledRecording r2 = schedulesToCheck.get(j);
- if (!conflicts.contains(r2) && r1.getEndTimeMs() > r2.getStartTimeMs()) {
- overlaps.add(r2);
+ } else {
+ ScheduledRecording candidate = findReplaceableRecording(recordings, schedule);
+ if (candidate != null) {
+ if (!modified2OriginalSchedules.containsKey(candidate)) {
+ conflicts.put(candidate, true);
+ }
+ recordings.remove(candidate);
+ recordings.add(schedule);
+ if (modified2OriginalSchedules.containsKey(schedule)) {
+ // Schedule has been modified, which means it's already conflicted.
+ // Modify its state to partially conflicted.
+ conflicts.put(modified2OriginalSchedules.get(schedule), true);
+ }
+ } else {
+ if (!modified2OriginalSchedules.containsKey(schedule)) {
+ // if schedule has been modified, it's already conflicted.
+ // No need to add it again.
+ conflicts.put(schedule, false);
+ }
+ long earliestEndTime = getEarliestEndTime(recordings);
+ if (earliestEndTime < schedule.getEndTimeMs()) {
+ // The schedule can starts when other recording ends even though it's
+ // clipped.
+ ScheduledRecording modifiedSchedule = ScheduledRecording.buildFrom(schedule)
+ .setStartTimeMs(earliestEndTime).build();
+ ScheduledRecording originalSchedule =
+ modified2OriginalSchedules.getOrDefault(schedule, schedule);
+ modified2OriginalSchedules.put(modifiedSchedule, originalSchedule);
+ int insertPosition = Collections.binarySearch(schedulesToCheck,
+ modifiedSchedule,
+ ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
+ if (insertPosition >= 0) {
+ schedulesToCheck.add(insertPosition, modifiedSchedule);
+ } else {
+ schedulesToCheck.add(-insertPosition - 1, modifiedSchedule);
+ }
+ }
}
}
- Collections.sort(overlaps, ScheduledRecording.PRIORITY_COMPARATOR);
- // If there are more than one overlapping schedules for the same channel, only one
- // schedule will be recorded.
- HashSet<Long> channelIds = new HashSet<>();
- for (Iterator<ScheduledRecording> iter = overlaps.iterator(); iter.hasNext(); ) {
+ }
+ // Returns only the schedules with the given range.
+ if (periods != null && !periods.isEmpty()) {
+ for (Iterator<ScheduledRecording> iter = conflicts.keySet().iterator();
+ iter.hasNext(); ) {
+ boolean overlapping = false;
ScheduledRecording schedule = iter.next();
- if (channelIds.contains(schedule.getChannelId())) {
- conflicts.add(schedule);
+ for (Range<Long> period : periods) {
+ if (schedule.isOverLapping(period)) {
+ overlapping = true;
+ break;
+ }
+ }
+ if (!overlapping) {
iter.remove();
- } else {
- channelIds.add(schedule.getChannelId());
}
}
- if (overlaps.size() > tunerCount) {
- conflicts.addAll(overlaps.subList(tunerCount, overlaps.size()));
+ }
+ return conflicts;
+ }
+
+ private static void removeFinishedRecordings(List<ScheduledRecording> recordings,
+ long currentTimeMs) {
+ for (Iterator<ScheduledRecording> iter = recordings.iterator(); iter.hasNext(); ) {
+ if (iter.next().getEndTimeMs() <= currentTimeMs) {
+ iter.remove();
}
}
- List<ScheduledRecording> result = new ArrayList<>(conflicts);
- Collections.sort(result, ScheduledRecording.PRIORITY_COMPARATOR);
- return result;
+ }
+
+ /**
+ * @see InputTaskScheduler#getReplacableTask
+ */
+ private static ScheduledRecording findReplaceableRecording(List<ScheduledRecording> recordings,
+ ScheduledRecording schedule) {
+ // Returns the recording with the following priority.
+ // 1. The recording with the lowest priority is returned.
+ // 2. If the priorities are the same, the recording which finishes early is returned.
+ // 3. If 1) and 2) are the same, the early created schedule is returned.
+ ScheduledRecording candidate = null;
+ for (ScheduledRecording recording : recordings) {
+ if (schedule.getPriority() > recording.getPriority()) {
+ if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, recording) > 0) {
+ candidate = recording;
+ }
+ }
+ }
+ return candidate;
+ }
+
+ private static long getEarliestEndTime(List<ScheduledRecording> recordings) {
+ long earliest = Long.MAX_VALUE;
+ for (ScheduledRecording recording : recordings) {
+ if (earliest > recording.getEndTimeMs()) {
+ earliest = recording.getEndTimeMs();
+ }
+ }
+ return earliest;
+ }
+
+ /**
+ * A listener which is notified the initialization of schedule manager.
+ */
+ public interface OnInitializeListener {
+ /**
+ * Called when the schedule manager has been initialized.
+ */
+ void onInitialize();
}
/**
@@ -714,4 +977,4 @@ public class DvrScheduleManager {
*/
void onConflictStateChange(boolean conflict, ScheduledRecording... schedules);
}
-}
+} \ No newline at end of file