aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/InputTaskScheduler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr/InputTaskScheduler.java')
-rw-r--r--src/com/android/tv/dvr/InputTaskScheduler.java431
1 files changed, 0 insertions, 431 deletions
diff --git a/src/com/android/tv/dvr/InputTaskScheduler.java b/src/com/android/tv/dvr/InputTaskScheduler.java
deleted file mode 100644
index 53c89ebc..00000000
--- a/src/com/android/tv/dvr/InputTaskScheduler.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.dvr;
-
-import android.content.Context;
-import android.media.tv.TvInputInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import com.android.tv.InputSessionManager;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelDataManager;
-import com.android.tv.util.Clock;
-import com.android.tv.util.CompositeComparator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The scheduler for a TV input.
- */
-public class InputTaskScheduler {
- private static final String TAG = "InputTaskScheduler";
- private static final boolean DEBUG = false;
-
- private static final int MSG_ADD_SCHEDULED_RECORDING = 1;
- private static final int MSG_REMOVE_SCHEDULED_RECORDING = 2;
- private static final int MSG_UPDATE_SCHEDULED_RECORDING = 3;
- private static final int MSG_BUILD_SCHEDULE = 4;
- private static final int MSG_STOP_SCHEDULE = 5;
-
- private static final float MIN_REMAIN_DURATION_PERCENT = 0.05f;
-
- // The candidate comparator should be the consistent with
- // DvrScheduleManager#CANDIDATE_COMPARATOR.
- private static final Comparator<RecordingTask> CANDIDATE_COMPARATOR =
- new CompositeComparator<>(
- RecordingTask.PRIORITY_COMPARATOR,
- RecordingTask.END_TIME_COMPARATOR,
- RecordingTask.ID_COMPARATOR);
-
- /**
- * Returns the comparator which the schedules are sorted with when executed.
- */
- public static Comparator<ScheduledRecording> getRecordingOrderComparator() {
- return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR;
- }
-
- /**
- * Wraps a {@link RecordingTask} removing it from {@link #mPendingRecordings} when it is done.
- */
- public final class HandlerWrapper extends Handler {
- public static final int MESSAGE_REMOVE = 999;
- private final long mId;
- private final RecordingTask mTask;
-
- HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording,
- RecordingTask recordingTask) {
- super(looper, recordingTask);
- mId = scheduledRecording.getId();
- mTask = recordingTask;
- mTask.setHandler(this);
- }
-
- @Override
- public void handleMessage(Message msg) {
- // The RecordingTask gets a chance first.
- // It must return false to pass this message to here.
- if (msg.what == MESSAGE_REMOVE) {
- if (DEBUG) Log.d(TAG, "done " + mId);
- mPendingRecordings.remove(mId);
- }
- removeCallbacksAndMessages(null);
- mHandler.removeMessages(MSG_BUILD_SCHEDULE);
- mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
- super.handleMessage(msg);
- }
- }
-
- private TvInputInfo mInput;
- private final Looper mLooper;
- private final ChannelDataManager mChannelDataManager;
- private final DvrManager mDvrManager;
- private final WritableDvrDataManager mDataManager;
- private final InputSessionManager mSessionManager;
- private final Clock mClock;
- private final Context mContext;
-
- private final LongSparseArray<HandlerWrapper> mPendingRecordings = new LongSparseArray<>();
- private final Map<Long, ScheduledRecording> mWaitingSchedules = new ArrayMap<>();
- private final Handler mMainThreadHandler;
- private final Handler mHandler;
- private final Object mInputLock = new Object();
- private final RecordingTaskFactory mRecordingTaskFactory;
-
- public InputTaskScheduler(Context context, TvInputInfo input, Looper looper,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock) {
- this(context, input, looper, channelDataManager, dvrManager, dataManager, sessionManager,
- clock, new Handler(Looper.getMainLooper()), null, null);
- }
-
- @VisibleForTesting
- InputTaskScheduler(Context context, TvInputInfo input, Looper looper,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock,
- Handler mainThreadHandler, @Nullable Handler workerThreadHandler,
- RecordingTaskFactory recordingTaskFactory) {
- if (DEBUG) Log.d(TAG, "Creating scheduler for " + input);
- mContext = context;
- mInput = input;
- mLooper = looper;
- mChannelDataManager = channelDataManager;
- mDvrManager = dvrManager;
- mDataManager = (WritableDvrDataManager) dataManager;
- mSessionManager = sessionManager;
- mClock = clock;
- mMainThreadHandler = mainThreadHandler;
- mRecordingTaskFactory = recordingTaskFactory != null ? recordingTaskFactory
- : new RecordingTaskFactory() {
- @Override
- public RecordingTask createRecordingTask(ScheduledRecording schedule, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock) {
- return new RecordingTask(mContext, schedule, channel, mDvrManager, mSessionManager,
- mDataManager, mClock);
- }
- };
- if (workerThreadHandler == null) {
- mHandler = new WorkerThreadHandler(looper);
- } else {
- mHandler = workerThreadHandler;
- }
- }
-
- /**
- * Adds a {@link ScheduledRecording}.
- */
- public void addSchedule(ScheduledRecording schedule) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule));
- }
-
- @VisibleForTesting
- void handleAddSchedule(ScheduledRecording schedule) {
- if (mPendingRecordings.get(schedule.getId()) != null
- || mWaitingSchedules.containsKey(schedule.getId())) {
- return;
- }
- mWaitingSchedules.put(schedule.getId(), schedule);
- mHandler.removeMessages(MSG_BUILD_SCHEDULE);
- mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
- }
-
- /**
- * Removes the {@link ScheduledRecording}.
- */
- public void removeSchedule(ScheduledRecording schedule) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule));
- }
-
- @VisibleForTesting
- void handleRemoveSchedule(ScheduledRecording schedule) {
- HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId());
- if (wrapper != null) {
- wrapper.mTask.cancel();
- return;
- }
- if (mWaitingSchedules.containsKey(schedule.getId())) {
- mWaitingSchedules.remove(schedule.getId());
- mHandler.removeMessages(MSG_BUILD_SCHEDULE);
- mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
- }
- }
-
- /**
- * Updates the {@link ScheduledRecording}.
- */
- public void updateSchedule(ScheduledRecording schedule) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule));
- }
-
- @VisibleForTesting
- void handleUpdateSchedule(ScheduledRecording schedule) {
- HandlerWrapper wrapper = mPendingRecordings.get(schedule.getId());
- if (wrapper != null) {
- if (schedule.getStartTimeMs() > mClock.currentTimeMillis()
- && schedule.getStartTimeMs() > wrapper.mTask.getStartTimeMs()) {
- // It shouldn't have started. Cancel and put to the waiting list.
- // The schedules will be rebuilt when the task is removed.
- // The reschedule is called in Scheduler.
- wrapper.mTask.cancel();
- mWaitingSchedules.put(schedule.getId(), schedule);
- return;
- }
- wrapper.sendMessage(wrapper.obtainMessage(RecordingTask.MSG_UDPATE_SCHEDULE, schedule));
- return;
- }
- if (mWaitingSchedules.containsKey(schedule.getId())) {
- mWaitingSchedules.put(schedule.getId(), schedule);
- mHandler.removeMessages(MSG_BUILD_SCHEDULE);
- mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
- }
- }
-
- /**
- * Updates the TV input.
- */
- public void updateTvInputInfo(TvInputInfo input) {
- synchronized (mInputLock) {
- mInput = input;
- }
- }
-
- /**
- * Stops the input task scheduler.
- */
- public void stop() {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE);
- }
-
- private void handleStopSchedule() {
- mWaitingSchedules.clear();
- int size = mPendingRecordings.size();
- for (int i = 0; i < size; ++i) {
- RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
- task.cleanUp();
- }
- }
-
- @VisibleForTesting
- void handleBuildSchedule() {
- if (mWaitingSchedules.isEmpty()) {
- return;
- }
- long currentTimeMs = mClock.currentTimeMillis();
- // Remove past schedules.
- for (Iterator<ScheduledRecording> iter = mWaitingSchedules.values().iterator();
- iter.hasNext(); ) {
- ScheduledRecording schedule = iter.next();
- if (schedule.getEndTimeMs() - currentTimeMs
- <= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) {
- fail(schedule);
- iter.remove();
- }
- }
- if (mWaitingSchedules.isEmpty()) {
- return;
- }
- // Record the schedules which should start now.
- List<ScheduledRecording> schedulesToStart = new ArrayList<>();
- for (ScheduledRecording schedule : mWaitingSchedules.values()) {
- if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED
- && schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS
- <= currentTimeMs && schedule.getEndTimeMs() > currentTimeMs) {
- schedulesToStart.add(schedule);
- }
- }
- // The schedules will be executed with the following order.
- // 1. The schedule which starts early. It can be replaced later when the schedule with the
- // higher priority needs to start.
- // 2. The schedule with the higher priority. It can be replaced later when the schedule with
- // the higher priority needs to start.
- // 3. The schedule which was created recently.
- Collections.sort(schedulesToStart, getRecordingOrderComparator());
- int tunerCount;
- synchronized (mInputLock) {
- tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0;
- }
- for (ScheduledRecording schedule : schedulesToStart) {
- if (hasTaskWhichFinishEarlier(schedule)) {
- // If there is a schedule which finishes earlier than the new schedule, rebuild the
- // schedules after it finishes.
- return;
- }
- if (mPendingRecordings.size() < tunerCount) {
- // Tuners available.
- createRecordingTask(schedule).start();
- mWaitingSchedules.remove(schedule.getId());
- } else {
- // No available tuners.
- RecordingTask task = getReplacableTask(schedule);
- if (task != null) {
- task.stop();
- // Just return. The schedules will be rebuilt after the task is stopped.
- return;
- }
- }
- }
- if (mWaitingSchedules.isEmpty()) {
- return;
- }
- // Set next scheduling.
- long earliest = Long.MAX_VALUE;
- for (ScheduledRecording schedule : mWaitingSchedules.values()) {
- // The conflicting schedules will be removed if they end before conflicting resolved.
- if (schedulesToStart.contains(schedule)) {
- if (earliest > schedule.getEndTimeMs()) {
- earliest = schedule.getEndTimeMs();
- }
- } else {
- if (earliest > schedule.getStartTimeMs()
- - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) {
- earliest = schedule.getStartTimeMs()
- - RecordingTask.RECORDING_EARLY_START_OFFSET_MS;
- }
- }
- }
- mHandler.sendEmptyMessageDelayed(MSG_BUILD_SCHEDULE, earliest - currentTimeMs);
- }
-
- private RecordingTask createRecordingTask(ScheduledRecording schedule) {
- Channel channel = mChannelDataManager.getChannel(schedule.getChannelId());
- RecordingTask recordingTask = mRecordingTaskFactory.createRecordingTask(schedule, channel,
- mDvrManager, mSessionManager, mDataManager, mClock);
- HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask);
- mPendingRecordings.put(schedule.getId(), handlerWrapper);
- return recordingTask;
- }
-
- private boolean hasTaskWhichFinishEarlier(ScheduledRecording schedule) {
- int size = mPendingRecordings.size();
- for (int i = 0; i < size; ++i) {
- RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
- if (task.getEndTimeMs() <= schedule.getStartTimeMs()) {
- return true;
- }
- }
- return false;
- }
-
- private RecordingTask getReplacableTask(ScheduledRecording schedule) {
- // Returns the recording with the following priority.
- // 1. The recording with the lowest priority is returned.
- // 2. If the priorities are the same, the recording which finishes early is returned.
- // 3. If 1) and 2) are the same, the early created schedule is returned.
- int size = mPendingRecordings.size();
- RecordingTask candidate = null;
- for (int i = 0; i < size; ++i) {
- RecordingTask task = mPendingRecordings.get(mPendingRecordings.keyAt(i)).mTask;
- if (schedule.getPriority() > task.getPriority()) {
- if (candidate == null || CANDIDATE_COMPARATOR.compare(candidate, task) > 0) {
- candidate = task;
- }
- }
- }
- return candidate;
- }
-
- private void fail(ScheduledRecording schedule) {
- // It's called when the scheduling has been failed without creating RecordingTask.
- runOnMainHandler(new Runnable() {
- @Override
- public void run() {
- ScheduledRecording scheduleInManager =
- mDataManager.getScheduledRecording(schedule.getId());
- if (scheduleInManager != null) {
- // The schedule should be updated based on the object from DataManager in case
- // when it has been updated.
- mDataManager.changeState(scheduleInManager,
- ScheduledRecording.STATE_RECORDING_FAILED);
- }
- }
- });
- }
-
- private void runOnMainHandler(Runnable runnable) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- runnable.run();
- } else {
- mMainThreadHandler.post(runnable);
- }
- }
-
- @VisibleForTesting
- interface RecordingTaskFactory {
- RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock);
- }
-
- private class WorkerThreadHandler extends Handler {
- public WorkerThreadHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ADD_SCHEDULED_RECORDING:
- handleAddSchedule((ScheduledRecording) msg.obj);
- break;
- case MSG_REMOVE_SCHEDULED_RECORDING:
- handleRemoveSchedule((ScheduledRecording) msg.obj);
- break;
- case MSG_UPDATE_SCHEDULED_RECORDING:
- handleUpdateSchedule((ScheduledRecording) msg.obj);
- case MSG_BUILD_SCHEDULE:
- handleBuildSchedule();
- break;
- case MSG_STOP_SCHEDULE:
- handleStopSchedule();
- break;
- }
- }
- }
-}