1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tv.dvr; 17 18 import android.content.ContentProviderOperation; 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.OperationApplicationException; 22 import android.database.Cursor; 23 import android.media.tv.TvInputInfo; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.RemoteException; 27 import android.support.media.tv.TvContractCompat; 28 import android.util.Log; 29 import com.android.tv.TvSingletons; 30 import com.android.tv.common.recording.RecordingStorageStatusManager; 31 import com.android.tv.common.util.CommonUtils; 32 import com.android.tv.util.TvInputManagerHelper; 33 import java.io.File; 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */ 38 public class DvrStorageStatusManager extends RecordingStorageStatusManager { 39 private static final String TAG = "DvrStorageStatusManager"; 40 41 private final Context mContext; 42 private CleanUpDbTask mCleanUpDbTask; 43 44 private static final String[] PROJECTION = { 45 TvContractCompat.RecordedPrograms._ID, 46 TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME, 47 TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI 48 }; 49 private static final int BATCH_OPERATION_COUNT = 100; 50 51 public DvrStorageStatusManager(Context context) { 52 super(context); 53 mContext = context; 54 } 55 56 @Override 57 protected void cleanUpDbIfNeeded() { 58 if (mCleanUpDbTask != null) { 59 mCleanUpDbTask.cancel(true); 60 } 61 mCleanUpDbTask = new CleanUpDbTask(); 62 mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 63 } 64 65 private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> { 66 private final ContentResolver mContentResolver; 67 68 private CleanUpDbTask() { 69 mContentResolver = mContext.getContentResolver(); 70 } 71 72 @Override 73 protected Boolean doInBackground(Void... params) { 74 @StorageStatus int storageStatus = getDvrStorageStatus(); 75 if (storageStatus == STORAGE_STATUS_MISSING) { 76 return null; 77 } 78 if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { 79 return true; 80 } 81 List<ContentProviderOperation> ops = getDeleteOps(); 82 if (ops == null || ops.isEmpty()) { 83 return null; 84 } 85 Log.i( 86 TAG, 87 "New device storage mounted. # of recordings to be forgotten : " + ops.size()); 88 for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) { 89 int toIndex = 90 (i + BATCH_OPERATION_COUNT) > ops.size() 91 ? ops.size() 92 : (i + BATCH_OPERATION_COUNT); 93 ArrayList<ContentProviderOperation> batchOps = 94 new ArrayList<>(ops.subList(i, toIndex)); 95 try { 96 mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps); 97 } catch (RemoteException | OperationApplicationException e) { 98 Log.e(TAG, "Failed to clean up RecordedPrograms.", e); 99 } 100 } 101 return null; 102 } 103 104 @Override 105 protected void onPostExecute(Boolean forgetStorage) { 106 if (forgetStorage != null && forgetStorage == true) { 107 DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager(); 108 TvInputManagerHelper tvInputManagerHelper = 109 TvSingletons.getSingletons(mContext).getTvInputManagerHelper(); 110 List<TvInputInfo> tvInputInfoList = 111 tvInputManagerHelper.getTvInputInfos(true, false); 112 if (tvInputInfoList == null || tvInputInfoList.isEmpty()) { 113 return; 114 } 115 for (TvInputInfo info : tvInputInfoList) { 116 if (CommonUtils.isBundledInput(info.getId())) { 117 dvrManager.forgetStorage(info.getId()); 118 } 119 } 120 } 121 if (mCleanUpDbTask == this) { 122 mCleanUpDbTask = null; 123 } 124 } 125 126 private List<ContentProviderOperation> getDeleteOps() { 127 List<ContentProviderOperation> ops = new ArrayList<>(); 128 129 try (Cursor c = 130 mContentResolver.query( 131 TvContractCompat.RecordedPrograms.CONTENT_URI, 132 PROJECTION, 133 null, 134 null, 135 null)) { 136 if (c == null) { 137 return null; 138 } 139 while (c.moveToNext()) { 140 @StorageStatus int storageStatus = getDvrStorageStatus(); 141 if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) { 142 ops.clear(); 143 break; 144 } 145 String id = c.getString(0); 146 String packageName = c.getString(1); 147 String dataUriString = c.getString(2); 148 if (dataUriString == null) { 149 continue; 150 } 151 Uri dataUri = Uri.parse(dataUriString); 152 if (!CommonUtils.isInBundledPackageSet(packageName) 153 || dataUri == null 154 || dataUri.getPath() == null 155 || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { 156 continue; 157 } 158 File recordedProgramDir = new File(dataUri.getPath()); 159 if (!recordedProgramDir.exists()) { 160 ops.add( 161 ContentProviderOperation.newDelete( 162 TvContractCompat.buildRecordedProgramUri( 163 Long.parseLong(id))) 164 .build()); 165 } 166 } 167 return ops; 168 } 169 } 170 } 171 } 172