diff options
author | shubang <shubang@google.com> | 2018-02-23 17:08:03 -0800 |
---|---|---|
committer | Nick Chalko <nchalko@google.com> | 2018-02-26 15:42:56 -0800 |
commit | 9850ee71f931f597658b39fba8fd18bead506955 (patch) | |
tree | c900aad62a7ab95bbeb25e1b32ae2fae2fa5e8b7 | |
parent | 9d283c91d496eac51237ced05d649081bb08d3e1 (diff) | |
download | TV-9850ee71f931f597658b39fba8fd18bead506955.tar.gz |
Write DVR failed reasons to DB
PiperOrigin-RevId: 186845549
Change-Id: I327129322ade4ef3ef0110bb51ec8f172c7a99ec
10 files changed, 206 insertions, 27 deletions
diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index 4a04de4e..b8bffa18 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -258,6 +258,19 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } @Override + public void changeState( + ScheduledRecording scheduledRecording, @RecordingState int newState, int reason) { + if (scheduledRecording.getState() != newState) { + ScheduledRecording.Builder builder = + ScheduledRecording.buildFrom(scheduledRecording).setState(newState); + if (newState == ScheduledRecording.STATE_RECORDING_FAILED) { + builder.setFailedReason(reason); + } + updateScheduledRecording(builder.build()); + } + } + + @Override public Collection<ScheduledRecording> getDeletedSchedules() { return mDeletedScheduleMap.values(); } diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index c74aa208..2b4ecbf5 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -228,6 +228,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { protected void onPostExecute(List<ScheduledRecording> result) { mPendingTasks.remove(this); long maxId = 0; + int reasonNotStarted = + ScheduledRecording + .FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED; List<ScheduledRecording> toUpdate = new ArrayList<>(); List<ScheduledRecording> toDelete = new ArrayList<>(); for (ScheduledRecording r : result) { @@ -244,11 +247,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { switch (r.getState()) { case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: if (r.getEndTimeMs() <= mClock.currentTimeMillis()) { + int reason = + ScheduledRecording.FAILED_REASON_NOT_FINISHED; toUpdate.add( ScheduledRecording.buildFrom(r) .setState( ScheduledRecording .STATE_RECORDING_FAILED) + .setFailedReason(reason) .build()); } else { toUpdate.add( @@ -266,6 +272,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { .setState( ScheduledRecording .STATE_RECORDING_FAILED) + .setFailedReason(reasonNotStarted) .build()); } break; diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java index 059b0a6d..1b505e80 100644 --- a/src/com/android/tv/dvr/WritableDvrDataManager.java +++ b/src/com/android/tv/dvr/WritableDvrDataManager.java @@ -57,6 +57,14 @@ public interface WritableDvrDataManager extends DvrDataManager { void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState); /** + * Changes the state of the recording. + * + * @param reason the reason of this change + */ + void changeState( + ScheduledRecording scheduledRecording, @RecordingState int newState, int reason); + + /** * Remove all the records related to the input. * * <p>Note that this should be called after the input was removed. diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java index 851aabc3..7c2d12d9 100644 --- a/src/com/android/tv/dvr/data/ScheduledRecording.java +++ b/src/com/android/tv/dvr/data/ScheduledRecording.java @@ -342,7 +342,35 @@ public final class ScheduledRecording implements Parcelable { public static final int STATE_RECORDING_DELETED = 5; public static final int STATE_RECORDING_CANCELED = 6; + /** The reasons of failed recordings */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FAILED_REASON_OTHER, + FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED, + FAILED_REASON_NOT_FINISHED, + FAILED_REASON_SCHEDULER_STOPPED, + FAILED_REASON_INVALID_CHANNEL, + FAILED_REASON_MESSAGE_NOT_SENT, + FAILED_REASON_CONNECTION_FAILED, + FAILED_REASON_RESOURCE_BUSY, + FAILED_REASON_INPUT_UNAVAILABLE, + FAILED_REASON_INPUT_DVR_UNSUPPORTED, + FAILED_REASON_INSUFFICIENT_SPACE + }) + public @interface RecordingFailedReason {} + public static final int FAILED_REASON_OTHER = 0; + public static final int FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED = 1; + public static final int FAILED_REASON_NOT_FINISHED = 2; + public static final int FAILED_REASON_SCHEDULER_STOPPED = 3; + public static final int FAILED_REASON_INVALID_CHANNEL = 4; + public static final int FAILED_REASON_MESSAGE_NOT_SENT = 5; + public static final int FAILED_REASON_CONNECTION_FAILED = 6; + public static final int FAILED_REASON_RESOURCE_BUSY = 7; + // For the following reasons, show advice to users + public static final int FAILED_REASON_INPUT_UNAVAILABLE = 8; + public static final int FAILED_REASON_INPUT_DVR_UNSUPPORTED = 9; + public static final int FAILED_REASON_INSUFFICIENT_SPACE = 10; @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_TIMED, TYPE_PROGRAM}) @@ -651,7 +679,7 @@ public final class ScheduledRecording implements Parcelable { } /** Returns the failed reason of the {@link ScheduledRecording}. */ - @Nullable + @Nullable @RecordingFailedReason public Integer getFailedReason() { return mFailedReason; } @@ -793,6 +821,26 @@ public final class ScheduledRecording implements Parcelable { return null; } switch (reason) { + case Schedules.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED: + return FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED; + case Schedules.FAILED_REASON_NOT_FINISHED: + return FAILED_REASON_NOT_FINISHED; + case Schedules.FAILED_REASON_SCHEDULER_STOPPED: + return FAILED_REASON_SCHEDULER_STOPPED; + case Schedules.FAILED_REASON_INVALID_CHANNEL: + return FAILED_REASON_INVALID_CHANNEL; + case Schedules.FAILED_REASON_MESSAGE_NOT_SENT: + return FAILED_REASON_MESSAGE_NOT_SENT; + case Schedules.FAILED_REASON_CONNECTION_FAILED: + return FAILED_REASON_CONNECTION_FAILED; + case Schedules.FAILED_REASON_RESOURCE_BUSY: + return FAILED_REASON_RESOURCE_BUSY; + case Schedules.FAILED_REASON_INPUT_UNAVAILABLE: + return FAILED_REASON_INPUT_UNAVAILABLE; + case Schedules.FAILED_REASON_INPUT_DVR_UNSUPPORTED: + return FAILED_REASON_INPUT_DVR_UNSUPPORTED; + case Schedules.FAILED_REASON_INSUFFICIENT_SPACE: + return FAILED_REASON_INSUFFICIENT_SPACE; case Schedules.FAILED_REASON_OTHER: default: return FAILED_REASON_OTHER; @@ -808,7 +856,27 @@ public final class ScheduledRecording implements Parcelable { return null; } switch (reason) { - // TODO(b/72638385): add reasons + case FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED: + return Schedules.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED; + case FAILED_REASON_NOT_FINISHED: + return Schedules.FAILED_REASON_NOT_FINISHED; + case FAILED_REASON_SCHEDULER_STOPPED: + return Schedules.FAILED_REASON_SCHEDULER_STOPPED; + case FAILED_REASON_INVALID_CHANNEL: + return Schedules.FAILED_REASON_INVALID_CHANNEL; + case FAILED_REASON_MESSAGE_NOT_SENT: + return Schedules.FAILED_REASON_MESSAGE_NOT_SENT; + case FAILED_REASON_CONNECTION_FAILED: + return Schedules.FAILED_REASON_CONNECTION_FAILED; + case FAILED_REASON_RESOURCE_BUSY: + return Schedules.FAILED_REASON_RESOURCE_BUSY; + case FAILED_REASON_INPUT_UNAVAILABLE: + return Schedules.FAILED_REASON_INPUT_UNAVAILABLE; + case FAILED_REASON_INPUT_DVR_UNSUPPORTED: + return Schedules.FAILED_REASON_INPUT_DVR_UNSUPPORTED; + case FAILED_REASON_INSUFFICIENT_SPACE: + return Schedules.FAILED_REASON_INSUFFICIENT_SPACE; + case FAILED_REASON_OTHER: // fall through default: return Schedules.FAILED_REASON_OTHER; } diff --git a/src/com/android/tv/dvr/provider/DvrContract.java b/src/com/android/tv/dvr/provider/DvrContract.java index e4f505ac..a5f2e2cd 100644 --- a/src/com/android/tv/dvr/provider/DvrContract.java +++ b/src/com/android/tv/dvr/provider/DvrContract.java @@ -58,6 +58,49 @@ public final class DvrContract { /** The recording failed reason for other reasons */ public static final String FAILED_REASON_OTHER = "FAILED_REASON_OTHER"; + /** The recording failed because the program ended before recording started. */ + public static final String FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED = + "FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED"; + + /** The recording failed because it was not finished successfully */ + public static final String FAILED_REASON_NOT_FINISHED = "FAILED_REASON_NOT_FINISHED"; + + /** The recording failed because the channel ID was invalid */ + public static final String FAILED_REASON_INVALID_CHANNEL = "FAILED_REASON_INVALID_CHANNEL"; + + /** The recording failed because the scheduler was stopped */ + public static final String FAILED_REASON_SCHEDULER_STOPPED + = "FAILED_REASON_SCHEDULER_STOPPED"; + + /** The recording failed because some messages were not sent to the message queue */ + public static final String FAILED_REASON_MESSAGE_NOT_SENT = + "FAILED_REASON_MESSAGE_NOT_SENT"; + + /** + * The recording failed because it was failed to establish a connection to the recording + * session for the corresponding TV input. + */ + public static final String FAILED_REASON_CONNECTION_FAILED = + "FAILED_REASON_CONNECTION_FAILED"; + + /** + * The recording failed because a required recording resource was not able to be + * allocated. + */ + public static final String FAILED_REASON_RESOURCE_BUSY = "FAILED_REASON_RESOURCE_BUSY"; + + /** The recording failed because the input was not available */ + public static final String FAILED_REASON_INPUT_UNAVAILABLE = + "FAILED_REASON_INPUT_UNAVAILABLE"; + + /** The recording failed because the input doesn't support recording */ + public static final String FAILED_REASON_INPUT_DVR_UNSUPPORTED = + "FAILED_REASON_INPUT_DVR_UNSUPPORTED"; + + /** The recording failed because the space was not sufficient */ + public static final String FAILED_REASON_INSUFFICIENT_SPACE = + "FAILED_REASON_INSUFFICIENT_SPACE"; + /** * The priority of this recording. * diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java index 0f1ea3b0..1021b2bc 100644 --- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java +++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java @@ -278,7 +278,9 @@ public class InputTaskScheduler { ScheduledRecording schedule = iter.next(); if (schedule.getEndTimeMs() - currentTimeMs <= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) { - fail(schedule); + Log.e(TAG, "Error! Program ended before recording started:" + schedule); + fail(schedule, + ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED); iter.remove(); } } @@ -389,7 +391,7 @@ public class InputTaskScheduler { return candidate; } - private void fail(ScheduledRecording schedule) { + private void fail(ScheduledRecording schedule, int reason) { // It's called when the scheduling has been failed without creating RecordingTask. runOnMainHandler( new Runnable() { @@ -399,10 +401,11 @@ public class InputTaskScheduler { 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. + // in case when it has been updated. mDataManager.changeState( - scheduleInManager, ScheduledRecording.STATE_RECORDING_FAILED); + scheduleInManager, + ScheduledRecording.STATE_RECORDING_FAILED, + reason); } } }); diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java index d631d84f..f309537d 100644 --- a/src/com/android/tv/dvr/recorder/RecordingScheduler.java +++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java @@ -280,12 +280,18 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId()); if (input == null) { Log.e(TAG, "Can't find input for " + schedule); - mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); + mDataManager.changeState( + schedule, + ScheduledRecording.STATE_RECORDING_FAILED, + ScheduledRecording.FAILED_REASON_INPUT_UNAVAILABLE); return; } if (!input.canRecord() || input.getTunerCount() <= 0) { Log.e(TAG, "TV input doesn't support recording: " + input); - mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED); + mDataManager.changeState( + schedule, + ScheduledRecording.STATE_RECORDING_FAILED, + ScheduledRecording.FAILED_REASON_INPUT_DVR_UNSUPPORTED); return; } InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(input.getId()); diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java index ff37f3f0..07a29e51 100644 --- a/src/com/android/tv/dvr/recorder/RecordingTask.java +++ b/src/com/android/tv/dvr/recorder/RecordingTask.java @@ -26,6 +26,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; @@ -194,7 +195,7 @@ public class RecordingTask extends RecordingCallback public void onDisconnected(String inputId) { if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")"); if (mRecordingSession != null && mState != State.FINISHED) { - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_NOT_FINISHED); } } @@ -202,7 +203,7 @@ public class RecordingTask extends RecordingCallback public void onConnectionFailed(String inputId) { if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")"); if (mRecordingSession != null) { - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_CONNECTION_FAILED); } } @@ -217,7 +218,7 @@ public class RecordingTask extends RecordingCallback || !sendEmptyMessageAtAbsoluteTime( MSG_START_RECORDING, mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) { - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT); } } @@ -249,6 +250,7 @@ public class RecordingTask extends RecordingCallback if (mRecordingSession == null) { return; } + int error; switch (reason) { case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE: Log.i(TAG, "Insufficient space to record " + mScheduledRecording); @@ -284,23 +286,28 @@ public class RecordingTask extends RecordingCallback } } }); - // fall through + error = ScheduledRecording.FAILED_REASON_INSUFFICIENT_SPACE; + break; + case TvInputManager.RECORDING_ERROR_RESOURCE_BUSY: + error = ScheduledRecording.FAILED_REASON_RESOURCE_BUSY; + break; default: - failAndQuit(); + error = ScheduledRecording.FAILED_REASON_OTHER; break; } + failAndQuit(error); } 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(); + failAndQuit(ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED); return; } if (mChannel == null) { Log.w(TAG, "Null channel for " + mScheduledRecording); - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_INVALID_CHANNEL); return; } if (mChannel.getId() != mScheduledRecording.getChannelId()) { @@ -310,7 +317,7 @@ public class RecordingTask extends RecordingCallback + mChannel + " does not match scheduled recording " + mScheduledRecording); - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_INVALID_CHANNEL); return; } @@ -329,8 +336,14 @@ public class RecordingTask extends RecordingCallback } private void failAndQuit() { + failAndQuit(ScheduledRecording.FAILED_REASON_OTHER); + } + + private void failAndQuit(Integer reason) { if (DEBUG) Log.d(TAG, "failAndQuit"); - updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); + updateRecordingState( + ScheduledRecording.STATE_RECORDING_FAILED, + reason); mState = State.ERROR; sendRemove(); } @@ -360,7 +373,7 @@ public class RecordingTask extends RecordingCallback if (!sendEmptyMessageAtAbsoluteTime( MSG_STOP_RECORDING, mScheduledRecording.getEndTimeMs())) { - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT); } } @@ -380,7 +393,7 @@ public class RecordingTask extends RecordingCallback if (mState == State.RECORDING_STARTED) { mHandler.removeMessages(MSG_STOP_RECORDING); if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) { - failAndQuit(); + failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT); } } } @@ -435,7 +448,13 @@ public class RecordingTask extends RecordingCallback } private void updateRecordingState(@ScheduledRecording.RecordingState int state) { - if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state); + updateRecordingState(state, null); + } + private void updateRecordingState( + @ScheduledRecording.RecordingState int state, @Nullable Integer reason) { + if (DEBUG) { + Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state); + } mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state).build(); runOnMainThread( @@ -449,11 +468,17 @@ public class RecordingTask extends RecordingCallback removeRecordedProgram(); } else { // Update the state based on the object in DataManager in case when it - // has been - // updated. mScheduledRecording will be updated from + // has been updated. mScheduledRecording will be updated from // onScheduledRecordingStateChanged. - mDataManager.updateScheduledRecording( - ScheduledRecording.buildFrom(schedule).setState(state).build()); + ScheduledRecording.Builder builder = + ScheduledRecording + .buildFrom(schedule) + .setState(state); + if (state == ScheduledRecording.STATE_RECORDING_FAILED + && reason != null) { + builder.setFailedReason(reason); + } + mDataManager.updateScheduledRecording(builder.build()); } } }); @@ -507,7 +532,9 @@ public class RecordingTask extends RecordingCallback /** Clean up the task. */ public void cleanUp() { if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) { - updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED); + updateRecordingState( + ScheduledRecording.STATE_RECORDING_FAILED, + ScheduledRecording.FAILED_REASON_SCHEDULER_STOPPED); } release(); if (mHandler != null) { diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java index 35713666..cba6293b 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java @@ -108,7 +108,8 @@ class DetailsContent { String description; if (scheduledRecording.getState() == ScheduledRecording.STATE_RECORDING_FAILED && errMsg != null) { - description = errMsg; + description = errMsg + + " (Error code: " + scheduledRecording.getFailedReason() + ")"; } else { description = !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java index 65c58ce8..38d3d582 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java @@ -437,6 +437,9 @@ class ScheduleRowPresenter extends RowPresenter { // TODO(b/72638385): show real error messages // TODO(b/72638385): use a better name for ConflictInfoXXX conflictInfo = "Failed"; + if (schedule.getFailedReason() != null) { + conflictInfo += " (Error code: " + schedule.getFailedReason() + ")"; + } } else if (mDvrScheduleManager.isPartiallyConflicting(row.getSchedule())) { conflictInfo = mTunerConflictWillBePartiallyRecordedInfo; } else { |