diff options
Diffstat (limited to 'src/com/android/tv/dvr/DvrStorageStatusManager.java')
-rw-r--r-- | src/com/android/tv/dvr/DvrStorageStatusManager.java | 334 |
1 files changed, 54 insertions, 280 deletions
diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java index 2d41d732..ed8d6903 100644 --- a/src/com/android/tv/dvr/DvrStorageStatusManager.java +++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,291 +11,55 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ - package com.android.tv.dvr; -import android.content.BroadcastReceiver; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.OperationApplicationException; import android.database.Cursor; -import android.media.tv.TvContract; import android.media.tv.TvInputInfo; import android.net.Uri; import android.os.AsyncTask; -import android.os.Environment; -import android.os.Looper; import android.os.RemoteException; -import android.os.StatFs; -import android.support.annotation.AnyThread; -import android.support.annotation.IntDef; -import android.support.annotation.WorkerThread; +import android.support.media.tv.TvContractCompat; import android.util.Log; - -import com.android.tv.TvApplication; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.tuner.tvinput.TunerTvInputService; +import com.android.tv.TvSingletons; +import com.android.tv.common.recording.RecordingStorageStatusManager; +import com.android.tv.common.util.CommonUtils; import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; - import java.io.File; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -/** - * Signals DVR storage status change such as plugging/unplugging. - */ -public class DvrStorageStatusManager { +/** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */ +public class DvrStorageStatusManager extends RecordingStorageStatusManager { private static final String TAG = "DvrStorageStatusManager"; - private static final boolean DEBUG = false; - - /** - * Minimum storage size to support DVR - */ - public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB - private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES - = 10 * 1024 * 1024 * 1024L; // 10GB - private static final String RECORDING_DATA_SUB_PATH = "/recording"; - - private static final String[] PROJECTION = { - TvContract.RecordedPrograms._ID, - TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME, - TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI - }; - private final static int BATCH_OPERATION_COUNT = 100; - - @IntDef({STORAGE_STATUS_OK, STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL, - STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, STORAGE_STATUS_MISSING}) - @Retention(RetentionPolicy.SOURCE) - public @interface StorageStatus { - } - - /** - * Current storage is OK to record a program. - */ - public static final int STORAGE_STATUS_OK = 0; - - /** - * Current storage's total capacity is smaller than DVR requirement. - */ - public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1; - - /** - * Current storage's free space is insufficient to record programs. - */ - public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2; - - /** - * Current storage is missing. - */ - public static final int STORAGE_STATUS_MISSING = 3; private final Context mContext; - private final Set<OnStorageMountChangedListener> mOnStorageMountChangedListeners = - new CopyOnWriteArraySet<>(); - private final boolean mRunningInMainProcess; - private MountedStorageStatus mMountedStorageStatus; - private boolean mStorageValid; private CleanUpDbTask mCleanUpDbTask; - private class MountedStorageStatus { - private final boolean mStorageMounted; - private final File mStorageMountedDir; - private final long mStorageMountedCapacity; - - private MountedStorageStatus(boolean mounted, File mountedDir, long capacity) { - mStorageMounted = mounted; - mStorageMountedDir = mountedDir; - mStorageMountedCapacity = capacity; - } - - private boolean isValidForDvr() { - return mStorageMounted && mStorageMountedCapacity >= MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof MountedStorageStatus)) { - return false; - } - MountedStorageStatus status = (MountedStorageStatus) other; - return mStorageMounted == status.mStorageMounted - && Objects.equals(mStorageMountedDir, status.mStorageMountedDir) - && mStorageMountedCapacity == status.mStorageMountedCapacity; - } - } - - public interface OnStorageMountChangedListener { - - /** - * Listener for DVR storage status change. - * - * @param storageMounted {@code true} when DVR possible storage is mounted, - * {@code false} otherwise. - */ - void onStorageMountChanged(boolean storageMounted); - } - - private final class StorageStatusBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - MountedStorageStatus result = getStorageStatusInternal(); - if (mMountedStorageStatus.equals(result)) { - return; - } - mMountedStorageStatus = result; - if (result.mStorageMounted && mRunningInMainProcess) { - // Cleans up DB in LC process. - // Tuner process is not always on. - if (mCleanUpDbTask != null) { - mCleanUpDbTask.cancel(true); - } - mCleanUpDbTask = new CleanUpDbTask(); - mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - boolean valid = result.isValidForDvr(); - if (valid == mStorageValid) { - return; - } - mStorageValid = valid; - for (OnStorageMountChangedListener l : mOnStorageMountChangedListeners) { - l.onStorageMountChanged(valid); - } - } - } + private static final String[] PROJECTION = { + TvContractCompat.RecordedPrograms._ID, + TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME, + TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI + }; + private static final int BATCH_OPERATION_COUNT = 100; - /** - * Creates DvrStorageStatusManager. - * - * @param context {@link Context} - */ - public DvrStorageStatusManager(final Context context, boolean runningInMainProcess) { + public DvrStorageStatusManager(Context context) { + super(context); mContext = context; - mRunningInMainProcess = runningInMainProcess; - mMountedStorageStatus = getStorageStatusInternal(); - mStorageValid = mMountedStorageStatus.isValidForDvr(); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MEDIA_MOUNTED); - filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - filter.addAction(Intent.ACTION_MEDIA_EJECT); - filter.addAction(Intent.ACTION_MEDIA_REMOVED); - filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); - filter.addDataScheme(ContentResolver.SCHEME_FILE); - mContext.registerReceiver(new StorageStatusBroadcastReceiver(), filter); } - /** - * Adds the listener for receiving storage status change. - * - * @param listener - */ - public void addListener(OnStorageMountChangedListener listener) { - mOnStorageMountChangedListeners.add(listener); - } - - /** - * Removes the current listener. - */ - public void removeListener(OnStorageMountChangedListener listener) { - mOnStorageMountChangedListeners.remove(listener); - } - - /** - * Returns true if a storage is mounted. - */ - public boolean isStorageMounted() { - return mMountedStorageStatus.mStorageMounted; - } - - /** - * Returns the path to DVR recording data directory. - * This can take for a while sometimes. - */ - @WorkerThread - public File getRecordingRootDataDirectory() { - SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper()); - if (mMountedStorageStatus.mStorageMountedDir == null) { - return null; - } - File root = mContext.getExternalFilesDir(null); - String rootPath; - try { - rootPath = root != null ? root.getCanonicalPath() : null; - } catch (IOException | SecurityException e) { - return null; - } - return rootPath == null ? null : new File(rootPath + RECORDING_DATA_SUB_PATH); - } - - /** - * Returns the current storage status for DVR recordings. - * - * @return {@link StorageStatus} - */ - @AnyThread - public @StorageStatus int getDvrStorageStatus() { - MountedStorageStatus status = mMountedStorageStatus; - if (status.mStorageMountedDir == null) { - return STORAGE_STATUS_MISSING; - } - if (CommonFeatures.FORCE_RECORDING_UNTIL_NO_SPACE.isEnabled(mContext)) { - return STORAGE_STATUS_OK; - } - if (status.mStorageMountedCapacity < MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES) { - return STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL; - } - try { - StatFs statFs = new StatFs(status.mStorageMountedDir.toString()); - if (statFs.getAvailableBytes() < MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES) { - return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; - } - } catch (IllegalArgumentException e) { - // In rare cases, storage status change was not notified yet. - SoftPreconditions.checkState(false); - return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT; - } - return STORAGE_STATUS_OK; - } - - /** - * Returns whether the storage has sufficient storage. - * - * @return {@code true} when there is sufficient storage, {@code false} otherwise - */ - public boolean isStorageSufficient() { - return getDvrStorageStatus() == STORAGE_STATUS_OK; - } - - private MountedStorageStatus getStorageStatusInternal() { - boolean storageMounted = - Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); - File storageMountedDir = storageMounted ? Environment.getExternalStorageDirectory() : null; - storageMounted = storageMounted && storageMountedDir != null; - long storageMountedCapacity = 0L; - if (storageMounted) { - try { - StatFs statFs = new StatFs(storageMountedDir.toString()); - storageMountedCapacity = statFs.getTotalBytes(); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Storage mount status was changed."); - storageMounted = false; - storageMountedDir = null; - } + @Override + protected void cleanUpDbIfNeeded() { + if (mCleanUpDbTask != null) { + mCleanUpDbTask.cancel(true); } - return new MountedStorageStatus( - storageMounted, storageMountedDir, storageMountedCapacity); + mCleanUpDbTask = new CleanUpDbTask(); + mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> { @@ -307,26 +71,29 @@ public class DvrStorageStatusManager { @Override protected Boolean doInBackground(Void... params) { - @DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus(); - if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { + @StorageStatus int storageStatus = getDvrStorageStatus(); + if (storageStatus == STORAGE_STATUS_MISSING) { return null; } - if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { + if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { return true; } List<ContentProviderOperation> ops = getDeleteOps(); if (ops == null || ops.isEmpty()) { return null; } - Log.i(TAG, "New device storage mounted. # of recordings to be forgotten : " - + ops.size()); - for (int i = 0 ; i < ops.size() && !isCancelled() ; i += BATCH_OPERATION_COUNT) { - int toIndex = (i + BATCH_OPERATION_COUNT) > ops.size() - ? ops.size() : (i + BATCH_OPERATION_COUNT); + Log.i( + TAG, + "New device storage mounted. # of recordings to be forgotten : " + ops.size()); + for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) { + int toIndex = + (i + BATCH_OPERATION_COUNT) > ops.size() + ? ops.size() + : (i + BATCH_OPERATION_COUNT); ArrayList<ContentProviderOperation> batchOps = new ArrayList<>(ops.subList(i, toIndex)); try { - mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, batchOps); + mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps); } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, "Failed to clean up RecordedPrograms.", e); } @@ -337,16 +104,16 @@ public class DvrStorageStatusManager { @Override protected void onPostExecute(Boolean forgetStorage) { if (forgetStorage != null && forgetStorage == true) { - DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager(); + DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager(); TvInputManagerHelper tvInputManagerHelper = - TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + TvSingletons.getSingletons(mContext).getTvInputManagerHelper(); List<TvInputInfo> tvInputInfoList = tvInputManagerHelper.getTvInputInfos(true, false); if (tvInputInfoList == null || tvInputInfoList.isEmpty()) { return; } for (TvInputInfo info : tvInputInfoList) { - if (Utils.isBundledInput(info.getId())) { + if (CommonUtils.isBundledInput(info.getId())) { dvrManager.forgetStorage(info.getId()); } } @@ -359,16 +126,19 @@ public class DvrStorageStatusManager { private List<ContentProviderOperation> getDeleteOps() { List<ContentProviderOperation> ops = new ArrayList<>(); - try (Cursor c = mContentResolver.query( - TvContract.RecordedPrograms.CONTENT_URI, PROJECTION, null, null, null)) { + try (Cursor c = + mContentResolver.query( + TvContractCompat.RecordedPrograms.CONTENT_URI, + PROJECTION, + null, + null, + null)) { if (c == null) { return null; } while (c.moveToNext()) { - @DvrStorageStatusManager.StorageStatus int storageStatus = - getDvrStorageStatus(); - if (isCancelled() - || storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) { + @StorageStatus int storageStatus = getDvrStorageStatus(); + if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) { ops.clear(); break; } @@ -379,15 +149,19 @@ public class DvrStorageStatusManager { continue; } Uri dataUri = Uri.parse(dataUriString); - if (!Utils.isInBundledPackageSet(packageName) - || dataUri == null || dataUri.getPath() == null + if (!CommonUtils.isInBundledPackageSet(packageName) + || dataUri == null + || dataUri.getPath() == null || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { continue; } File recordedProgramDir = new File(dataUri.getPath()); if (!recordedProgramDir.exists()) { - ops.add(ContentProviderOperation.newDelete( - TvContract.buildRecordedProgramUri(Long.parseLong(id))).build()); + ops.add( + ContentProviderOperation.newDelete( + TvContractCompat.buildRecordedProgramUri( + Long.parseLong(id))) + .build()); } } return ops; |