148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho/*
248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Copyright (C) 2015 The Android Open Source Project
348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Licensed under the Apache License, Version 2.0 (the "License");
548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * you may not use this file except in compliance with the License.
648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * You may obtain a copy of the License at
748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *      http://www.apache.org/licenses/LICENSE-2.0
948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
1048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Unless required by applicable law or agreed to in writing, software
1148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * distributed under the License is distributed on an "AS IS" BASIS,
1248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * See the License for the specific language governing permissions and
1448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * limitations under the License.
1548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho */
1648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
1748dadb49248271b01997862e1335912a4f2e189fYoungsang Chopackage com.android.usbtuner.tvinput;
1848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
1948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.ContentResolver;
2048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.ContentUris;
2148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.ContentValues;
2248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.Context;
2348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.database.Cursor;
2448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.MediaDataSource;
2548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.tv.TvContract;
2648dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.tv.TvInputManager;
2748dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.net.Uri;
2848dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Handler;
2948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.HandlerThread;
3048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Looper;
3148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Message;
3248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.support.annotation.IntDef;
3348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.support.annotation.Nullable;
3448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.util.Log;
3548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.widget.Toast;
3648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
3748dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.google.android.exoplayer.util.Assertions;
3848dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.recording.RecordedProgram;
3948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.recording.RecordingCapability;
4048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.DvbDeviceAccessor;
4148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.TunerHal;
4248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.UsbTunerDataSource;
4348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.data.PsipData;
4448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.data.TunerChannel;
4548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.exoplayer.Recorder;
4648dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.exoplayer.cache.CacheManager;
4748dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.exoplayer.cache.DvrStorageManager;
4848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
4948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.io.File;
5048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.io.IOException;
5148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.lang.annotation.Retention;
5248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.lang.annotation.RetentionPolicy;
5348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.List;
5448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.Locale;
5548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.Random;
5648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
5748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho/**
5848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Implements a DVR feature.
5948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho */
6048dadb49248271b01997862e1335912a4f2e189fYoungsang Chopublic class TunerRecordingSessionWorker implements PlaybackCacheListener,
6148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        EventDetector.EventListener, Recorder.RecordListener,
6248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Handler.Callback {
6348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static String TAG = "TunerRecordingSessionWorker";
6448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final boolean DEBUG = false;
6548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
6648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final String SORT_BY_TIME = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
6748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            + ", " + TvContract.Programs.COLUMN_CHANNEL_ID + ", "
6848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS;
6948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_CONNECT = 1;
7048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_DISCONNECT = 2;
7148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_START_RECORDING = 3;
7248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_STOP_RECORDING = 4;
7348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_RECORDING_RESULT = 5;
7448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_DELETE_RECORDING = 6;
7548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int MSG_RELEASE = 7;
7648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private RecordingCapability mCapabilities;
7748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
7848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public RecordingCapability getCapabilities() {
7948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mCapabilities;
8048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
8148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
8248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @IntDef({STATE_IDLE, STATE_CONNECTED, STATE_RECORDING})
8348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Retention(RetentionPolicy.SOURCE)
8448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public @interface DvrSessionState {}
8548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int STATE_IDLE = 1;
8648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int STATE_CONNECTED = 2;
8748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int STATE_RECORDING = 3;
8848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
8948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final long CHANNEL_ID_NONE = -1;
9048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
9148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Context mContext;
9248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final ChannelDataManager mChannelDataManager;
9348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Handler mHandler;
9448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Random mRandom = new Random();
9548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
9648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private TunerHal mTunerHal;
9748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private UsbTunerDataSource mTunerSource;
9848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private TunerChannel mChannel;
9948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private File mStorageDir;
10048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private long mRecordStartTime;
10148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private long mRecordEndTime;
10248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private CacheManager mCacheManager;
10348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private Recorder mRecorder;
10448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final TunerRecordingSession mSession;
10548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @DvrSessionState private int mSessionState = STATE_IDLE;
10648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final String mInputId;
10748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
10848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public TunerRecordingSessionWorker(Context context, String inputId,
10948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            ChannelDataManager dataManager, TunerRecordingSession session) {
11048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mRandom.setSeed(System.nanoTime());
11148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mContext = context;
11248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        HandlerThread handlerThread = new HandlerThread(TAG);
11348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        handlerThread.start();
11448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler = new Handler(handlerThread.getLooper(), this);
11548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mChannelDataManager = dataManager;
11648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mChannelDataManager.checkDataVersion(context);
11748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId);
11848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mInputId = inputId;
11948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (DEBUG) Log.d(TAG, mCapabilities.toString());
12048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSession = session;
12148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
12248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
12348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    // PlaybackCacheListener
12448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
12548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void onCacheStartTimeChanged(long startTimeMs) {
12648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
12748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
12848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
12948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void onCacheStateChanged(boolean available) {
13048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
13148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
13248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
13348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void onDiskTooSlow() {
13448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
13548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
13648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    // EventDetector.EventListener
13748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
13848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
13948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mChannel == null || mChannel.compareTo(channel) != 0) {
14048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return;
14148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
14248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
14348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
14448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
14548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
14648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
14748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mChannel == null || mChannel.compareTo(channel) != 0) {
14848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return;
14948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
15048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mChannelDataManager.notifyEventDetected(channel, items);
15148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
15248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
15348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void connect(Uri channelUri) {
15448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.removeCallbacksAndMessages(null);
15548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.obtainMessage(MSG_CONNECT, channelUri).sendToTarget();
15648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
15748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
15848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void disconnect() {
15948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.sendEmptyMessage(MSG_DISCONNECT);
16048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
16148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
16248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void startRecording() {
16348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.sendEmptyMessage(MSG_START_RECORDING);
16448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
16548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
16648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void stopRecording() {
16748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.sendEmptyMessage(MSG_STOP_RECORDING);
16848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
16948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
17048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void notifyRecordingFinished(boolean success) {
17148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.obtainMessage(MSG_RECORDING_RESULT, success).sendToTarget();
17248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
17348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
17448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void deleteRecording(Uri mediaUri) {
17548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.obtainMessage(MSG_DELETE_RECORDING, mediaUri).sendToTarget();
17648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
17748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
17848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void release() {
17948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.removeCallbacksAndMessages(null);
18048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.sendEmptyMessage(MSG_RELEASE);
18148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
18248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
18348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
18448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public boolean handleMessage(Message msg) {
18548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // TODO: Add RecordStopped status
18648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        switch (msg.what) {
18748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_CONNECT: {
18848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Uri channelUri = (Uri) msg.obj;
18948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (onConnect(channelUri)) {
19048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mSession.onTuned(channelUri);
19148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                } else {
19248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Log.w(TAG, "Recording session connect failed");
19348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mSession.onConnectFailed();
19448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
19548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
19648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
19748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_START_RECORDING: {
19848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if(onStartRecording()) {
19948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Toast.makeText(mContext, "USB TV tuner: Recording started",
20048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            Toast.LENGTH_SHORT).show();
20148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
20248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                else {
20348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mSession.onRecordUnexpectedlyStopped(TvInputManager.RECORDING_ERROR_UNKNOWN);
20448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
20548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
20648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
20748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_DISCONNECT: {
20848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
20948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
21048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_STOP_RECORDING: {
21148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                onStopRecording();
21248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                new Handler(Looper.getMainLooper()).post(new Runnable() {
21348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    @Override
21448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    public void run() {
21548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        Toast.makeText(mContext, "USB TV tuner: Recording stopped",
21648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                Toast.LENGTH_SHORT).show();
21748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    }
21848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                });
21948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
22048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
22148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_RECORDING_RESULT: {
22248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                onRecordingResult((Boolean) msg.obj);
22348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
22448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
22548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_DELETE_RECORDING: {
22648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Uri toDelete = (Uri) msg.obj;
22748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                onDeleteRecording(toDelete);
22848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
22948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
23048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            case MSG_RELEASE: {
23148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                onRelease();
23248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return true;
23348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
23448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
23548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return false;
23648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
23748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
23848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Nullable
23948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private TunerChannel getChannel(Uri channelUri) {
24048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (channelUri == null) {
24148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return null;
24248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
24348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        long channelId;
24448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        try {
24548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            channelId = ContentUris.parseId(channelUri);
24648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        } catch (UnsupportedOperationException | NumberFormatException e) {
24748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            channelId = CHANNEL_ID_NONE;
24848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
24948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId);
25048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
25148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
25248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private String getStorageKey() {
25348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        long prefix = System.currentTimeMillis();
25448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        int suffix = mRandom.nextInt();
25548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix);
25648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
25748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
25848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private File getMediaDir(String storageKey) {
25948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return new File(mContext.getCacheDir().getAbsolutePath() + "/recording/" + storageKey);
26048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
26148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
26248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private File getMediaDir(Uri mediaUri) {
26348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        String mediaPath = mediaUri.getPath();
26448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mediaPath == null || mediaPath.length() == 0) {
26548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return null;
26648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
26748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return new File(mContext.getCacheDir().getAbsolutePath() + "/recording" +
26848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mediaUri.getPath());
26948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
27048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
27148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void reset() {
27248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mRecorder != null) {
27348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder.release();
27448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder = null;
27548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
27648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mCacheManager != null) {
27748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCacheManager.close();
27848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCacheManager = null;
27948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
28048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mTunerSource != null) {
28148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTunerSource.stopStream();
28248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTunerSource = null;
28348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
28448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mTunerHal != null) {
28548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            try {
28648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mTunerHal.close();
28748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } catch (Exception ex) {
28848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Log.e(TAG, "Error on closing tuner HAL.", ex);
28948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
29048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTunerHal = null;
29148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
29248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSessionState = STATE_IDLE;
29348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
29448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
29548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void resetRecorder() {
29648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Assertions.checkArgument(mSessionState != STATE_IDLE);
29748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mRecorder != null) {
29848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder.release();
29948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder = null;
30048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
30148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mCacheManager != null) {
30248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCacheManager.close();
30348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCacheManager = null;
30448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
30548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mTunerSource != null) {
30648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTunerSource.stopStream();
30748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTunerSource = null;
30848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
30948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSessionState = STATE_CONNECTED;
31048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
31148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
31248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean onConnect(Uri channelUri) {
31348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState == STATE_RECORDING) {
31448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
31548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
31648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mChannel = getChannel(channelUri);
31748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mChannel == null) {
31848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel);
31948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
32048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
32148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState == STATE_CONNECTED) {
32248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return true;
32348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
32448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mTunerHal = TunerHal.createInstance(mContext);
32548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mTunerHal == null) {
32648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Failed to start recording. Couldn't open a DVB device");
32748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            reset();
32848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
32948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
33048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSessionState = STATE_CONNECTED;
33148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return true;
33248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
33348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
33448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean onStartRecording() {
33548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState != STATE_CONNECTED) {
33648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
33748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
33848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mStorageDir = getMediaDir(getStorageKey());
33948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mTunerSource = new UsbTunerDataSource(mTunerHal, this);
34048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (!mTunerSource.tuneToChannel(mChannel)) {
34148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Failed to start recording. Couldn't tune to the channel for " +
34248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mChannel.toString());
34348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            resetRecorder();
34448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
34548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
34648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mCacheManager = new CacheManager(new DvrStorageManager(mStorageDir, true));
34748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mTunerSource.startStream();
34848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mRecordStartTime = System.currentTimeMillis();
34948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mRecorder = new Recorder((MediaDataSource) mTunerSource,
35048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mCacheManager, this, this);
35148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        try {
35248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder.prepare();
35348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        } catch (IOException e) {
35448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Failed to start recording. Couldn't prepare a extractor");
35548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            resetRecorder();
35648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return false;
35748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
35848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSessionState = STATE_RECORDING;
35948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return true;
36048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
36148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
36248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void onStopRecording() {
36348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState != STATE_RECORDING) {
36448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return;
36548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
36648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // Do not change session status.
36748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mRecorder != null) {
36848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder.release();
36948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecordEndTime = System.currentTimeMillis();
37048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecorder = null;
37148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
37248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
37348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
37448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static class Program {
37548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private long mChannelId;
37648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mTitle;
37748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mEpisodeTitle;
37848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private int mSeasonNumber;
37948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private int mEpisodeNumber;
38048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mDescription;
38148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mPosterArtUri;
38248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mThumbnailUri;
38348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mCanonicalGenres;
38448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private String mContentRatings;
38548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private long mStartTimeUtcMillis;
38648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private long mEndTimeUtcMillis;
38748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private long mVideoWidth;
38848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private long mVideoHeight;
38948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
39048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private static final String[] PROJECTION = {
39148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_CHANNEL_ID,
39248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_TITLE,
39348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_EPISODE_TITLE,
39448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_SEASON_NUMBER,
39548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_EPISODE_NUMBER,
39648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
39748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_POSTER_ART_URI,
39848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_THUMBNAIL_URI,
39948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_CANONICAL_GENRE,
40048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_CONTENT_RATING,
40148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
40248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
40348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_VIDEO_WIDTH,
40448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                TvContract.Programs.COLUMN_VIDEO_HEIGHT
40548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        };
40648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
40748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public Program(Cursor cursor) {
40848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            int index = 0;
40948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mChannelId = cursor.getLong(index++);
41048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTitle = cursor.getString(index++);
41148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEpisodeTitle = cursor.getString(index++);
41248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mSeasonNumber = cursor.getInt(index++);
41348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEpisodeNumber = cursor.getInt(index++);
41448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mDescription = cursor.getString(index++);
41548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPosterArtUri = cursor.getString(index++);
41648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mThumbnailUri = cursor.getString(index++);
41748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCanonicalGenres = cursor.getString(index++);
41848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mContentRatings = cursor.getString(index++);
41948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mStartTimeUtcMillis = cursor.getLong(index++);
42048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEndTimeUtcMillis = cursor.getLong(index++);
42148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mVideoWidth = cursor.getLong(index++);
42248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mVideoHeight = cursor.getLong(index++);
42348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
42448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
42548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public Program(long channelId) {
42648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mChannelId = channelId;
42748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTitle = "Unknown";
42848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEpisodeTitle = "";
42948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mSeasonNumber = 0;
43048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEpisodeNumber = 0;
43148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mDescription = "Unknown";
43248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPosterArtUri = null;
43348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mThumbnailUri = null;
43448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCanonicalGenres = null;
43548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mContentRatings = null;
43648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mStartTimeUtcMillis = 0;
43748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEndTimeUtcMillis = 0;
43848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mVideoWidth = 0;
43948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mVideoHeight = 0;
44048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
44148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
44248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public static Program onQuery(Cursor c) {
44348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Program program = null;
44448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (c != null && c.moveToNext()) {
44548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                program = new Program(c);
44648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
44748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return program;
44848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
44948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
45048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public ContentValues buildValues() {
45148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            ContentValues values = new ContentValues();
45248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[0], mChannelId);
45348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[1], mTitle);
45448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[2], mEpisodeTitle);
45548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[3], mSeasonNumber);
45648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[4], mEpisodeNumber);
45748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[5], mDescription);
45848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[6], mPosterArtUri);
45948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[7], mThumbnailUri);
46048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[8], mCanonicalGenres);
46148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[9], mContentRatings);
46248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[10], mStartTimeUtcMillis);
46348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[11], mEndTimeUtcMillis);
46448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[12], mVideoWidth);
46548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            values.put(PROJECTION[13], mVideoHeight);
46648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return values;
46748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
46848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
46948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
47048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private Program getRecordedProgram() {
47148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        ContentResolver resolver = mContext.getContentResolver();
47248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        long avg = mRecordStartTime / 2 + mRecordEndTime / 2;
47348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Uri programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg);
47448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        try (Cursor c = resolver.query(programUri, Program.PROJECTION, null, null, SORT_BY_TIME)) {
47548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (c != null) {
47648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Program result = Program.onQuery(c);
47748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (DEBUG) {
47848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Log.v(TAG, "Finished query for " + this);
47948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
48048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return result;
48148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } else {
48248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (c == null) {
48348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Log.e(TAG, "Unknown query error for " + this);
48448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                } else {
48548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    if (DEBUG) {
48648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        Log.d(TAG, "Canceled query for " + this);
48748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    }
48848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
48948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return null;
49048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
49148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
49248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
49348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
49448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private Uri insertRecordedProgram(Program program, long channelId, String storageUri,
49548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            long totalBytes, long startTime, long endTime) {
49648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        RecordedProgram recordedProgram = RecordedProgram.builder()
49748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .setInputId(mInputId)
49848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .setChannelId(channelId)
49948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .setDataUri(storageUri)
50048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .setDurationMillis(endTime - startTime)
50148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .setDataBytes(totalBytes)
50248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                .build();
50348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Uri uri = mContext.getContentResolver().insert(TvContract.RecordedPrograms.CONTENT_URI,
50448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                RecordedProgram.toValues(recordedProgram));
50548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return uri;
50648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
50748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
50848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean onRecordingResult(boolean success) {
50948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState == STATE_RECORDING && success) {
51048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Uri uri = insertRecordedProgram(getRecordedProgram(), mChannel.getChannelId(),
51148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mStorageDir.toURI().toString(), 1024 * 1024,
51248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mRecordStartTime, mRecordEndTime);
51348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (uri != null) {
51448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mSession.onRecordFinished(uri);
51548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
51648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            resetRecorder();
51748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return true;
51848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
51948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
52048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSessionState == STATE_RECORDING) {
52148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mSession.onRecordUnexpectedlyStopped(TvInputManager.RECORDING_ERROR_UNKNOWN);
52248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Recording failed: " + mChannel == null ? "" : mChannel.toString());
52348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            resetRecorder();
52448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        } else {
52548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.e(TAG, "Recording session status abnormal");
52648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            reset();
52748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
52848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return false;
52948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
53048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
53148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void onDeleteRecording(Uri mediaUri) {
53248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // TODO: notify the deletion result to LiveChannels
53348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        File mediaDir = getMediaDir(mediaUri);
53448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mediaDir == null) {
53548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return;
53648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
53748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        for(File file: mediaDir.listFiles()) {
53848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            file.delete();
53948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
54048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mediaDir.delete();
54148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
54248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
54348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void onRelease() {
54448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // Current recording will be canceled.
54548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        reset();
54648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mHandler.getLooper().quitSafely();
54748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // TODO: Remove failed recording files.
54848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
54948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho}
550