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