aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/RecordingTask.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr/RecordingTask.java')
-rw-r--r--src/com/android/tv/dvr/RecordingTask.java519
1 files changed, 0 insertions, 519 deletions
diff --git a/src/com/android/tv/dvr/RecordingTask.java b/src/com/android/tv/dvr/RecordingTask.java
deleted file mode 100644
index c3d236b0..00000000
--- a/src/com/android/tv/dvr/RecordingTask.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Copyright (C) 2015 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.annotation.TargetApi;
-import android.content.Context;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvRecordingClient.RecordingCallback;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.android.tv.InputSessionManager;
-import com.android.tv.InputSessionManager.RecordingSession;
-import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
-import com.android.tv.dvr.InputTaskScheduler.HandlerWrapper;
-import com.android.tv.util.Clock;
-import com.android.tv.util.Utils;
-
-import java.util.Comparator;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A Handler that actually starts and stop a recording at the right time.
- *
- * <p>This is run on the looper of thread named {@value DvrRecordingService#HANDLER_THREAD_NAME}.
- * There is only one looper so messages must be handled quickly or start a separate thread.
- */
-@WorkerThread
-@VisibleForTesting
-@TargetApi(Build.VERSION_CODES.N)
-public class RecordingTask extends RecordingCallback implements Handler.Callback,
- DvrManager.Listener {
- private static final String TAG = "RecordingTask";
- private static final boolean DEBUG = false;
-
- /**
- * Compares the end time in ascending order.
- */
- public static final Comparator<RecordingTask> END_TIME_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs());
- }
- };
-
- /**
- * Compares ID in ascending order.
- */
- public static final Comparator<RecordingTask> ID_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getScheduleId(), rhs.getScheduleId());
- }
- };
-
- /**
- * Compares the priority in ascending order.
- */
- public static final Comparator<RecordingTask> PRIORITY_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getPriority(), rhs.getPriority());
- }
- };
-
- @VisibleForTesting
- static final int MSG_INITIALIZE = 1;
- @VisibleForTesting
- static final int MSG_START_RECORDING = 2;
- @VisibleForTesting
- static final int MSG_STOP_RECORDING = 3;
- /**
- * Message to update schedule.
- */
- public static final int MSG_UDPATE_SCHEDULE = 4;
-
- /**
- * The time when the start command will be sent before the recording starts.
- */
- public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3);
- /**
- * If the recording starts later than the scheduled start time or ends before the scheduled end
- * time, it's considered as clipped.
- */
- private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
-
- @VisibleForTesting
- enum State {
- NOT_STARTED,
- SESSION_ACQUIRED,
- CONNECTION_PENDING,
- CONNECTED,
- RECORDING_STARTED,
- RECORDING_STOP_REQUESTED,
- FINISHED,
- ERROR,
- RELEASED,
- }
- private final InputSessionManager mSessionManager;
- private final DvrManager mDvrManager;
- private final Context mContext;
-
- private final WritableDvrDataManager mDataManager;
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
- private RecordingSession mRecordingSession;
- private Handler mHandler;
- private ScheduledRecording mScheduledRecording;
- private final Channel mChannel;
- private State mState = State.NOT_STARTED;
- private final Clock mClock;
- private boolean mStartedWithClipping;
- private Uri mRecordedProgramUri;
- private boolean mCanceled;
-
- RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock) {
- mContext = context;
- mScheduledRecording = scheduledRecording;
- mChannel = channel;
- mSessionManager = sessionManager;
- mDataManager = dataManager;
- mClock = clock;
- mDvrManager = dvrManager;
-
- if (DEBUG) Log.d(TAG, "created recording task " + mScheduledRecording);
- }
-
- public void setHandler(Handler handler) {
- mHandler = handler;
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- if (DEBUG) Log.d(TAG, "handleMessage " + msg);
- SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
- TAG, "Null handler trying to handle " + msg);
- try {
- switch (msg.what) {
- case MSG_INITIALIZE:
- handleInit();
- break;
- case MSG_START_RECORDING:
- handleStartRecording();
- break;
- case MSG_STOP_RECORDING:
- handleStopRecording();
- break;
- case MSG_UDPATE_SCHEDULE:
- handleUpdateSchedule((ScheduledRecording) msg.obj);
- break;
- case HandlerWrapper.MESSAGE_REMOVE:
- mHandler.removeCallbacksAndMessages(null);
- mHandler = null;
- release();
- return false;
- default:
- SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg);
- break;
- }
- return true;
- } catch (Exception e) {
- Log.w(TAG, "Error processing message " + msg + " for " + mScheduledRecording, e);
- failAndQuit();
- }
- return false;
- }
-
- @Override
- public void onDisconnected(String inputId) {
- if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")");
- if (mRecordingSession != null && mState != State.FINISHED) {
- failAndQuit();
- }
- }
-
- @Override
- public void onConnectionFailed(String inputId) {
- if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")");
- if (mRecordingSession != null) {
- failAndQuit();
- }
- }
-
- @Override
- public void onTuned(Uri channelUri) {
- if (DEBUG) Log.d(TAG, "onTuned");
- if (mRecordingSession == null) {
- return;
- }
- mState = State.CONNECTED;
- if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING,
- mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) {
- failAndQuit();
- }
- }
-
- @Override
- public void onRecordingStopped(Uri recordedProgramUri) {
- if (DEBUG) Log.d(TAG, "onRecordingStopped");
- if (mRecordingSession == null) {
- return;
- }
- mRecordedProgramUri = recordedProgramUri;
- mState = State.FINISHED;
- int state = ScheduledRecording.STATE_RECORDING_FINISHED;
- if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS
- > mClock.currentTimeMillis()) {
- state = ScheduledRecording.STATE_RECORDING_CLIPPED;
- }
- updateRecordingState(state);
- sendRemove();
- if (mCanceled) {
- removeRecordedProgram();
- }
- }
-
- @Override
- public void onError(int reason) {
- if (DEBUG) Log.d(TAG, "onError reason " + reason);
- if (mRecordingSession == null) {
- return;
- }
- switch (reason) {
- case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE:
- mMainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- if (TvApplication.getSingletons(mContext).getMainActivityWrapper()
- .isResumed()) {
- Toast.makeText(mContext.getApplicationContext(),
- R.string.dvr_error_insufficient_space_description,
- Toast.LENGTH_LONG)
- .show();
- } else {
- Utils.setRecordingFailedReason(mContext.getApplicationContext(),
- TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- }
- }
- });
- // Pass through
- default:
- failAndQuit();
- break;
- }
- }
-
- private void handleInit() {
- if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording);
- if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) {
- Log.w(TAG, "End time already past, not recording " + mScheduledRecording);
- failAndQuit();
- return;
- }
- if (mChannel == null) {
- Log.w(TAG, "Null channel for " + mScheduledRecording);
- failAndQuit();
- return;
- }
- if (mChannel.getId() != mScheduledRecording.getChannelId()) {
- Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording "
- + mScheduledRecording);
- failAndQuit();
- return;
- }
-
- String inputId = mChannel.getInputId();
- mRecordingSession = mSessionManager.createRecordingSession(inputId,
- "recordingTask-" + mScheduledRecording.getId(), this,
- mHandler, mScheduledRecording.getEndTimeMs());
- mState = State.SESSION_ACQUIRED;
- mDvrManager.addListener(this, mHandler);
- mRecordingSession.tune(inputId, mChannel.getUri());
- mState = State.CONNECTION_PENDING;
- }
-
- private void failAndQuit() {
- if (DEBUG) Log.d(TAG, "failAndQuit");
- updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
- mState = State.ERROR;
- sendRemove();
- }
-
- private void sendRemove() {
- if (DEBUG) Log.d(TAG, "sendRemove");
- if (mHandler != null) {
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(
- HandlerWrapper.MESSAGE_REMOVE));
- }
- }
-
- private void handleStartRecording() {
- if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording);
- long programId = mScheduledRecording.getProgramId();
- mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null
- : TvContract.buildProgramUri(programId));
- updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS);
- // If it starts late, it's clipped.
- if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS
- < mClock.currentTimeMillis()) {
- mStartedWithClipping = true;
- }
- mState = State.RECORDING_STARTED;
-
- if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING,
- mScheduledRecording.getEndTimeMs())) {
- failAndQuit();
- }
- }
-
- private void handleStopRecording() {
- if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording);
- mRecordingSession.stopRecording();
- mState = State.RECORDING_STOP_REQUESTED;
- }
-
- private void handleUpdateSchedule(ScheduledRecording schedule) {
- mScheduledRecording = schedule;
- // Check end time only. The start time is checked in InputTaskScheduler.
- if (schedule.getEndTimeMs() != mScheduledRecording.getEndTimeMs()) {
- if (mRecordingSession != null) {
- mRecordingSession.setEndTimeMs(schedule.getEndTimeMs());
- }
- if (mState == State.RECORDING_STARTED) {
- mHandler.removeMessages(MSG_STOP_RECORDING);
- if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) {
- failAndQuit();
- }
- }
- }
- }
-
- @VisibleForTesting
- State getState() {
- return mState;
- }
-
- private long getScheduleId() {
- return mScheduledRecording.getId();
- }
-
- /**
- * Returns the priority.
- */
- public long getPriority() {
- return mScheduledRecording.getPriority();
- }
-
- /**
- * Returns the start time of the recording.
- */
- public long getStartTimeMs() {
- return mScheduledRecording.getStartTimeMs();
- }
-
- /**
- * Returns the end time of the recording.
- */
- public long getEndTimeMs() {
- return mScheduledRecording.getEndTimeMs();
- }
-
- private void release() {
- if (mRecordingSession != null) {
- mSessionManager.releaseRecordingSession(mRecordingSession);
- mRecordingSession = null;
- }
- mDvrManager.removeListener(this);
- }
-
- private boolean sendEmptyMessageAtAbsoluteTime(int what, long when) {
- long now = mClock.currentTimeMillis();
- long delay = Math.max(0L, when - now);
- if (DEBUG) {
- Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000
- + " seconds to arrive at " + Utils.toIsoDateTimeString(when));
- }
- return mHandler.sendEmptyMessageDelayed(what, delay);
- }
-
- private void updateRecordingState(@ScheduledRecording.RecordingState int state) {
- if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state);
- mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state)
- .build();
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- ScheduledRecording schedule = mDataManager.getScheduledRecording(
- mScheduledRecording.getId());
- if (schedule == null) {
- // Schedule has been deleted. Delete the recorded program.
- removeRecordedProgram();
- } else {
- // Update the state based on the object in DataManager in case when it has been
- // updated. mScheduledRecording will be updated from
- // onScheduledRecordingStateChanged.
- mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule)
- .setState(state).build());
- }
- }
- });
- }
-
- @Override
- public void onStopRecordingRequested(ScheduledRecording recording) {
- if (recording.getId() != mScheduledRecording.getId()) {
- return;
- }
- stop();
- }
-
- /**
- * Starts the task.
- */
- public void start() {
- mHandler.sendEmptyMessage(MSG_INITIALIZE);
- }
-
- /**
- * Stops the task.
- */
- public void stop() {
- if (DEBUG) Log.d(TAG, "stop");
- switch (mState) {
- case RECORDING_STARTED:
- mHandler.removeMessages(MSG_STOP_RECORDING);
- handleStopRecording();
- break;
- case RECORDING_STOP_REQUESTED:
- // Do nothing
- break;
- case NOT_STARTED:
- case SESSION_ACQUIRED:
- case CONNECTION_PENDING:
- case CONNECTED:
- case FINISHED:
- case ERROR:
- case RELEASED:
- default:
- sendRemove();
- break;
- }
- }
-
- /**
- * Cancels the task
- */
- public void cancel() {
- if (DEBUG) Log.d(TAG, "cancel");
- mCanceled = true;
- stop();
- removeRecordedProgram();
- }
-
- /**
- * Clean up the task.
- */
- public void cleanUp() {
- if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) {
- updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
- }
- release();
- if (mHandler != null) {
- mHandler.removeCallbacksAndMessages(null);
- }
- }
-
- @Override
- public String toString() {
- return getClass().getName() + "(" + mScheduledRecording + ")";
- }
-
- private void removeRecordedProgram() {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mRecordedProgramUri != null) {
- mDvrManager.removeRecordedProgram(mRecordedProgramUri);
- }
- }
- });
- }
-
- private void runOnMainThread(Runnable runnable) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- runnable.run();
- } else {
- mMainThreadHandler.post(runnable);
- }
- }
-}