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.annotation.Nullable; 28 import android.util.Log; 29 import androidx.tvprovider.media.tv.TvContractCompat; 30 import com.android.tv.TvSingletons; 31 import com.android.tv.common.recording.RecordingStorageStatusManager; 32 import com.android.tv.common.util.CommonUtils; 33 import com.android.tv.util.TvInputManagerHelper; 34 import java.io.File; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */ 39 public class DvrStorageStatusManager extends RecordingStorageStatusManager { 40 private static final String TAG = "DvrStorageStatusManager"; 41 42 private final Context mContext; 43 private CleanUpDbTask mCleanUpDbTask; 44 45 private static final String[] PROJECTION = { 46 TvContractCompat.RecordedPrograms._ID, 47 TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME, 48 TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI 49 }; 50 private static final int BATCH_OPERATION_COUNT = 100; 51 DvrStorageStatusManager(Context context)52 public DvrStorageStatusManager(Context context) { 53 super(context); 54 mContext = context; 55 } 56 57 @Override cleanUpDbIfNeeded()58 protected void cleanUpDbIfNeeded() { 59 if (mCleanUpDbTask != null) { 60 mCleanUpDbTask.cancel(true); 61 } 62 mCleanUpDbTask = new CleanUpDbTask(); 63 mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 64 } 65 66 private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> { 67 private final ContentResolver mContentResolver; 68 CleanUpDbTask()69 private CleanUpDbTask() { 70 mContentResolver = mContext.getContentResolver(); 71 } 72 73 @Override doInBackground(Void... params)74 protected Boolean doInBackground(Void... params) { 75 @StorageStatus int storageStatus = getDvrStorageStatus(); 76 if (storageStatus == STORAGE_STATUS_MISSING) { 77 return null; 78 } 79 if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) { 80 return true; 81 } 82 List<ContentProviderOperation> ops = getDeleteOps(); 83 if (ops == null || ops.isEmpty()) { 84 return null; 85 } 86 Log.i( 87 TAG, 88 "New device storage mounted. # of recordings to be forgotten : " + ops.size()); 89 for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) { 90 int toIndex = 91 (i + BATCH_OPERATION_COUNT) > ops.size() 92 ? ops.size() 93 : (i + BATCH_OPERATION_COUNT); 94 ArrayList<ContentProviderOperation> batchOps = 95 new ArrayList<>(ops.subList(i, toIndex)); 96 try { 97 mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps); 98 } catch (RemoteException | OperationApplicationException e) { 99 Log.e(TAG, "Failed to clean up RecordedPrograms.", e); 100 } 101 } 102 return null; 103 } 104 105 @Override onPostExecute(Boolean forgetStorage)106 protected void onPostExecute(Boolean forgetStorage) { 107 if (forgetStorage != null && forgetStorage == true) { 108 DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager(); 109 TvInputManagerHelper tvInputManagerHelper = 110 TvSingletons.getSingletons(mContext).getTvInputManagerHelper(); 111 List<TvInputInfo> tvInputInfoList = 112 tvInputManagerHelper.getTvInputInfos(true, false); 113 if (tvInputInfoList == null || tvInputInfoList.isEmpty()) { 114 return; 115 } 116 for (TvInputInfo info : tvInputInfoList) { 117 if (CommonUtils.isBundledInput(info.getId())) { 118 dvrManager.forgetStorage(info.getId()); 119 } 120 } 121 } 122 if (mCleanUpDbTask == this) { 123 mCleanUpDbTask = null; 124 } 125 } 126 127 128 @Nullable getDeleteOps()129 private List<ContentProviderOperation> getDeleteOps() { 130 List<ContentProviderOperation> ops = new ArrayList<>(); 131 132 try (Cursor c = 133 mContentResolver.query( 134 TvContractCompat.RecordedPrograms.CONTENT_URI, 135 PROJECTION, 136 null, 137 null, 138 null)) { 139 if (c == null) { 140 return null; 141 } 142 while (c.moveToNext()) { 143 @StorageStatus int storageStatus = getDvrStorageStatus(); 144 if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) { 145 ops.clear(); 146 break; 147 } 148 String id = c.getString(0); 149 String packageName = c.getString(1); 150 String dataUriString = c.getString(2); 151 if (dataUriString == null) { 152 continue; 153 } 154 Uri dataUri = Uri.parse(dataUriString); 155 if (!CommonUtils.isInBundledPackageSet(packageName) 156 || dataUri == null 157 || dataUri.getPath() == null 158 || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) { 159 continue; 160 } 161 File recordedProgramDir = new File(dataUri.getPath()); 162 if (!recordedProgramDir.exists()) { 163 ops.add( 164 ContentProviderOperation.newDelete( 165 TvContractCompat.buildRecordedProgramUri( 166 Long.parseLong(id))) 167 .build()); 168 } 169 } 170 return ops; 171 } catch (Exception e) { 172 Log.w(TAG, "Error when getting delete ops at CleanUpDbTask", e); 173 return null; 174 } 175 } 176 } 177 } 178