TunerRecordingSessionWorker.java revision a1589bd48e05abbee991e0cdd27fa402a5dc5001
165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/*
265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2015 The Android Open Source Project
365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License.
665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at
765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software
1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and
1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License.
1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.tvinput;
1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentResolver;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentUris;
2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentValues;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.database.Cursor;
2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvContract;
25944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport android.media.tv.TvContract.RecordedPrograms;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvInputManager;
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.net.Uri;
28d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.os.AsyncTask;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Handler;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.HandlerThread;
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Message;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.IntDef;
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.MainThread;
3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.Nullable;
35944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport android.support.media.tv.Program;
3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Log;
37633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport android.util.Pair;
38944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.BaseApplication;
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.recording.RecordingCapability;
40944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.recording.RecordingStorageStatusManager;
41944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.CommonUtils;
4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.DvbDeviceAccessor;
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.PsipData;
44633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.tuner.data.PsipData.EitItem;
4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.TunerChannel;
46633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.SampleExtractor;
4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.buffer.BufferManager;
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
51d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.tuner.source.TsDataSource;
52d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.tuner.source.TsDataSourceManager;
53a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Teamimport com.google.android.exoplayer.C;
5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.io.File;
5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.io.IOException;
5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.lang.annotation.Retention;
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.lang.annotation.RetentionPolicy;
58633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport java.util.ArrayList;
5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List;
6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Locale;
6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Random;
6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.TimeUnit;
6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6495961816a768da387f0b5523cf4363ace2044089Nick Chalko/** Implements a DVR feature. */
65944779887775bd950cf1abf348d2df461593f6abLive Channels Team@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
6695961816a768da387f0b5523cf4363ace2044089Nick Chalkopublic class TunerRecordingSessionWorker
6795961816a768da387f0b5523cf4363ace2044089Nick Chalko        implements PlaybackBufferListener,
6895961816a768da387f0b5523cf4363ace2044089Nick Chalko                EventDetector.EventListener,
6995961816a768da387f0b5523cf4363ace2044089Nick Chalko                SampleExtractor.OnCompletionListener,
7095961816a768da387f0b5523cf4363ace2044089Nick Chalko                Handler.Callback {
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final String TAG = "TunerRecordingSessionW";
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final boolean DEBUG = false;
7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7495961816a768da387f0b5523cf4363ace2044089Nick Chalko    private static final String SORT_BY_TIME =
7595961816a768da387f0b5523cf4363ace2044089Nick Chalko            TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
7695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + ", "
7795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + TvContract.Programs.COLUMN_CHANNEL_ID
7895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + ", "
7995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS;
80633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final long PREPARE_RECORDER_POLL_MS = 50;
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_TUNE = 1;
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_START_RECORDING = 2;
86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_PREPARE_RECODER = 3;
87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_STOP_RECORDING = 4;
88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_MONITOR_STORAGE_STATUS = 5;
89d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_RELEASE = 6;
90633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int MSG_UPDATE_CC_INFO = 7;
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final RecordingCapability mCapabilities;
9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
93944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private static final String[] PROGRAM_PROJECTION = {
94944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CHANNEL_ID,
95944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_TITLE,
96944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SEASON_TITLE,
97944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_EPISODE_TITLE,
98944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
99944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
100944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
101944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_POSTER_ART_URI,
102944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_THUMBNAIL_URI,
103944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CANONICAL_GENRE,
104944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CONTENT_RATING,
105944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
106944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
107944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_VIDEO_WIDTH,
108944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_VIDEO_HEIGHT,
109944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
110944779887775bd950cf1abf348d2df461593f6abLive Channels Team    };
11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
112633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING})
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Retention(RetentionPolicy.SOURCE)
11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public @interface DvrSessionState {}
11595961816a768da387f0b5523cf4363ace2044089Nick Chalko
11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int STATE_IDLE = 1;
117633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_TUNING = 2;
118633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_TUNED = 3;
119633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_RECORDING = 4;
12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long CHANNEL_ID_NONE = -1;
122633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int MAX_TUNING_RETRY = 6;
12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Context mContext;
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ChannelDataManager mChannelDataManager;
126944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private final RecordingStorageStatusManager mRecordingStorageStatusManager;
12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Handler mHandler;
128d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private final TsDataSourceManager mSourceManager;
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Random mRandom = new Random();
13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
131d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private TsDataSource mTunerSource;
13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerChannel mChannel;
13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private File mStorageDir;
13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private long mRecordStartTime;
13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private long mRecordEndTime;
13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mRecorderRunning;
13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private SampleExtractor mRecorder;
13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final TunerRecordingSession mSession;
13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @DvrSessionState private int mSessionState = STATE_IDLE;
14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final String mInputId;
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Uri mProgramUri;
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
143633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private PsipData.EitItem mCurrenProgram;
144633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private List<AtscCaptionTrack> mCaptionTracks;
145633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private DvrStorageManager mDvrStorageManager;
146633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
14795961816a768da387f0b5523cf4363ace2044089Nick Chalko    public TunerRecordingSessionWorker(
14895961816a768da387f0b5523cf4363ace2044089Nick Chalko            Context context,
14995961816a768da387f0b5523cf4363ace2044089Nick Chalko            String inputId,
15095961816a768da387f0b5523cf4363ace2044089Nick Chalko            ChannelDataManager dataManager,
15195961816a768da387f0b5523cf4363ace2044089Nick Chalko            TunerRecordingSession session) {
15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRandom.setSeed(System.nanoTime());
15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mContext = context;
15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        HandlerThread handlerThread = new HandlerThread(TAG);
15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        handlerThread.start();
15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler = new Handler(handlerThread.getLooper(), this);
157944779887775bd950cf1abf348d2df461593f6abLive Channels Team        mRecordingStorageStatusManager =
158944779887775bd950cf1abf348d2df461593f6abLive Channels Team                BaseApplication.getSingletons(context).getRecordingStorageStatusManager();
15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager = dataManager;
16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.checkDataVersion(context);
161d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mSourceManager = TsDataSourceManager.createSourceManager(true);
16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId);
16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mInputId = inputId;
16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, mCapabilities.toString());
16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSession = session;
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // PlaybackBufferListener
16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17095961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onBufferStartTimeChanged(long startTimeMs) {}
17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17395961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onBufferStateChanged(boolean available) {}
17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17695961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onDiskTooSlow() {}
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // EventDetector.EventListener
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null || mChannel.compareTo(channel) != 0) {
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null || mChannel.compareTo(channel) != 0) {
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
192633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.notifyEventDetected(channel, items);
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onChannelScanDone() {
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // do nothing.
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // SampleExtractor.OnCompletionListener
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onCompletion(boolean success, long lastExtractedPositionUs) {
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        onRecordingResult(success, lastExtractedPositionUs);
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        reset();
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20895961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Tunes to {@code channelUri}. */
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void tune(Uri channelUri) {
21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeCallbacksAndMessages(null);
212633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget();
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
21595961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Starts recording. */
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void startRecording(@Nullable Uri programUri) {
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget();
21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22195961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Stops recording. */
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void stopRecording() {
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_STOP_RECORDING);
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22795961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Releases all resources. */
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void release() {
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeCallbacksAndMessages(null);
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_RELEASE);
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public boolean handleMessage(Message msg) {
23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        switch (msg.what) {
23795961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_TUNE:
23895961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
23995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Uri channelUri = (Uri) msg.obj;
24095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    int retryCount = msg.arg1;
24195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Tune to " + channelUri);
24295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (doTune(channelUri)) {
24395961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (mSessionState == STATE_TUNED) {
24495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mSession.onTuned(channelUri);
245633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        } else {
24695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            Log.w(TAG, "Tuner stream cannot be created due to resource shortage.");
24795961816a768da387f0b5523cf4363ace2044089Nick Chalko                            if (retryCount < MAX_TUNING_RETRY) {
24895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                Message tuneMsg =
24995961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mHandler.obtainMessage(
25095961816a768da387f0b5523cf4363ace2044089Nick Chalko                                                MSG_TUNE, retryCount + 1, 0, channelUri);
25195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS);
25295961816a768da387f0b5523cf4363ace2044089Nick Chalko                            } else {
25395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY);
25495961816a768da387f0b5523cf4363ace2044089Nick Chalko                                reset();
25595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            }
256633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        }
257633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    }
258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    return true;
259d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
26095961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_START_RECORDING:
26195961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
26295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Start recording");
26395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (!doStartRecording((Uri) msg.obj)) {
26495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
265d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
26895961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_PREPARE_RECODER:
26995961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
27095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Preparing recorder");
27195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (!mRecorderRunning) {
27295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
27395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
27495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    try {
27595961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (!mRecorder.prepare()) {
27695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mHandler.sendEmptyMessageDelayed(
27795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS);
27895961816a768da387f0b5523cf4363ace2044089Nick Chalko                        }
27995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    } catch (IOException e) {
28095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor");
28195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
28295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
28395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
28695961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_STOP_RECORDING:
28795961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
28895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Stop recording");
28995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (mSessionState != STATE_RECORDING) {
29095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
29195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
29295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
29395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (mRecorderRunning) {
29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        stopRecorder();
29665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
29795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
29895961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
29995961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_MONITOR_STORAGE_STATUS:
30095961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
30195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (mSessionState != STATE_RECORDING) {
30295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
30395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
304944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (!mRecordingStorageStatusManager.isStorageSufficient()) {
30595961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (mRecorderRunning) {
30695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            stopRecorder();
30795961816a768da387f0b5523cf4363ace2044089Nick Chalko                        }
30895961816a768da387f0b5523cf4363ace2044089Nick Chalko                        new DeleteRecordingTask().execute(mStorageDir);
30995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
31095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
31195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    } else {
31295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mHandler.sendEmptyMessageDelayed(
31395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS);
31495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
31595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
31695961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
31795961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_RELEASE:
31895961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
31995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    // Since release was requested, current recording will be cancelled
32095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    // without notification.
32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    reset();
32295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mSourceManager.release();
32395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mHandler.removeCallbacksAndMessages(null);
32495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mHandler.getLooper().quitSafely();
32595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
32695961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
32795961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_UPDATE_CC_INFO:
32895961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
32995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Pair<TunerChannel, List<EitItem>> pair =
33095961816a768da387f0b5523cf4363ace2044089Nick Chalko                            (Pair<TunerChannel, List<EitItem>>) msg.obj;
33195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    updateCaptionTracks(pair.first, pair.second);
33295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return false;
33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Nullable
33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerChannel getChannel(Uri channelUri) {
34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (channelUri == null) {
34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return null;
34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long channelId;
34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        try {
34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            channelId = ContentUris.parseId(channelUri);
34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        } catch (UnsupportedOperationException | NumberFormatException e) {
34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            channelId = CHANNEL_ID_NONE;
34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId);
35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private String getStorageKey() {
35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long prefix = System.currentTimeMillis();
35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int suffix = mRandom.nextInt();
35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix);
35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void reset() {
35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorder != null) {
36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder.release();
36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder = null;
36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mTunerSource != null) {
36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSourceManager.releaseDataSource(mTunerSource);
36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTunerSource = null;
36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
367633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager = null;
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_IDLE;
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = false;
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean doTune(Uri channelUri) {
373633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) {
37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Tuning was requested from wrong status.");
37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannel = getChannel(channelUri);
37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null) {
38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel);
38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
383633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        } else if (mChannel.isRecordingProhibited()) {
384633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
385633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel);
386633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return false;
38765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
388944779887775bd950cf1abf348d2df461593f6abLive Channels Team        if (!mRecordingStorageStatusManager.isStorageSufficient()) {
38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Tuning failed due to insufficient storage.");
39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this);
39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mTunerSource == null) {
395633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            // Retry tuning in this case.
396633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            mSessionState = STATE_TUNING;
397633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return true;
39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_TUNED;
40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return true;
40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean doStartRecording(@Nullable Uri programUri) {
40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mSessionState != STATE_TUNED) {
40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Recording session status abnormal");
40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
40865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
40995961816a768da387f0b5523cf4363ace2044089Nick Chalko        mStorageDir =
410944779887775bd950cf1abf348d2df461593f6abLive Channels Team                mRecordingStorageStatusManager.isStorageSufficient()
41195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        ? new File(
412944779887775bd950cf1abf348d2df461593f6abLive Channels Team                                mRecordingStorageStatusManager.getRecordingRootDataDirectory(),
41395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                getStorageKey())
41495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        : null;
41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mStorageDir == null) {
41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Failed to start recording due to insufficient storage.");
41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Since tuning might be happened a while ago, shifts the start position of tuned source.
42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition());
42265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordStartTime = System.currentTimeMillis();
423633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
42495961816a768da387f0b5523cf4363ace2044089Nick Chalko        mRecorder =
42595961816a768da387f0b5523cf4363ace2044089Nick Chalko                new ExoPlayerSampleExtractor(
42695961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true);
42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorder.setOnCompletionListener(this, mHandler);
42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mProgramUri = programUri;
42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_RECORDING;
43065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = true;
431d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mHandler.sendEmptyMessage(MSG_PREPARE_RECODER);
43265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
43395961816a768da387f0b5523cf4363ace2044089Nick Chalko        mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS);
43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return true;
43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void stopRecorder() {
43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Do not change session status.
43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorder != null) {
44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder.release();
44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecordEndTime = System.currentTimeMillis();
44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder = null;
44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = false;
44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Log.i(TAG, "Recording stopped");
44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
44865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
449633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) {
45095961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (mChannel == null
45195961816a768da387f0b5523cf4363ace2044089Nick Chalko                || channel == null
45295961816a768da387f0b5523cf4363ace2044089Nick Chalko                || mChannel.compareTo(channel) != 0
45395961816a768da387f0b5523cf4363ace2044089Nick Chalko                || items == null
45495961816a768da387f0b5523cf4363ace2044089Nick Chalko                || items.isEmpty()) {
455633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return;
456633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
457633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        PsipData.EitItem currentProgram = getCurrentProgram(items);
45895961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (currentProgram == null
45995961816a768da387f0b5523cf4363ace2044089Nick Chalko                || !currentProgram.hasCaptionTrack()
460a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Team                || (mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0)) {
461633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return;
462633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
463633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mCurrenProgram = currentProgram;
464633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks());
465633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        if (DEBUG) {
46695961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.d(
46795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
46895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram);
469633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
470633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    }
471633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
472633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) {
473633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        for (PsipData.EitItem item : items) {
474633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            if (mRecordStartTime >= item.getStartTimeUtcMillis()
475633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    && mRecordStartTime < item.getEndTimeUtcMillis()) {
476633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                return item;
477633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
478633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
479633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        return null;
480633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    }
481633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
48265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Program getRecordedProgram() {
48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ContentResolver resolver = mContext.getContentResolver();
48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Uri programUri = mProgramUri;
48565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mProgramUri == null) {
48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            long avg = mRecordStartTime / 2 + mRecordEndTime / 2;
48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg);
48865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
489944779887775bd950cf1abf348d2df461593f6abLive Channels Team        try (Cursor c = resolver.query(programUri, PROGRAM_PROJECTION, null, null, SORT_BY_TIME)) {
490944779887775bd950cf1abf348d2df461593f6abLive Channels Team            if (c != null && c.moveToNext()) {
491944779887775bd950cf1abf348d2df461593f6abLive Channels Team                Program result = Program.fromCursor(c);
49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (DEBUG) {
49365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.v(TAG, "Finished query for " + this);
49465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
49565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return result;
49665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            } else {
49765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (c == null) {
49865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.e(TAG, "Unknown query error for " + this);
49965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                } else {
500944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (DEBUG) Log.d(TAG, "Can not find program:" + programUri);
50165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
50265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return null;
50365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
50465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
50565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
50665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
50795961816a768da387f0b5523cf4363ace2044089Nick Chalko    private Uri insertRecordedProgram(
50895961816a768da387f0b5523cf4363ace2044089Nick Chalko            Program program,
50995961816a768da387f0b5523cf4363ace2044089Nick Chalko            long channelId,
51095961816a768da387f0b5523cf4363ace2044089Nick Chalko            String storageUri,
51195961816a768da387f0b5523cf4363ace2044089Nick Chalko            long totalBytes,
51295961816a768da387f0b5523cf4363ace2044089Nick Chalko            long startTime,
51395961816a768da387f0b5523cf4363ace2044089Nick Chalko            long endTime) {
514944779887775bd950cf1abf348d2df461593f6abLive Channels Team        ContentValues values = new ContentValues();
515944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_INPUT_ID, mInputId);
516944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
517944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, storageUri);
518944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - startTime);
519944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes);
520944779887775bd950cf1abf348d2df461593f6abLive Channels Team        // startTime and endTime could be overridden by program's start and end value.
521944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
522944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
52365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (program != null) {
524944779887775bd950cf1abf348d2df461593f6abLive Channels Team            values.putAll(program.toContentValues());
52565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
52695961816a768da387f0b5523cf4363ace2044089Nick Chalko        return mContext.getContentResolver()
52795961816a768da387f0b5523cf4363ace2044089Nick Chalko                .insert(TvContract.RecordedPrograms.CONTENT_URI, values);
52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
52965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
53065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void onRecordingResult(boolean success, long lastExtractedPositionUs) {
53165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mSessionState != STATE_RECORDING) {
53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Error notification is not needed.
53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Recording session status abnormal");
53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
53565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorderRunning) {
53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // In case of recorder not being stopped, because of premature termination of recording.
53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            stopRecorder();
53965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
54095961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (!success
54195961816a768da387f0b5523cf4363ace2044089Nick Chalko                && lastExtractedPositionUs
54295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) {
543d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            new DeleteRecordingTask().execute(mStorageDir);
54465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
54565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Recording failed during recording");
54665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
54765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
54865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Log.i(TAG, "recording finished " + (success ? "completely" : "partially"));
549633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        long recordEndTime =
550633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                (lastExtractedPositionUs == C.UNKNOWN_TIME_US)
551633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        ? System.currentTimeMillis()
552633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        : mRecordStartTime + lastExtractedPositionUs / 1000;
553633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        Uri uri =
554633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                insertRecordedProgram(
555633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        getRecordedProgram(),
556633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        mChannel.getChannelId(),
557633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        Uri.fromFile(mStorageDir).toString(),
558633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        1024 * 1024,
559633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        mRecordStartTime,
560633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        recordEndTime);
56165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (uri == null) {
562d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            new DeleteRecordingTask().execute(mStorageDir);
56365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
56465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Inserting a recording to DB failed");
56565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
56665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
567633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks);
56865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSession.onRecordFinished(uri);
56965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
571d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> {
572d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
573d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
574d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public Void doInBackground(File... files) {
575d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (files == null || files.length == 0) {
576d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                return null;
577d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
57895961816a768da387f0b5523cf4363ace2044089Nick Chalko            for (File file : files) {
579944779887775bd950cf1abf348d2df461593f6abLive Channels Team                CommonUtils.deleteDirOrFile(file);
580d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
581d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return null;
582d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
583d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
58465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}
585