1 /* 2 * Copyright (C) 2022 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 17 package com.android.providers.telephony; 18 19 import static java.util.Arrays.stream; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.os.Bundle; 26 import android.os.FileUtils; 27 import android.provider.Telephony; 28 import android.util.Log; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.util.Arrays; 33 import java.util.HashSet; 34 import java.util.Set; 35 import java.util.stream.Collectors; 36 37 /** Tools to find and delete unreferenced MMS parts from the parts directory. */ 38 public class MmsPartsCleanup { 39 private static final String TAG = "MmsPartsCleanup"; 40 41 static final String PART_FILE_COUNT = "part_file_count"; 42 static final String PART_TABLE_ENTRY_COUNT = "part_table_entry_count"; 43 static final String DELETED_COUNT = "deleted_count"; 44 45 /** 46 * @param context 47 * @param doDelete if true, delete the unreferenced MMS part files found in the data dir. 48 * if false, compute the number of unreferenced part files and return the 49 * results, but don't actually delete the files 50 * @param bundle an existing bundle where int values will be added: 51 * PART_FILE_COUNT - an int count of MMS attachment files in the parts 52 * directory 53 * PART_TABLE_ENTRY_COUNT - an int count of MMS attachments referenced 54 * by existing MMS messages 55 * DELETED_COUNT - the number of non-referenced MMS part files delete (or would 56 * be deleted if doDelete is true) 57 */ cleanupDanglingParts(Context context, boolean doDelete, Bundle bundle)58 public static void cleanupDanglingParts(Context context, boolean doDelete, Bundle bundle) { 59 Set<String> danglingFilePathsToDelete = getDanglingMmsParts(context, bundle); 60 danglingFilePathsToDelete.forEach(path -> { 61 File partFile = new File(path); 62 if (partFile.exists()) { 63 if (doDelete) { 64 Log.d(TAG, "Deleting dangling MMS part: " + partFile.getAbsolutePath()); 65 partFile.delete(); 66 } else { 67 Log.d(TAG, 68 "Would have deleted dangling MMS part: " 69 + partFile.getAbsolutePath()); 70 } 71 } else { 72 Log.wtf(TAG, 73 "Part file path does not exist: " 74 + partFile.getAbsolutePath()); 75 } 76 }); 77 bundle.putInt(DELETED_COUNT, danglingFilePathsToDelete.size()); 78 } 79 80 /** 81 * @param context 82 * @return a set of file path names for every MMS attachment file found in the data directory 83 */ 84 @NonNull getAllMmsPartsInPartsDir(Context context)85 private static Set<String> getAllMmsPartsInPartsDir(Context context) { 86 Set<String> allMmsAttachments = new HashSet<>(); 87 try { 88 String partsDirPath = context.getDir(MmsProvider.PARTS_DIR_NAME, 0) 89 .getCanonicalPath(); 90 Log.d(TAG, "getDanglingMmsParts: " + partsDirPath); 91 File partsDir = new File(partsDirPath); 92 allMmsAttachments = Arrays.stream(FileUtils.listFilesOrEmpty(partsDir)).map(p -> 93 { 94 try { 95 return p.getCanonicalPath(); 96 } catch (IOException e) { 97 Log.d(TAG, "getDanglingMmsParts: couldn't get path for " + p); 98 } 99 return null; 100 }).filter(path -> path != null).collect(Collectors.toSet()); 101 } catch (IOException e) { 102 Log.e(TAG, "getDanglingMmsParts: failed " + e, e); 103 allMmsAttachments.clear(); 104 } 105 return allMmsAttachments; 106 } 107 108 /** 109 * @param partFilePaths the incoming set contains the file paths of all MMS attachments in the 110 * data directory. This function modifies this set by removing any path 111 * that is referenced from an MMS part in the parts table. In other words, 112 * when this function returns, partFilePaths contains only the unreferenced 113 * file paths 114 * @param context 115 * @return the number of MMS parts that are referenced and still attached to an MMS message 116 */ 117 @NonNull removeReferencedPartsFromSet(Set<String> partFilePaths, Context context)118 private static int removeReferencedPartsFromSet(Set<String> partFilePaths, Context context) { 119 if (partFilePaths.isEmpty()) { 120 return 0; 121 } 122 SQLiteDatabase db = MmsSmsDatabaseHelper.getInstanceForCe(context).getReadableDatabase(); 123 int referencedPartCount = 0; 124 try (Cursor partRows = 125 db.query( 126 MmsProvider.TABLE_PART, 127 new String[] {Telephony.Mms.Part._DATA}, 128 Telephony.Mms.Part._DATA + " IS NOT NULL AND " 129 + Telephony.Mms.Part._DATA + " <> ''", 130 null, 131 null, 132 null, 133 null)) { 134 if (partRows != null) { 135 while (partRows.moveToNext()) { 136 String path = partRows.getString(0); 137 if (path != null) { 138 if (partFilePaths.remove(path)) { 139 ++referencedPartCount; 140 } 141 } 142 } 143 } 144 } 145 return referencedPartCount; 146 } 147 148 /** 149 * @param context 150 * @param bundle an existing bundle that is modified to contain two possible key/values: 151 * PART_FILE_COUNT - an int count of MMS attachment files in the parts directory 152 * PART_TABLE_ENTRY_COUNT - an int count of MMS attachments referenced by existing 153 * MMS messages 154 * @return a set of file path names for MMS attachments that live in the data directory, but 155 * aren't referenced by any existing MMS message 156 */ 157 @NonNull getDanglingMmsParts(Context context, Bundle bundle)158 private static Set<String> getDanglingMmsParts(Context context, Bundle bundle) { 159 Set<String> allExistingMmsAttachments = getAllMmsPartsInPartsDir(context); 160 bundle.putInt(PART_FILE_COUNT, allExistingMmsAttachments.size()); 161 bundle.putInt(PART_TABLE_ENTRY_COUNT, 162 removeReferencedPartsFromSet(allExistingMmsAttachments, context)); 163 164 return allExistingMmsAttachments; 165 } 166 } 167