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
176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkopackage com.android.tv.dvr.recorder;
1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.annotation.TargetApi;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentUris;
2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvContract;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.net.Uri;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Build;
2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Message;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.MainThread;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.NonNull;
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.Nullable;
2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.ArraySet;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Log;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.ApplicationSingletons;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.InputSessionManager;
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.InputSessionManager.OnTvViewChannelChangeListener;
3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.MainActivity;
3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.TvApplication;
3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.WeakHandler;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.Channel;
3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.ChannelDataManager;
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.DvrScheduleManager;
416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording;
426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.ui.DvrUiHelper;
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
4465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.ArrayList;
4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap;
4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List;
4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map;
4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Set;
4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.TimeUnit;
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
5165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/**
5265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Checking the runtime conflict of DVR recording.
5365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * <p>
5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * This class runs only while the {@link MainActivity} is resumed and holds the upcoming conflicts.
5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@TargetApi(Build.VERSION_CODES.N)
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@MainThread
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class ConflictChecker {
5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final String TAG = "ConflictChecker";
6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final boolean DEBUG = false;
6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_CHECK_CONFLICT = 1;
6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long CHECK_RETRY_PERIOD_MS = TimeUnit.SECONDS.toMillis(30);
6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * To show watch conflict dialog, the start time of the earliest conflicting schedule should be
6865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * less than or equal to this time.
6965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long MAX_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.MINUTES.toMillis(5);
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * To show watch conflict dialog, the start time of the earliest conflicting schedule should be
7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * greater than or equal to this time.
7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long MIN_WATCH_CONFLICT_CHECK_TIME_MS = TimeUnit.SECONDS.toMillis(30);
7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final MainActivity mMainActivity;
7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ChannelDataManager mChannelDataManager;
7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final DvrScheduleManager mScheduleManager;
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final InputSessionManager mSessionManager;
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ConflictCheckerHandler mHandler = new ConflictCheckerHandler(this);
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final List<ScheduledRecording> mUpcomingConflicts = new ArrayList<>();
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Set<OnUpcomingConflictChangeListener> mOnUpcomingConflictChangeListeners =
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            new ArraySet<>();
8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Map<Long, List<ScheduledRecording>> mCheckedConflictsMap = new HashMap<>();
8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ScheduledRecordingListener mScheduledRecordingListener =
8965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            new ScheduledRecordingListener() {
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings);
9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings);
9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (DEBUG) Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings);
10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    };
10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final OnTvViewChannelChangeListener mOnTvViewChannelChangeListener =
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            new OnTvViewChannelChangeListener() {
11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                @Override
11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                public void onTvViewChannelChange(@Nullable Uri channelUri) {
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            };
11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
11765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mStarted;
11865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public ConflictChecker(MainActivity mainActivity) {
12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mMainActivity = mainActivity;
12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ApplicationSingletons appSingletons = TvApplication.getSingletons(mainActivity);
12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager = appSingletons.getChannelDataManager();
12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mScheduleManager = appSingletons.getDvrScheduleManager();
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionManager = appSingletons.getInputSessionManager();
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Starts checking the conflict.
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void start() {
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mStarted) {
13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mStarted = true;
13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mScheduleManager.addScheduledRecordingListener(mScheduledRecordingListener);
13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionManager.addOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener);
13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Stops checking the conflict.
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void stop() {
14465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (!mStarted) {
14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
14765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mStarted = false;
14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionManager.removeOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener);
14965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mScheduleManager.removeScheduledRecordingListener(mScheduledRecordingListener);
15065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeCallbacksAndMessages(null);
15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the upcoming conflicts.
15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public List<ScheduledRecording> getUpcomingConflicts() {
15765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return new ArrayList<>(mUpcomingConflicts);
15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Adds a {@link OnUpcomingConflictChangeListener}.
16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void addOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) {
16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnUpcomingConflictChangeListeners.add(listener);
16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Removes the {@link OnUpcomingConflictChangeListener}.
16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void removeOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) {
17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnUpcomingConflictChangeListeners.remove(listener);
17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void notifyUpcomingConflictChanged() {
17565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (OnUpcomingConflictChangeListener l : mOnUpcomingConflictChangeListeners) {
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onUpcomingConflictChange();
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Remembers the user's decision to record while watching the channel.
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void setCheckedConflictsForChannel(long mChannelId, List<ScheduledRecording> conflicts) {
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mCheckedConflictsMap.put(mChannelId, new ArrayList<>(conflicts));
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void onCheckConflict() {
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Checks the conflicting schedules and setup the next re-check time.
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // If there are upcoming conflicts soon, it opens the conflict dialog.
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "Handling MSG_CHECK_CONFLICT");
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_CHECK_CONFLICT);
19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mUpcomingConflicts.clear();
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (!mScheduleManager.isInitialized()
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                || !mChannelDataManager.isDbLoadFinished()) {
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, CHECK_RETRY_PERIOD_MS);
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            notifyUpcomingConflictChanged();
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mSessionManager.getCurrentTvViewChannelUri() == null) {
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // As MainActivity is not using a tuner, no need to check the conflict.
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            notifyUpcomingConflictChanged();
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Uri channelUri = mSessionManager.getCurrentTvViewChannelUri();
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            notifyUpcomingConflictChanged();
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long channelId = ContentUris.parseId(channelUri);
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Channel channel = mChannelDataManager.getChannel(channelId);
21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // The conflicts caused by watching the channel.
21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        List<ScheduledRecording> conflicts = mScheduleManager
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                .getConflictingSchedulesForWatching(channel.getId());
21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long earliestToCheck = Long.MAX_VALUE;
21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long currentTimeMs = System.currentTimeMillis();
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (ScheduledRecording schedule : conflicts) {
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            long startTimeMs = schedule.getStartTimeMs();
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (startTimeMs < currentTimeMs + MIN_WATCH_CONFLICT_CHECK_TIME_MS) {
21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // The start time of the upcoming conflict remains less than the minimum
22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // check time.
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                continue;
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (startTimeMs > currentTimeMs + MAX_WATCH_CONFLICT_CHECK_TIME_MS) {
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // The start time of the upcoming conflict remains greater than the
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // maximum check time. Setup the next re-check time.
22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                long nextCheckTimeMs = startTimeMs - MAX_WATCH_CONFLICT_CHECK_TIME_MS;
22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (earliestToCheck > nextCheckTimeMs) {
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    earliestToCheck = nextCheckTimeMs;
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            } else {
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // Found upcoming conflicts which will start soon.
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mUpcomingConflicts.add(schedule);
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // The schedule will be removed from the "upcoming conflict" when the
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // recording is almost started.
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                long nextCheckTimeMs = startTimeMs - MIN_WATCH_CONFLICT_CHECK_TIME_MS;
23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (earliestToCheck > nextCheckTimeMs) {
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    earliestToCheck = nextCheckTimeMs;
23865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
23965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
24065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (earliestToCheck != Long.MAX_VALUE) {
24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT,
24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    earliestToCheck - currentTimeMs);
24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "upcoming conflicts: " + mUpcomingConflicts);
24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        notifyUpcomingConflictChanged();
24765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (!mUpcomingConflicts.isEmpty()
24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                && !DvrUiHelper.isChannelWatchConflictDialogShown(mMainActivity)) {
24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Don't show the conflict dialog if the user already knows.
25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            List<ScheduledRecording> checkedConflicts = mCheckedConflictsMap.get(
25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    channel.getId());
25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (checkedConflicts == null
25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    || !checkedConflicts.containsAll(mUpcomingConflicts)) {
25465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                DvrUiHelper.showChannelWatchConflictDialog(mMainActivity, channel);
25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
25865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static class ConflictCheckerHandler extends WeakHandler<ConflictChecker> {
26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ConflictCheckerHandler(ConflictChecker conflictChecker) {
26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            super(conflictChecker);
26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        protected void handleMessage(Message msg, @NonNull ConflictChecker conflictChecker) {
26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            switch (msg.what) {
26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_CHECK_CONFLICT:
26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    conflictChecker.onCheckConflict();
26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * A listener for the change of upcoming conflicts.
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public interface OnUpcomingConflictChangeListener {
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        void onUpcomingConflictChange();
27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
28065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}
281