aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/RecordingTask.java
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2016-08-31 16:00:31 -0700
committerNick Chalko <nchalko@google.com>2016-09-07 05:38:33 -0700
commit65fda1eaa94968bb55d5ded10dcb0b3f37fb05f2 (patch)
treeffc8e4c5a71c130d3782bf03e674f9d77ca77f72 /src/com/android/tv/dvr/RecordingTask.java
parentad819718f80e796cf039f96537b5c8cd127c042b (diff)
downloadTV-65fda1eaa94968bb55d5ded10dcb0b3f37fb05f2.tar.gz
Sync to ub-tv-dev at http://ag/1415258
Bug: 30970843 Change-Id: I0aa43094d103de28956a3d9b56a594ea46a20543
Diffstat (limited to 'src/com/android/tv/dvr/RecordingTask.java')
-rw-r--r--src/com/android/tv/dvr/RecordingTask.java302
1 files changed, 218 insertions, 84 deletions
diff --git a/src/com/android/tv/dvr/RecordingTask.java b/src/com/android/tv/dvr/RecordingTask.java
index 804485b3..2373f15c 100644
--- a/src/com/android/tv/dvr/RecordingTask.java
+++ b/src/com/android/tv/dvr/RecordingTask.java
@@ -16,18 +16,28 @@
package com.android.tv.dvr;
+import android.annotation.TargetApi;
+import android.content.Context;
import android.media.tv.TvContract;
-import android.media.tv.TvRecordingClient;
+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;
@@ -40,22 +50,33 @@ import java.util.concurrent.TimeUnit;
* There is only one looper so messages must be handled quickly or start a separate thread.
*/
@WorkerThread
-class RecordingTask extends TvRecordingClient.RecordingCallback
- implements Handler.Callback, DvrManager.Listener {
+@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;
@VisibleForTesting
- static final int MESSAGE_INIT = 1;
+ static final int MSG_INITIALIZE = 1;
@VisibleForTesting
- static final int MESSAGE_START_RECORDING = 2;
+ static final int MSG_START_RECORDING = 2;
@VisibleForTesting
- static final int MESSAGE_STOP_RECORDING = 3;
-
- @VisibleForTesting
- static final long MS_BEFORE_START = TimeUnit.SECONDS.toMillis(5);
- @VisibleForTesting
- static final long MS_AFTER_END = TimeUnit.SECONDS.toMillis(5);
+ 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 {
@@ -63,27 +84,32 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
SESSION_ACQUIRED,
CONNECTION_PENDING,
CONNECTED,
- RECORDING_START_REQUESTED,
RECORDING_STARTED,
RECORDING_STOP_REQUESTED,
+ FINISHED,
ERROR,
RELEASED,
}
- private final DvrSessionManager mSessionManager;
+ 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 TvRecordingClient mTvRecordingClient;
+ 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(ScheduledRecording scheduledRecording, Channel channel,
- DvrManager dvrManager, DvrSessionManager sessionManager,
+ RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel,
+ DvrManager dvrManager, InputSessionManager sessionManager,
WritableDvrDataManager dataManager, Clock clock) {
+ mContext = context;
mScheduledRecording = scheduledRecording;
mChannel = channel;
mSessionManager = sessionManager;
@@ -101,27 +127,30 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
@Override
public boolean handleMessage(Message msg) {
if (DEBUG) Log.d(TAG, "handleMessage " + msg);
- SoftPreconditions
- .checkState(msg.what == Scheduler.HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
- TAG, "Null handler trying to handle " + msg);
+ SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
+ TAG, "Null handler trying to handle " + msg);
try {
switch (msg.what) {
- case MESSAGE_INIT:
+ case MSG_INITIALIZE:
handleInit();
break;
- case MESSAGE_START_RECORDING:
+ case MSG_START_RECORDING:
handleStartRecording();
break;
- case MESSAGE_STOP_RECORDING:
+ case MSG_STOP_RECORDING:
handleStopRecording();
break;
- case Scheduler.HandlerWrapper.MESSAGE_REMOVE:
- // Clear the handler
+ 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) {
@@ -132,54 +161,83 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
}
@Override
+ public void onDisconnected(String inputId) {
+ if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")");
+ if (mRecordingSession != null && mState != State.FINISHED) {
+ failAndQuit();
+ }
+ }
+
+ @Override
public void onTuned(Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "onTuned");
+ if (DEBUG) Log.d(TAG, "onTuned");
+ if (mRecordingSession == null) {
+ return;
}
- super.onTuned(channelUri);
mState = State.CONNECTED;
- if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MESSAGE_START_RECORDING,
- mScheduledRecording.getStartTimeMs() - MS_BEFORE_START)) {
- mState = State.ERROR;
- return;
+ if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING,
+ mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) {
+ failAndQuit();
}
}
-
@Override
public void onRecordingStopped(Uri recordedProgramUri) {
- super.onRecordingStopped(recordedProgramUri);
- mState = State.CONNECTED;
- updateRecording(ScheduledRecording.buildFrom(mScheduledRecording)
- .setState(ScheduledRecording.STATE_RECORDING_FINISHED).build());
+ 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);
- super.onError(reason);
- // TODO(dvr) handle success
+ 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:
- updateRecording(ScheduledRecording.buildFrom(mScheduledRecording)
- .setState(ScheduledRecording.STATE_RECORDING_FAILED)
- .build());
+ failAndQuit();
+ break;
}
- release();
- sendRemove();
}
private void handleInit() {
if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording);
- //TODO check recording preconditions
-
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();
@@ -193,18 +251,11 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
}
String inputId = mChannel.getInputId();
- if (mSessionManager.canAcquireDvrSession(inputId, mChannel)) {
- mTvRecordingClient = mSessionManager
- .createTvRecordingClient("recordingTask-" + mScheduledRecording.getId(), this,
- mHandler);
- mState = State.SESSION_ACQUIRED;
- } else {
- Log.w(TAG, "Unable to acquire a session for " + mScheduledRecording);
- failAndQuit();
- return;
- }
+ mRecordingSession = mSessionManager.createRecordingSession(inputId,
+ "recordingTask-" + mScheduledRecording.getId(), this, mHandler);
+ mState = State.SESSION_ACQUIRED;
mDvrManager.addListener(this, mHandler);
- mTvRecordingClient.tune(inputId, mChannel.getUri());
+ mRecordingSession.tune(inputId, mChannel.getUri());
mState = State.CONNECTION_PENDING;
}
@@ -218,41 +269,78 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
private void sendRemove() {
if (DEBUG) Log.d(TAG, "sendRemove");
if (mHandler != null) {
- mHandler.sendEmptyMessage(Scheduler.HandlerWrapper.MESSAGE_REMOVE);
+ mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(
+ HandlerWrapper.MESSAGE_REMOVE));
}
}
private void handleStartRecording() {
if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording);
- // TODO(DVR) handle errors
long programId = mScheduledRecording.getProgramId();
- mTvRecordingClient.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null
+ mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null
: TvContract.buildProgramUri(programId));
- updateRecording(ScheduledRecording.buildFrom(mScheduledRecording)
- .setState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS).build());
+ 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 (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MESSAGE_STOP_RECORDING,
- mScheduledRecording.getEndTimeMs() + MS_AFTER_END)) {
- mState = State.ERROR;
- return;
+ if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING,
+ mScheduledRecording.getEndTimeMs())) {
+ failAndQuit();
}
}
private void handleStopRecording() {
if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording);
- mTvRecordingClient.stopRecording();
+ 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()
+ && mState == State.RECORDING_STARTED) {
+ mHandler.removeMessages(MSG_STOP_RECORDING);
+ if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) {
+ failAndQuit();
+ }
+ }
+ }
+
@VisibleForTesting
State getState() {
return mState;
}
+ /**
+ * 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 (mTvRecordingClient != null) {
- mSessionManager.releaseTvRecordingClient(mTvRecordingClient);
+ if (mRecordingSession != null) {
+ mSessionManager.releaseRecordingSession(mRecordingSession);
+ mRecordingSession = null;
}
mDvrManager.removeListener(this);
}
@@ -268,22 +356,24 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
}
private void updateRecordingState(@ScheduledRecording.RecordingState int state) {
- updateRecording(ScheduledRecording.buildFrom(mScheduledRecording).setState(state).build());
- }
-
- @VisibleForTesting
- static Uri getIdAsMediaUri(ScheduledRecording scheduledRecording) {
- // TODO define the URI format
- return new Uri.Builder().appendPath(String.valueOf(scheduledRecording.getId())).build();
- }
-
- private void updateRecording(ScheduledRecording updatedScheduledRecording) {
- if (DEBUG) Log.d(TAG, "updateScheduledRecording " + updatedScheduledRecording);
- mScheduledRecording = updatedScheduledRecording;
+ if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state);
+ mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state)
+ .build();
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
- mDataManager.updateScheduledRecording(mScheduledRecording);
+ 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());
+ }
}
});
}
@@ -293,9 +383,24 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
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(MESSAGE_STOP_RECORDING);
+ mHandler.removeMessages(MSG_STOP_RECORDING);
handleStopRecording();
break;
case RECORDING_STOP_REQUESTED:
@@ -305,7 +410,7 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
case SESSION_ACQUIRED:
case CONNECTION_PENDING:
case CONNECTED:
- case RECORDING_START_REQUESTED:
+ case FINISHED:
case ERROR:
case RELEASED:
default:
@@ -314,8 +419,37 @@ class RecordingTask extends TvRecordingClient.RecordingCallback
}
}
+ /**
+ * Cancels the task
+ */
+ public void cancel() {
+ if (DEBUG) Log.d(TAG, "cancel");
+ mCanceled = true;
+ stop();
+ removeRecordedProgram();
+ }
+
@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);
+ }
+ }
}