165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/*
265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2016 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
17633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkopackage com.android.tv.dvr.recorder;
1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputInfo;
2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Handler;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Looper;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Message;
2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.VisibleForTesting;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.ArrayMap;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Log;
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.LongSparseArray;
2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.InputSessionManager;
29944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.Clock;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.ChannelDataManager;
310cc0713c1bf8027642987b750b80217569d2932aLive Channels Teamimport com.android.tv.data.api.Channel;
32633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.DvrDataManager;
33633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.DvrManager;
34633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.WritableDvrDataManager;
35633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording;
36d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.util.CompositeComparator;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.ArrayList;
3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Collections;
39d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Comparator;
4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Iterator;
4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List;
4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map;
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
4495961816a768da387f0b5523cf4363ace2044089Nick Chalko/** The scheduler for a TV input. */
4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class InputTaskScheduler {
4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final String TAG = "InputTaskScheduler";
4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final boolean DEBUG = false;
4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_ADD_SCHEDULED_RECORDING = 1;
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_REMOVE_SCHEDULED_RECORDING = 2;
5165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_UPDATE_SCHEDULED_RECORDING = 3;
5265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_BUILD_SCHEDULE = 4;
53d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_STOP_SCHEDULE = 5;
54d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
55d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final float MIN_REMAIN_DURATION_PERCENT = 0.05f;
56d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
57d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    // The candidate comparator should be the consistent with
58d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    // DvrScheduleManager#CANDIDATE_COMPARATOR.
59d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final Comparator<RecordingTask> CANDIDATE_COMPARATOR =
60d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            new CompositeComparator<>(
61d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    RecordingTask.PRIORITY_COMPARATOR,
62d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    RecordingTask.END_TIME_COMPARATOR,
63d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    RecordingTask.ID_COMPARATOR);
64d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
6595961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Returns the comparator which the schedules are sorted with when executed. */
66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public static Comparator<ScheduledRecording> getRecordingOrderComparator() {
67d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR;
68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
6965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Wraps a {@link RecordingTask} removing it from {@link #mPendingRecordings} when it is done.
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public final class HandlerWrapper extends Handler {
7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public static final int MESSAGE_REMOVE = 999;
7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final long mId;
7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final RecordingTask mTask;
7765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7895961816a768da387f0b5523cf4363ace2044089Nick Chalko        HandlerWrapper(
7995961816a768da387f0b5523cf4363ace2044089Nick Chalko                Looper looper, ScheduledRecording scheduledRecording, RecordingTask recordingTask) {
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            super(looper, recordingTask);
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mId = scheduledRecording.getId();
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTask = recordingTask;
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTask.setHandler(this);
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void handleMessage(Message msg) {
8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // The RecordingTask gets a chance first.
8965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // It must return false to pass this message to here.
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (msg.what == MESSAGE_REMOVE) {
9195961816a768da387f0b5523cf4363ace2044089Nick Chalko                if (DEBUG) Log.d(TAG, "done " + mId);
9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mPendingRecordings.remove(mId);
9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            removeCallbacksAndMessages(null);
9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.removeMessages(MSG_BUILD_SCHEDULE);
9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            super.handleMessage(msg);
9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TvInputInfo mInput;
10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Looper mLooper;
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ChannelDataManager mChannelDataManager;
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final DvrManager mDvrManager;
10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final WritableDvrDataManager mDataManager;
10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final InputSessionManager mSessionManager;
10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Clock mClock;
10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Context mContext;
10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final LongSparseArray<HandlerWrapper> mPendingRecordings = new LongSparseArray<>();
11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Map<Long, ScheduledRecording> mWaitingSchedules = new ArrayMap<>();
11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Handler mMainThreadHandler;
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Handler mHandler;
11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Object mInputLock = new Object();
11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final RecordingTaskFactory mRecordingTaskFactory;
11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
11795961816a768da387f0b5523cf4363ace2044089Nick Chalko    public InputTaskScheduler(
11895961816a768da387f0b5523cf4363ace2044089Nick Chalko            Context context,
11995961816a768da387f0b5523cf4363ace2044089Nick Chalko            TvInputInfo input,
12095961816a768da387f0b5523cf4363ace2044089Nick Chalko            Looper looper,
12195961816a768da387f0b5523cf4363ace2044089Nick Chalko            ChannelDataManager channelDataManager,
12295961816a768da387f0b5523cf4363ace2044089Nick Chalko            DvrManager dvrManager,
12395961816a768da387f0b5523cf4363ace2044089Nick Chalko            DvrDataManager dataManager,
12495961816a768da387f0b5523cf4363ace2044089Nick Chalko            InputSessionManager sessionManager,
12595961816a768da387f0b5523cf4363ace2044089Nick Chalko            Clock clock) {
12695961816a768da387f0b5523cf4363ace2044089Nick Chalko        this(
12795961816a768da387f0b5523cf4363ace2044089Nick Chalko                context,
12895961816a768da387f0b5523cf4363ace2044089Nick Chalko                input,
12995961816a768da387f0b5523cf4363ace2044089Nick Chalko                looper,
13095961816a768da387f0b5523cf4363ace2044089Nick Chalko                channelDataManager,
13195961816a768da387f0b5523cf4363ace2044089Nick Chalko                dvrManager,
13295961816a768da387f0b5523cf4363ace2044089Nick Chalko                dataManager,
13395961816a768da387f0b5523cf4363ace2044089Nick Chalko                sessionManager,
13495961816a768da387f0b5523cf4363ace2044089Nick Chalko                clock,
13595961816a768da387f0b5523cf4363ace2044089Nick Chalko                null);
13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
13995961816a768da387f0b5523cf4363ace2044089Nick Chalko    InputTaskScheduler(
14095961816a768da387f0b5523cf4363ace2044089Nick Chalko            Context context,
14195961816a768da387f0b5523cf4363ace2044089Nick Chalko            TvInputInfo input,
14295961816a768da387f0b5523cf4363ace2044089Nick Chalko            Looper looper,
14395961816a768da387f0b5523cf4363ace2044089Nick Chalko            ChannelDataManager channelDataManager,
14495961816a768da387f0b5523cf4363ace2044089Nick Chalko            DvrManager dvrManager,
14595961816a768da387f0b5523cf4363ace2044089Nick Chalko            DvrDataManager dataManager,
14695961816a768da387f0b5523cf4363ace2044089Nick Chalko            InputSessionManager sessionManager,
14795961816a768da387f0b5523cf4363ace2044089Nick Chalko            Clock clock,
14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            RecordingTaskFactory recordingTaskFactory) {
14965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "Creating scheduler for " + input);
15065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mContext = context;
15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mInput = input;
15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mLooper = looper;
15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager = channelDataManager;
15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mDvrManager = dvrManager;
15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mDataManager = (WritableDvrDataManager) dataManager;
15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionManager = sessionManager;
15765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mClock = clock;
158633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mMainThreadHandler = new Handler(Looper.getMainLooper());
15995961816a768da387f0b5523cf4363ace2044089Nick Chalko        mRecordingTaskFactory =
16095961816a768da387f0b5523cf4363ace2044089Nick Chalko                recordingTaskFactory != null
16195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        ? recordingTaskFactory
16295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        : new RecordingTaskFactory() {
16395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            @Override
16495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            public RecordingTask createRecordingTask(
16595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    ScheduledRecording schedule,
16695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    Channel channel,
16795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    DvrManager dvrManager,
16895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    InputSessionManager sessionManager,
16995961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    WritableDvrDataManager dataManager,
17095961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    Clock clock) {
17195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                return new RecordingTask(
17295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mContext,
17395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        schedule,
17495961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        channel,
17595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mDvrManager,
17695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mSessionManager,
17795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mDataManager,
17895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mClock);
17995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            }
18095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        };
181633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mHandler = new WorkerThreadHandler(looper);
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18495961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Adds a {@link ScheduledRecording}. */
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void addSchedule(ScheduledRecording schedule) {
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule));
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void handleAddSchedule(ScheduledRecording schedule) {
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mPendingRecordings.get(schedule.getId()) != null
19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                || mWaitingSchedules.containsKey(schedule.getId())) {
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mWaitingSchedules.put(schedule.getId(), schedule);
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_BUILD_SCHEDULE);
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20095961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Removes the {@link ScheduledRecording}. */
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void removeSchedule(ScheduledRecording schedule) {
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule));
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void handleRemoveSchedule(ScheduledRecording schedule) {
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId());
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (wrapper != null) {
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            wrapper.mTask.cancel();
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mWaitingSchedules.containsKey(schedule.getId())) {
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mWaitingSchedules.remove(schedule.getId());
21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.removeMessages(MSG_BUILD_SCHEDULE);
21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
21995961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Updates the {@link ScheduledRecording}. */
22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void updateSchedule(ScheduledRecording schedule) {
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule));
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void handleUpdateSchedule(ScheduledRecording schedule) {
22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId());
22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (wrapper != null) {
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (schedule.getStartTimeMs() > mClock.currentTimeMillis()
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    && schedule.getStartTimeMs() > wrapper.mTask.getStartTimeMs()) {
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // It shouldn't have started. Cancel and put to the waiting list.
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // The schedules will be rebuilt when the task is removed.
232633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                // The reschedule is called in RecordingScheduler.
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                wrapper.mTask.cancel();
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mWaitingSchedules.put(schedule.getId(), schedule);
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return;
23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            wrapper.sendMessage(wrapper.obtainMessage(RecordingTask.MSG_UDPATE_SCHEDULE, schedule));
23865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
23965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mWaitingSchedules.containsKey(schedule.getId())) {
24165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mWaitingSchedules.put(schedule.getId(), schedule);
24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.removeMessages(MSG_BUILD_SCHEDULE);
24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
24795961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Updates the TV input. */
24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void updateTvInputInfo(TvInputInfo input) {
24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mInputLock) {
25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mInput = input;
25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
25495961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Stops the input task scheduler. */
255d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public void stop() {
256d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mHandler.removeCallbacksAndMessages(null);
257d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE);
258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
259d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
260d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private void handleStopSchedule() {
261d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mWaitingSchedules.clear();
262d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        int size = mPendingRecordings.size();
263d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        for (int i = 0; i < size; ++i) {
264d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
265d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            task.cleanUp();
266d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
267d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
268d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void handleBuildSchedule() {
27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mWaitingSchedules.isEmpty()) {
27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long currentTimeMs = mClock.currentTimeMillis();
27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Remove past schedules.
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (Iterator<ScheduledRecording> iter = mWaitingSchedules.values().iterator();
27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                iter.hasNext(); ) {
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            ScheduledRecording schedule = iter.next();
279d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (schedule.getEndTimeMs() - currentTimeMs
280d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    <= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) {
2819850ee71f931f597658b39fba8fd18bead506955shubang                Log.e(TAG, "Error! Program ended before recording started:" + schedule);
2829850ee71f931f597658b39fba8fd18bead506955shubang                fail(schedule,
2839850ee71f931f597658b39fba8fd18bead506955shubang                        ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED);
28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                iter.remove();
28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
28665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
28765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mWaitingSchedules.isEmpty()) {
28865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
28965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
29065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Record the schedules which should start now.
29165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        List<ScheduledRecording> schedulesToStart = new ArrayList<>();
29265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (ScheduledRecording schedule : mWaitingSchedules.values()) {
29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED
29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    && schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS
29595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            <= currentTimeMs
29695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    && schedule.getEndTimeMs() > currentTimeMs) {
29765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                schedulesToStart.add(schedule);
29865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
29965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
300d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // The schedules will be executed with the following order.
301d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 1. The schedule which starts early. It can be replaced later when the schedule with the
302d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        //    higher priority needs to start.
303d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 2. The schedule with the higher priority. It can be replaced later when the schedule with
304d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        //    the higher priority needs to start.
305d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 3. The schedule which was created recently.
306d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        Collections.sort(schedulesToStart, getRecordingOrderComparator());
30765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int tunerCount;
30865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mInputLock) {
30965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0;
31065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
31165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (ScheduledRecording schedule : schedulesToStart) {
31265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (hasTaskWhichFinishEarlier(schedule)) {
31365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // If there is a schedule which finishes earlier than the new schedule, rebuild the
31465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // schedules after it finishes.
31565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return;
31665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
31765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mPendingRecordings.size() < tunerCount) {
31865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // Tuners available.
31965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                createRecordingTask(schedule).start();
32065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mWaitingSchedules.remove(schedule.getId());
32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            } else {
32265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // No available tuners.
32365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                RecordingTask task = getReplacableTask(schedule);
32465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (task != null) {
32565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    task.stop();
32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // Just return. The schedules will be rebuilt after the task is stopped.
32765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return;
32865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
32965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
33065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
33165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mWaitingSchedules.isEmpty()) {
33265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Set next scheduling.
33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long earliest = Long.MAX_VALUE;
33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (ScheduledRecording schedule : mWaitingSchedules.values()) {
337d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            // The conflicting schedules will be removed if they end before conflicting resolved.
338d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (schedulesToStart.contains(schedule)) {
339d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (earliest > schedule.getEndTimeMs()) {
340d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    earliest = schedule.getEndTimeMs();
341d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
342d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            } else {
34395961816a768da387f0b5523cf4363ace2044089Nick Chalko                if (earliest
34495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        > schedule.getStartTimeMs()
34595961816a768da387f0b5523cf4363ace2044089Nick Chalko                                - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) {
34695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    earliest =
34795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            schedule.getStartTimeMs()
34895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    - RecordingTask.RECORDING_EARLY_START_OFFSET_MS;
349d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
352d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mHandler.sendEmptyMessageDelayed(MSG_BUILD_SCHEDULE, earliest - currentTimeMs);
35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private RecordingTask createRecordingTask(ScheduledRecording schedule) {
35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Channel channel = mChannelDataManager.getChannel(schedule.getChannelId());
35795961816a768da387f0b5523cf4363ace2044089Nick Chalko        RecordingTask recordingTask =
35895961816a768da387f0b5523cf4363ace2044089Nick Chalko                mRecordingTaskFactory.createRecordingTask(
35995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        schedule, channel, mDvrManager, mSessionManager, mDataManager, mClock);
36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask);
36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mPendingRecordings.put(schedule.getId(), handlerWrapper);
36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return recordingTask;
36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean hasTaskWhichFinishEarlier(ScheduledRecording schedule) {
36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int size = mPendingRecordings.size();
36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (int i = 0; i < size; ++i) {
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (task.getEndTimeMs() <= schedule.getStartTimeMs()) {
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return true;
37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return false;
37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private RecordingTask getReplacableTask(ScheduledRecording schedule) {
377d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // Returns the recording with the following priority.
378d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 1. The recording with the lowest priority is returned.
379d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 2. If the priorities are the same, the recording which finishes early is returned.
380d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        // 3. If 1) and 2) are the same, the early created schedule is returned.
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int size = mPendingRecordings.size();
38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        RecordingTask candidate = null;
38365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (int i = 0; i < size; ++i) {
38465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
385d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (schedule.getPriority() > task.getPriority()) {
386d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, task) > 0) {
387d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    candidate = task;
388d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return candidate;
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3949850ee71f931f597658b39fba8fd18bead506955shubang    private void fail(ScheduledRecording schedule, int reason) {
39565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // It's called when the scheduling has been failed without creating RecordingTask.
39695961816a768da387f0b5523cf4363ace2044089Nick Chalko        runOnMainHandler(
39795961816a768da387f0b5523cf4363ace2044089Nick Chalko                new Runnable() {
39895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    @Override
39995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    public void run() {
40095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        ScheduledRecording scheduleInManager =
40195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                mDataManager.getScheduledRecording(schedule.getId());
40295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (scheduleInManager != null) {
40395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            // The schedule should be updated based on the object from DataManager
4049850ee71f931f597658b39fba8fd18bead506955shubang                            // in case when it has been updated.
40595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mDataManager.changeState(
4069850ee71f931f597658b39fba8fd18bead506955shubang                                    scheduleInManager,
4079850ee71f931f597658b39fba8fd18bead506955shubang                                    ScheduledRecording.STATE_RECORDING_FAILED,
4089850ee71f931f597658b39fba8fd18bead506955shubang                                    reason);
40995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        }
41095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
41195961816a768da387f0b5523cf4363ace2044089Nick Chalko                });
41265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
41365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
41465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void runOnMainHandler(Runnable runnable) {
41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            runnable.run();
41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        } else {
41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mMainThreadHandler.post(runnable);
41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @VisibleForTesting
42365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    interface RecordingTaskFactory {
42495961816a768da387f0b5523cf4363ace2044089Nick Chalko        RecordingTask createRecordingTask(
42595961816a768da387f0b5523cf4363ace2044089Nick Chalko                ScheduledRecording scheduledRecording,
42695961816a768da387f0b5523cf4363ace2044089Nick Chalko                Channel channel,
42795961816a768da387f0b5523cf4363ace2044089Nick Chalko                DvrManager dvrManager,
42895961816a768da387f0b5523cf4363ace2044089Nick Chalko                InputSessionManager sessionManager,
42995961816a768da387f0b5523cf4363ace2044089Nick Chalko                WritableDvrDataManager dataManager,
43095961816a768da387f0b5523cf4363ace2044089Nick Chalko                Clock clock);
43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
43265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
43365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private class WorkerThreadHandler extends Handler {
43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public WorkerThreadHandler(Looper looper) {
43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            super(looper);
43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void handleMessage(Message msg) {
44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            switch (msg.what) {
44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_ADD_SCHEDULED_RECORDING:
44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    handleAddSchedule((ScheduledRecording) msg.obj);
44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_REMOVE_SCHEDULED_RECORDING:
44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    handleRemoveSchedule((ScheduledRecording) msg.obj);
44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_UPDATE_SCHEDULED_RECORDING:
44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    handleUpdateSchedule((ScheduledRecording) msg.obj);
449944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    break;
45065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_BUILD_SCHEDULE:
45165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    handleBuildSchedule();
45265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
453d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                case MSG_STOP_SCHEDULE:
454d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    handleStopSchedule();
455d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    break;
45665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
45765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
45865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
45965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}
460