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. */
6595961816a768da387f0b5523cf4363ace2044089Nick Chalkopublic class TunerRecordingSessionWorker
6695961816a768da387f0b5523cf4363ace2044089Nick Chalko        implements PlaybackBufferListener,
6795961816a768da387f0b5523cf4363ace2044089Nick Chalko                EventDetector.EventListener,
6895961816a768da387f0b5523cf4363ace2044089Nick Chalko                SampleExtractor.OnCompletionListener,
6995961816a768da387f0b5523cf4363ace2044089Nick Chalko                Handler.Callback {
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final String TAG = "TunerRecordingSessionW";
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final boolean DEBUG = false;
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7395961816a768da387f0b5523cf4363ace2044089Nick Chalko    private static final String SORT_BY_TIME =
7495961816a768da387f0b5523cf4363ace2044089Nick Chalko            TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
7595961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + ", "
7695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + TvContract.Programs.COLUMN_CHANNEL_ID
7795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + ", "
7895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS;
79633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final long PREPARE_RECORDER_POLL_MS = 50;
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_TUNE = 1;
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MSG_START_RECORDING = 2;
85d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_PREPARE_RECODER = 3;
86d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_STOP_RECORDING = 4;
87d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_MONITOR_STORAGE_STATUS = 5;
88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final int MSG_RELEASE = 6;
89633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int MSG_UPDATE_CC_INFO = 7;
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final RecordingCapability mCapabilities;
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
92944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private static final String[] PROGRAM_PROJECTION = {
93944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CHANNEL_ID,
94944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_TITLE,
95944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SEASON_TITLE,
96944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_EPISODE_TITLE,
97944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
98944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
99944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
100944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_POSTER_ART_URI,
101944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_THUMBNAIL_URI,
102944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CANONICAL_GENRE,
103944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_CONTENT_RATING,
104944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
105944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
106944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_VIDEO_WIDTH,
107944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_VIDEO_HEIGHT,
108944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
109944779887775bd950cf1abf348d2df461593f6abLive Channels Team    };
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
111633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING})
11265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Retention(RetentionPolicy.SOURCE)
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public @interface DvrSessionState {}
11495961816a768da387f0b5523cf4363ace2044089Nick Chalko
11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int STATE_IDLE = 1;
116633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_TUNING = 2;
117633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_TUNED = 3;
118633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int STATE_RECORDING = 4;
11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final long CHANNEL_ID_NONE = -1;
121633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private static final int MAX_TUNING_RETRY = 6;
12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Context mContext;
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final ChannelDataManager mChannelDataManager;
125944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private final RecordingStorageStatusManager mRecordingStorageStatusManager;
12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Handler mHandler;
127d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private final TsDataSourceManager mSourceManager;
12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Random mRandom = new Random();
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
130d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private TsDataSource mTunerSource;
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerChannel mChannel;
13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private File mStorageDir;
13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private long mRecordStartTime;
13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private long mRecordEndTime;
13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mRecorderRunning;
13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private SampleExtractor mRecorder;
13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final TunerRecordingSession mSession;
13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @DvrSessionState private int mSessionState = STATE_IDLE;
13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final String mInputId;
14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Uri mProgramUri;
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
142633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private PsipData.EitItem mCurrenProgram;
143633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private List<AtscCaptionTrack> mCaptionTracks;
144633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private DvrStorageManager mDvrStorageManager;
145633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
14695961816a768da387f0b5523cf4363ace2044089Nick Chalko    public TunerRecordingSessionWorker(
14795961816a768da387f0b5523cf4363ace2044089Nick Chalko            Context context,
14895961816a768da387f0b5523cf4363ace2044089Nick Chalko            String inputId,
14995961816a768da387f0b5523cf4363ace2044089Nick Chalko            ChannelDataManager dataManager,
15095961816a768da387f0b5523cf4363ace2044089Nick Chalko            TunerRecordingSession session) {
15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRandom.setSeed(System.nanoTime());
15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mContext = context;
15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        HandlerThread handlerThread = new HandlerThread(TAG);
15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        handlerThread.start();
15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler = new Handler(handlerThread.getLooper(), this);
156944779887775bd950cf1abf348d2df461593f6abLive Channels Team        mRecordingStorageStatusManager =
157944779887775bd950cf1abf348d2df461593f6abLive Channels Team                BaseApplication.getSingletons(context).getRecordingStorageStatusManager();
15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager = dataManager;
15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.checkDataVersion(context);
160d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mSourceManager = TsDataSourceManager.createSourceManager(true);
16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId);
16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mInputId = inputId;
16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (DEBUG) Log.d(TAG, mCapabilities.toString());
16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSession = session;
16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // PlaybackBufferListener
16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
16995961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onBufferStartTimeChanged(long startTimeMs) {}
17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17295961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onBufferStateChanged(boolean available) {}
17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17595961816a768da387f0b5523cf4363ace2044089Nick Chalko    public void onDiskTooSlow() {}
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // EventDetector.EventListener
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null || mChannel.compareTo(channel) != 0) {
18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null || mChannel.compareTo(channel) != 0) {
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
191633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannelDataManager.notifyEventDetected(channel, items);
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onChannelScanDone() {
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // do nothing.
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // SampleExtractor.OnCompletionListener
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void onCompletion(boolean success, long lastExtractedPositionUs) {
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        onRecordingResult(success, lastExtractedPositionUs);
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        reset();
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20795961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Tunes to {@code channelUri}. */
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void tune(Uri channelUri) {
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeCallbacksAndMessages(null);
211633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget();
21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
21495961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Starts recording. */
21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void startRecording(@Nullable Uri programUri) {
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget();
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22095961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Stops recording. */
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void stopRecording() {
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_STOP_RECORDING);
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22695961816a768da387f0b5523cf4363ace2044089Nick Chalko    /** Releases all resources. */
22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @MainThread
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void release() {
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeCallbacksAndMessages(null);
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.sendEmptyMessage(MSG_RELEASE);
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public boolean handleMessage(Message msg) {
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        switch (msg.what) {
23695961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_TUNE:
23795961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
23895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Uri channelUri = (Uri) msg.obj;
23995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    int retryCount = msg.arg1;
24095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Tune to " + channelUri);
24195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (doTune(channelUri)) {
24295961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (mSessionState == STATE_TUNED) {
24395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mSession.onTuned(channelUri);
244633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        } else {
24595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            Log.w(TAG, "Tuner stream cannot be created due to resource shortage.");
24695961816a768da387f0b5523cf4363ace2044089Nick Chalko                            if (retryCount < MAX_TUNING_RETRY) {
24795961816a768da387f0b5523cf4363ace2044089Nick Chalko                                Message tuneMsg =
24895961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        mHandler.obtainMessage(
24995961816a768da387f0b5523cf4363ace2044089Nick Chalko                                                MSG_TUNE, retryCount + 1, 0, channelUri);
25095961816a768da387f0b5523cf4363ace2044089Nick Chalko                                mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS);
25195961816a768da387f0b5523cf4363ace2044089Nick Chalko                            } else {
25295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY);
25395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                reset();
25495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            }
255633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        }
256633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    }
257d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    return true;
258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
25995961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_START_RECORDING:
26095961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
26195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Start recording");
26295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (!doStartRecording((Uri) msg.obj)) {
26395961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
264d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
26795961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_PREPARE_RECODER:
26895961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
26995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Preparing recorder");
27095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (!mRecorderRunning) {
27195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
27295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
27395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    try {
27495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (!mRecorder.prepare()) {
27595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            mHandler.sendEmptyMessageDelayed(
27695961816a768da387f0b5523cf4363ace2044089Nick Chalko                                    MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS);
27795961816a768da387f0b5523cf4363ace2044089Nick Chalko                        }
27895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    } catch (IOException e) {
27995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor");
28095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
28195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
28295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
28595961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_STOP_RECORDING:
28695961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
28795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (DEBUG) Log.d(TAG, "Stop recording");
28895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (mSessionState != STATE_RECORDING) {
28995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
29095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
29195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
29295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (mRecorderRunning) {
29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        stopRecorder();
29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
29695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
29795961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
29895961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_MONITOR_STORAGE_STATUS:
29995961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
30095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    if (mSessionState != STATE_RECORDING) {
30195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        return true;
30295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
303944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (!mRecordingStorageStatusManager.isStorageSufficient()) {
30495961816a768da387f0b5523cf4363ace2044089Nick Chalko                        if (mRecorderRunning) {
30595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            stopRecorder();
30695961816a768da387f0b5523cf4363ace2044089Nick Chalko                        }
30795961816a768da387f0b5523cf4363ace2044089Nick Chalko                        new DeleteRecordingTask().execute(mStorageDir);
30895961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
30995961816a768da387f0b5523cf4363ace2044089Nick Chalko                        reset();
31095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    } else {
31195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        mHandler.sendEmptyMessageDelayed(
31295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS);
31395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    }
31495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
31595961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
31695961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_RELEASE:
31795961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
31895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    // Since release was requested, current recording will be cancelled
31995961816a768da387f0b5523cf4363ace2044089Nick Chalko                    // without notification.
32065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    reset();
32195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mSourceManager.release();
32295961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mHandler.removeCallbacksAndMessages(null);
32395961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mHandler.getLooper().quitSafely();
32495961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
32595961816a768da387f0b5523cf4363ace2044089Nick Chalko                }
32695961816a768da387f0b5523cf4363ace2044089Nick Chalko            case MSG_UPDATE_CC_INFO:
32795961816a768da387f0b5523cf4363ace2044089Nick Chalko                {
32895961816a768da387f0b5523cf4363ace2044089Nick Chalko                    Pair<TunerChannel, List<EitItem>> pair =
32995961816a768da387f0b5523cf4363ace2044089Nick Chalko                            (Pair<TunerChannel, List<EitItem>>) msg.obj;
33095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    updateCaptionTracks(pair.first, pair.second);
33195961816a768da387f0b5523cf4363ace2044089Nick Chalko                    return true;
33265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return false;
33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Nullable
33865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerChannel getChannel(Uri channelUri) {
33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (channelUri == null) {
34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return null;
34165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long channelId;
34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        try {
34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            channelId = ContentUris.parseId(channelUri);
34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        } catch (UnsupportedOperationException | NumberFormatException e) {
34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            channelId = CHANNEL_ID_NONE;
34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId);
34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private String getStorageKey() {
35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        long prefix = System.currentTimeMillis();
35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        int suffix = mRandom.nextInt();
35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix);
35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void reset() {
35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorder != null) {
35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder.release();
36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder = null;
36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mTunerSource != null) {
36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSourceManager.releaseDataSource(mTunerSource);
36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTunerSource = null;
36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
366633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager = null;
36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_IDLE;
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = false;
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean doTune(Uri channelUri) {
372633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) {
37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Tuning was requested from wrong status.");
37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannel = getChannel(channelUri);
37865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mChannel == null) {
37965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel);
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
382633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        } else if (mChannel.isRecordingProhibited()) {
383633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
384633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel);
385633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return false;
38665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
387944779887775bd950cf1abf348d2df461593f6abLive Channels Team        if (!mRecordingStorageStatusManager.isStorageSufficient()) {
38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Tuning failed due to insufficient storage.");
39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this);
39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mTunerSource == null) {
394633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            // Retry tuning in this case.
395633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            mSessionState = STATE_TUNING;
396633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return true;
39765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_TUNED;
39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return true;
40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean doStartRecording(@Nullable Uri programUri) {
40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mSessionState != STATE_TUNED) {
40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Recording session status abnormal");
40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
40895961816a768da387f0b5523cf4363ace2044089Nick Chalko        mStorageDir =
409944779887775bd950cf1abf348d2df461593f6abLive Channels Team                mRecordingStorageStatusManager.isStorageSufficient()
41095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        ? new File(
411944779887775bd950cf1abf348d2df461593f6abLive Channels Team                                mRecordingStorageStatusManager.getRecordingRootDataDirectory(),
41295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                getStorageKey())
41395961816a768da387f0b5523cf4363ace2044089Nick Chalko                        : null;
41465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mStorageDir == null) {
41565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
41665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Failed to start recording due to insufficient storage.");
41765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return false;
41865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
41965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Since tuning might be happened a while ago, shifts the start position of tuned source.
42065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition());
42165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecordStartTime = System.currentTimeMillis();
422633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
42395961816a768da387f0b5523cf4363ace2044089Nick Chalko        mRecorder =
42495961816a768da387f0b5523cf4363ace2044089Nick Chalko                new ExoPlayerSampleExtractor(
42595961816a768da387f0b5523cf4363ace2044089Nick Chalko                        Uri.EMPTY, mTunerSource, new BufferManager(mDvrStorageManager), this, true);
42665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorder.setOnCompletionListener(this, mHandler);
42765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mProgramUri = programUri;
42865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSessionState = STATE_RECORDING;
42965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = true;
430d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mHandler.sendEmptyMessage(MSG_PREPARE_RECODER);
43165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
43295961816a768da387f0b5523cf4363ace2044089Nick Chalko        mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS, STORAGE_MONITOR_INTERVAL_MS);
43365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return true;
43465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
43665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void stopRecorder() {
43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Do not change session status.
43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorder != null) {
43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder.release();
44065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecordEndTime = System.currentTimeMillis();
44165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mRecorder = null;
44265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
44365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mRecorderRunning = false;
44465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
44565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Log.i(TAG, "Recording stopped");
44665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
44765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
448633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) {
44995961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (mChannel == null
45095961816a768da387f0b5523cf4363ace2044089Nick Chalko                || channel == null
45195961816a768da387f0b5523cf4363ace2044089Nick Chalko                || mChannel.compareTo(channel) != 0
45295961816a768da387f0b5523cf4363ace2044089Nick Chalko                || items == null
45395961816a768da387f0b5523cf4363ace2044089Nick Chalko                || items.isEmpty()) {
454633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return;
455633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
456633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        PsipData.EitItem currentProgram = getCurrentProgram(items);
45795961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (currentProgram == null
45895961816a768da387f0b5523cf4363ace2044089Nick Chalko                || !currentProgram.hasCaptionTrack()
459a1589bd48e05abbee991e0cdd27fa402a5dc5001Live Channels Team                || (mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0)) {
460633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            return;
461633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
462633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mCurrenProgram = currentProgram;
463633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks());
464633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        if (DEBUG) {
46595961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.d(
46695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
46795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "updated " + mCaptionTracks.size() + " caption tracks for " + currentProgram);
468633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
469633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    }
470633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
471633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) {
472633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        for (PsipData.EitItem item : items) {
473633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            if (mRecordStartTime >= item.getStartTimeUtcMillis()
474633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    && mRecordStartTime < item.getEndTimeUtcMillis()) {
475633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                return item;
476633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
477633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        }
478633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        return null;
479633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    }
480633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko
48165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Program getRecordedProgram() {
48265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        ContentResolver resolver = mContext.getContentResolver();
48365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Uri programUri = mProgramUri;
48465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mProgramUri == null) {
48565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            long avg = mRecordStartTime / 2 + mRecordEndTime / 2;
48665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg);
48765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
488944779887775bd950cf1abf348d2df461593f6abLive Channels Team        try (Cursor c = resolver.query(programUri, PROGRAM_PROJECTION, null, null, SORT_BY_TIME)) {
489944779887775bd950cf1abf348d2df461593f6abLive Channels Team            if (c != null && c.moveToNext()) {
490944779887775bd950cf1abf348d2df461593f6abLive Channels Team                Program result = Program.fromCursor(c);
49165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (DEBUG) {
49265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.v(TAG, "Finished query for " + this);
49365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
49465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return result;
49565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            } else {
49665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (c == null) {
49765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.e(TAG, "Unknown query error for " + this);
49865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                } else {
499944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (DEBUG) Log.d(TAG, "Can not find program:" + programUri);
50065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
50165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return null;
50265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
50365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
50465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
50565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
50695961816a768da387f0b5523cf4363ace2044089Nick Chalko    private Uri insertRecordedProgram(
50795961816a768da387f0b5523cf4363ace2044089Nick Chalko            Program program,
50895961816a768da387f0b5523cf4363ace2044089Nick Chalko            long channelId,
50995961816a768da387f0b5523cf4363ace2044089Nick Chalko            String storageUri,
51095961816a768da387f0b5523cf4363ace2044089Nick Chalko            long totalBytes,
51195961816a768da387f0b5523cf4363ace2044089Nick Chalko            long startTime,
51295961816a768da387f0b5523cf4363ace2044089Nick Chalko            long endTime) {
513944779887775bd950cf1abf348d2df461593f6abLive Channels Team        ContentValues values = new ContentValues();
514944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_INPUT_ID, mInputId);
515944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
516944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, storageUri);
517944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, endTime - startTime);
518944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, totalBytes);
519944779887775bd950cf1abf348d2df461593f6abLive Channels Team        // startTime and endTime could be overridden by program's start and end value.
520944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
521944779887775bd950cf1abf348d2df461593f6abLive Channels Team        values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
52265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (program != null) {
523944779887775bd950cf1abf348d2df461593f6abLive Channels Team            values.putAll(program.toContentValues());
52465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
52595961816a768da387f0b5523cf4363ace2044089Nick Chalko        return mContext.getContentResolver()
52695961816a768da387f0b5523cf4363ace2044089Nick Chalko                .insert(TvContract.RecordedPrograms.CONTENT_URI, values);
52765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
52865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
52965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private void onRecordingResult(boolean success, long lastExtractedPositionUs) {
53065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mSessionState != STATE_RECORDING) {
53165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Error notification is not needed.
53265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Recording session status abnormal");
53365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
53465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
53565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (mRecorderRunning) {
53665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // In case of recorder not being stopped, because of premature termination of recording.
53765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            stopRecorder();
53865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
53995961816a768da387f0b5523cf4363ace2044089Nick Chalko        if (!success
54095961816a768da387f0b5523cf4363ace2044089Nick Chalko                && lastExtractedPositionUs
54195961816a768da387f0b5523cf4363ace2044089Nick Chalko                        < TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) {
542d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            new DeleteRecordingTask().execute(mStorageDir);
54365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
54465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.w(TAG, "Recording failed during recording");
54565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
54665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
54765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        Log.i(TAG, "recording finished " + (success ? "completely" : "partially"));
548633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        long recordEndTime =
549633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                (lastExtractedPositionUs == C.UNKNOWN_TIME_US)
550633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        ? System.currentTimeMillis()
551633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        : mRecordStartTime + lastExtractedPositionUs / 1000;
552633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        Uri uri =
553633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                insertRecordedProgram(
554633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        getRecordedProgram(),
555633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        mChannel.getChannelId(),
556633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        Uri.fromFile(mStorageDir).toString(),
557633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        1024 * 1024,
558633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        mRecordStartTime,
559633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        recordEndTime);
56065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (uri == null) {
561d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            new DeleteRecordingTask().execute(mStorageDir);
56265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
56365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.e(TAG, "Inserting a recording to DB failed");
56465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return;
56565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
566633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks);
56765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSession.onRecordFinished(uri);
56865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
569d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
570d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> {
571d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
572d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
573d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public Void doInBackground(File... files) {
574d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (files == null || files.length == 0) {
575d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                return null;
576d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
57795961816a768da387f0b5523cf4363ace2044089Nick Chalko            for (File file : files) {
578944779887775bd950cf1abf348d2df461593f6abLive Channels Team                CommonUtils.deleteDirOrFile(file);
579d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
580d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return null;
581d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
582d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
58365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}
584