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