11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/*
21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project
31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License.
61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at
71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software
111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and
141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License
151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkopackage com.android.tv.dvr.recorder;
181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.annotation.TargetApi;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context;
212e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.media.tv.TvContract;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputManager;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvRecordingClient.RecordingCallback;
241abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.net.Uri;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Build;
26ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.os.Handler;
27ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.os.Looper;
28ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.os.Message;
291abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.support.annotation.VisibleForTesting;
30ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.WorkerThread;
311abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Log;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.widget.Toast;
331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.InputSessionManager;
3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.InputSessionManager.RecordingSession;
3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.R;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.TvApplication;
382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport com.android.tv.common.SoftPreconditions;
391abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport com.android.tv.data.Channel;
406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.DvrManager;
416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.WritableDvrDataManager;
426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording;
436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper;
441abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport com.android.tv.util.Clock;
45ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.util.Utils;
461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
47d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Comparator;
481abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.concurrent.TimeUnit;
491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/**
51ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * A Handler that actually starts and stop a recording at the right time.
52ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko *
53ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>This is run on the looper of thread named {@value DvrRecordingService#HANDLER_THREAD_NAME}.
54ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * There is only one looper so messages must be handled quickly or start a separate thread.
551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
56ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko@WorkerThread
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@TargetApi(Build.VERSION_CODES.N)
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class RecordingTask extends RecordingCallback implements Handler.Callback,
5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        DvrManager.Listener {
601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String TAG = "RecordingTask";
612e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private static final boolean DEBUG = false;
62ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
63d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    /**
64d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Compares the end time in ascending order.
65d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     */
66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public static final Comparator<RecordingTask> END_TIME_COMPARATOR
67d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            = new Comparator<RecordingTask>() {
68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
69d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public int compare(RecordingTask lhs, RecordingTask rhs) {
70d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs());
71d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
72d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    };
73d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
74d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    /**
75d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Compares ID in ascending order.
76d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     */
77d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public static final Comparator<RecordingTask> ID_COMPARATOR
78d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            = new Comparator<RecordingTask>() {
79d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
80d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public int compare(RecordingTask lhs, RecordingTask rhs) {
81d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return Long.compare(lhs.getScheduleId(), rhs.getScheduleId());
82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    };
84d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
85d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    /**
86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Compares the priority in ascending order.
87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     */
88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public static final Comparator<RecordingTask> PRIORITY_COMPARATOR
89d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            = new Comparator<RecordingTask>() {
90d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
91d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public int compare(RecordingTask lhs, RecordingTask rhs) {
92d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return Long.compare(lhs.getPriority(), rhs.getPriority());
93d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
94d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    };
95d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
96ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @VisibleForTesting
9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    static final int MSG_INITIALIZE = 1;
98ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @VisibleForTesting
9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    static final int MSG_START_RECORDING = 2;
100ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @VisibleForTesting
10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    static final int MSG_STOP_RECORDING = 3;
10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Message to update schedule.
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public static final int MSG_UDPATE_SCHEDULE = 4;
10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * The time when the start command will be sent before the recording starts.
10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3);
11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * If the recording starts later than the scheduled start time or ends before the scheduled end
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * time, it's considered as clipped.
11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
116ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
117ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @VisibleForTesting
118ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    enum State {
119ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        NOT_STARTED,
120ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        SESSION_ACQUIRED,
121ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        CONNECTION_PENDING,
122ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        CONNECTED,
123ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        RECORDING_STARTED,
1242e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        RECORDING_STOP_REQUESTED,
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        FINISHED,
126ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        ERROR,
127ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        RELEASED,
128ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final InputSessionManager mSessionManager;
1302e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private final DvrManager mDvrManager;
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Context mContext;
132ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
1331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final WritableDvrDataManager mDataManager;
134ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private RecordingSession mRecordingSession;
136ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private Handler mHandler;
1372e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private ScheduledRecording mScheduledRecording;
1382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private final Channel mChannel;
139ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private State mState = State.NOT_STARTED;
140ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private final Clock mClock;
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mStartedWithClipping;
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Uri mRecordedProgramUri;
14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mCanceled;
1441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel,
14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            DvrManager dvrManager, InputSessionManager sessionManager,
1471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            WritableDvrDataManager dataManager, Clock clock) {
14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mContext = context;
1492e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mScheduledRecording = scheduledRecording;
1502e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mChannel = channel;
1511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mSessionManager = sessionManager;
1521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mDataManager = dataManager;
1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mClock = clock;
1542e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mDvrManager = dvrManager;
155ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
1562e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "created recording task " + mScheduledRecording);
1571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
159ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    public void setHandler(Handler handler) {
160ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        mHandler = handler;
161ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
1621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
163ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @Override
164ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    public boolean handleMessage(Message msg) {
165ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (DEBUG) Log.d(TAG, "handleMessage " + msg);
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                TAG, "Null handler trying to handle " + msg);
1681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        try {
169ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            switch (msg.what) {
17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_INITIALIZE:
171ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    handleInit();
172ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    break;
17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_START_RECORDING:
174ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    handleStartRecording();
175ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    break;
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_STOP_RECORDING:
177ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    handleStopRecording();
178ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    break;
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case MSG_UDPATE_SCHEDULE:
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    handleUpdateSchedule((ScheduledRecording) msg.obj);
18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                case HandlerWrapper.MESSAGE_REMOVE:
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mHandler.removeCallbacksAndMessages(null);
184ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    mHandler = null;
185ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    release();
186ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    return false;
187ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                default:
188ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg);
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    break;
190ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
191ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            return true;
192ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        } catch (Exception e) {
1932e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            Log.w(TAG, "Error processing message " + msg + "  for " + mScheduledRecording, e);
194ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            failAndQuit();
1951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
196ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        return false;
197ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
198ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
199ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @Override
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onDisconnected(String inputId) {
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")");
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecordingSession != null && mState != State.FINISHED) {
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            failAndQuit();
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
208d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public void onConnectionFailed(String inputId) {
209d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")");
210d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (mRecordingSession != null) {
211d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            failAndQuit();
212d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
213d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
214d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
215d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    @Override
2162e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void onTuned(Uri channelUri) {
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "onTuned");
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecordingSession == null) {
21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
2202e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
221ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        mState = State.CONNECTED;
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING,
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) {
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            failAndQuit();
2252e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
226ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
227ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
2281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
2292e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void onRecordingStopped(Uri recordedProgramUri) {
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "onRecordingStopped");
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecordingSession == null) {
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordedProgramUri = recordedProgramUri;
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mState = State.FINISHED;
23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int state = ScheduledRecording.STATE_RECORDING_FINISHED;
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS
23865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                > mClock.currentTimeMillis()) {
23965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            state = ScheduledRecording.STATE_RECORDING_CLIPPED;
24065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        updateRecordingState(state);
2422e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        sendRemove();
24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mCanceled) {
24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            removeRecordedProgram();
24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
2461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
2492e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void onError(int reason) {
2502e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "onError reason " + reason);
25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecordingSession == null) {
25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
254ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        switch (reason) {
25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE:
25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mMainThreadHandler.post(new Runnable() {
25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    @Override
25865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    public void run() {
25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        if (TvApplication.getSingletons(mContext).getMainActivityWrapper()
26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                                .isResumed()) {
2616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            ScheduledRecording scheduledRecording = mDataManager
2626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                    .getScheduledRecording(mScheduledRecording.getId());
2636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            if (scheduledRecording != null) {
2646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                Toast.makeText(mContext.getApplicationContext(),
2656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                        mContext.getString(R.string
2666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                        .dvr_error_insufficient_space_description_one_recording,
2676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                        scheduledRecording.getProgramDisplayTitle(mContext)),
2686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                        Toast.LENGTH_LONG)
2696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                        .show();
2706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            }
27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        } else {
27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                            Utils.setRecordingFailedReason(mContext.getApplicationContext(),
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                                    TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
2746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(),
2756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                    mScheduledRecording.getProgramDisplayTitle(mContext));
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        }
27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                });
27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                // Pass through
2801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            default:
28165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                failAndQuit();
28265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                break;
283ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
2841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
286ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void handleInit() {
2872e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording);
2882e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) {
2892e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            Log.w(TAG, "End time already past, not recording " + mScheduledRecording);
290ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            failAndQuit();
291ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            return;
292ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
2932e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (mChannel == null) {
2942e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            Log.w(TAG, "Null channel for " + mScheduledRecording);
2952e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            failAndQuit();
2962e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            return;
2972e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2982e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (mChannel.getId() != mScheduledRecording.getChannelId()) {
2992e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording "
3002e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                    + mScheduledRecording);
301ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            failAndQuit();
302ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            return;
303ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
304ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
3052e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        String inputId = mChannel.getInputId();
30665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordingSession = mSessionManager.createRecordingSession(inputId,
307d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                "recordingTask-" + mScheduledRecording.getId(), this,
308d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mHandler, mScheduledRecording.getEndTimeMs());
30965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mState = State.SESSION_ACQUIRED;
3102e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mDvrManager.addListener(this, mHandler);
31165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordingSession.tune(inputId, mChannel.getUri());
3122e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mState = State.CONNECTION_PENDING;
313ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
314ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
315ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void failAndQuit() {
3162e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "failAndQuit");
3172e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
318ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        mState = State.ERROR;
319ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        sendRemove();
320ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
321ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
322ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void sendRemove() {
3232e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "sendRemove");
324ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (mHandler != null) {
32565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(
32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    HandlerWrapper.MESSAGE_REMOVE));
327ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
328ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
329ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
330ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void handleStartRecording() {
3312e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording);
3322e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        long programId = mScheduledRecording.getProgramId();
33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null
3342e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                : TvContract.buildProgramUri(programId));
33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS);
33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // If it starts late, it's clipped.
33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS
33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                < mClock.currentTimeMillis()) {
33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStartedWithClipping = true;
34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
3412e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mState = State.RECORDING_STARTED;
3422e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING,
34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mScheduledRecording.getEndTimeMs())) {
34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            failAndQuit();
346ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
347ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
348ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
349ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void handleStopRecording() {
3502e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording);
35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordingSession.stopRecording();
3522e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mState = State.RECORDING_STOP_REQUESTED;
353ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
354ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void handleUpdateSchedule(ScheduledRecording schedule) {
35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mScheduledRecording = schedule;
35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Check end time only. The start time is checked in InputTaskScheduler.
358d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (schedule.getEndTimeMs() != mScheduledRecording.getEndTimeMs()) {
359d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (mRecordingSession != null) {
360d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mRecordingSession.setEndTimeMs(schedule.getEndTimeMs());
361d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
362d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (mState == State.RECORDING_STARTED) {
363d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mHandler.removeMessages(MSG_STOP_RECORDING);
364d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) {
365d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    failAndQuit();
366d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
371ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @VisibleForTesting
372ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    State getState() {
373ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        return mState;
374ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
375ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
376d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private long getScheduleId() {
377d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        return mScheduledRecording.getId();
378d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
379d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the priority.
38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
38365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public long getPriority() {
38465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mScheduledRecording.getPriority();
38565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
38665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
38765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the start time of the recording.
38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public long getStartTimeMs() {
39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mScheduledRecording.getStartTimeMs();
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
39565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the end time of the recording.
39665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
39765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public long getEndTimeMs() {
39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mScheduledRecording.getEndTimeMs();
39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
401ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void release() {
40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecordingSession != null) {
40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSessionManager.releaseRecordingSession(mRecordingSession);
40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecordingSession = null;
405ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
4062e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mDvrManager.removeListener(this);
407ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
408ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
409ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private boolean sendEmptyMessageAtAbsoluteTime(int what, long when) {
410ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        long now = mClock.currentTimeMillis();
411ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        long delay = Math.max(0L, when - now);
412ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (DEBUG) {
413ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000
414ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    + " seconds to arrive at " + Utils.toIsoDateTimeString(when));
415ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
416ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        return mHandler.sendEmptyMessageDelayed(what, delay);
417ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
4181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private void updateRecordingState(@ScheduledRecording.RecordingState int state) {
42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state);
42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state)
42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                .build();
423d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        runOnMainThread(new Runnable() {
424ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            @Override
425ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            public void run() {
42665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                ScheduledRecording schedule = mDataManager.getScheduledRecording(
42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        mScheduledRecording.getId());
42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (schedule == null) {
42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // Schedule has been deleted. Delete the recorded program.
43065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    removeRecordedProgram();
43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                } else  {
43265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // Update the state based on the object in DataManager in case when it has been
43365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // updated. mScheduledRecording will be updated from
43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // onScheduledRecordingStateChanged.
43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule)
43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                            .setState(state).build());
43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
438ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
439ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        });
4401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
4411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
4432e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void onStopRecordingRequested(ScheduledRecording recording) {
4442e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (recording.getId() != mScheduledRecording.getId()) {
4452e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            return;
4462e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        stop();
44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
44965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
45065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
45165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Starts the task.
45265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
45365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void start() {
45465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_INITIALIZE);
45565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
45665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
45765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
45865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Stops the task.
45965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
46065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void stop() {
46165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "stop");
4622e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        switch (mState) {
4632e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case RECORDING_STARTED:
46465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mHandler.removeMessages(MSG_STOP_RECORDING);
4652e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                handleStopRecording();
4662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                break;
4672e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case RECORDING_STOP_REQUESTED:
4682e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                // Do nothing
4692e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                break;
4702e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case NOT_STARTED:
4712e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case SESSION_ACQUIRED:
4722e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case CONNECTION_PENDING:
4732e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case CONNECTED:
47465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            case FINISHED:
4752e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case ERROR:
4762e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            case RELEASED:
4772e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            default:
4782e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                sendRemove();
4792e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                break;
4802e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
4812e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
4822e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Cancels the task
48565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void cancel() {
48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, "cancel");
48865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mCanceled = true;
48965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        stop();
49065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        removeRecordedProgram();
49165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
493d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    /**
494d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Clean up the task.
495d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     */
496d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public void cleanUp() {
497d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) {
498d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
499d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
500d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        release();
501d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (mHandler != null) {
502d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            mHandler.removeCallbacksAndMessages(null);
503d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
504d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
505d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
5062e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    @Override
5071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public String toString() {
5082e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        return getClass().getName() + "(" + mScheduledRecording + ")";
5091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
51065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
51165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void removeRecordedProgram() {
51265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        runOnMainThread(new Runnable() {
51365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            @Override
51465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            public void run() {
51565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mRecordedProgramUri != null) {
51665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mDvrManager.removeRecordedProgram(mRecordedProgramUri);
51765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
51865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
51965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        });
52065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
52165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
52265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void runOnMainThread(Runnable runnable) {
52365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (Looper.myLooper() == Looper.getMainLooper()) {
52465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            runnable.run();
52565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        } else {
52665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mMainThreadHandler.post(runnable);
52765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
5291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko}
530