1 /* 2 * Copyright (C) 2020 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.server.people.data; 18 19 import static com.android.server.people.data.EventStore.CATEGORY_CALL; 20 import static com.android.server.people.data.EventStore.CATEGORY_CLASS_BASED; 21 import static com.android.server.people.data.EventStore.CATEGORY_LOCUS_ID_BASED; 22 import static com.android.server.people.data.EventStore.CATEGORY_SHORTCUT_BASED; 23 import static com.android.server.people.data.EventStore.CATEGORY_SMS; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.UserIdInt; 28 import android.annotation.WorkerThread; 29 import android.content.LocusId; 30 import android.os.FileUtils; 31 import android.text.TextUtils; 32 import android.util.ArrayMap; 33 34 import java.io.File; 35 import java.util.Map; 36 import java.util.concurrent.ScheduledExecutorService; 37 import java.util.function.Consumer; 38 import java.util.function.Predicate; 39 40 /** The data associated with a package. */ 41 public class PackageData { 42 43 @NonNull 44 private final String mPackageName; 45 46 private final @UserIdInt int mUserId; 47 48 @NonNull 49 private final ConversationStore mConversationStore; 50 51 @NonNull 52 private final EventStore mEventStore; 53 54 private final Predicate<String> mIsDefaultDialerPredicate; 55 56 private final Predicate<String> mIsDefaultSmsAppPredicate; 57 58 private final File mPackageDataDir; 59 PackageData(@onNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, @NonNull Predicate<String> isDefaultSmsAppPredicate, @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File perUserPeopleDataDir)60 PackageData(@NonNull String packageName, @UserIdInt int userId, 61 @NonNull Predicate<String> isDefaultDialerPredicate, 62 @NonNull Predicate<String> isDefaultSmsAppPredicate, 63 @NonNull ScheduledExecutorService scheduledExecutorService, 64 @NonNull File perUserPeopleDataDir) { 65 mPackageName = packageName; 66 mUserId = userId; 67 68 mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); 69 mPackageDataDir.mkdirs(); 70 71 mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService); 72 mEventStore = new EventStore(mPackageDataDir, scheduledExecutorService); 73 mIsDefaultDialerPredicate = isDefaultDialerPredicate; 74 mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; 75 } 76 77 /** 78 * Returns a map of package directory names as keys and their associated {@link PackageData}. 79 * This should be called when device is powered on and unlocked. 80 */ 81 @WorkerThread 82 @NonNull packagesDataFromDisk(@serIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, @NonNull Predicate<String> isDefaultSmsAppPredicate, @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File perUserPeopleDataDir)83 static Map<String, PackageData> packagesDataFromDisk(@UserIdInt int userId, 84 @NonNull Predicate<String> isDefaultDialerPredicate, 85 @NonNull Predicate<String> isDefaultSmsAppPredicate, 86 @NonNull ScheduledExecutorService scheduledExecutorService, 87 @NonNull File perUserPeopleDataDir) { 88 Map<String, PackageData> results = new ArrayMap<>(); 89 File[] packageDirs = perUserPeopleDataDir.listFiles(File::isDirectory); 90 if (packageDirs == null) { 91 return results; 92 } 93 for (File packageDir : packageDirs) { 94 PackageData packageData = new PackageData(packageDir.getName(), userId, 95 isDefaultDialerPredicate, isDefaultSmsAppPredicate, scheduledExecutorService, 96 perUserPeopleDataDir); 97 packageData.loadFromDisk(); 98 results.put(packageDir.getName(), packageData); 99 } 100 return results; 101 } 102 loadFromDisk()103 private void loadFromDisk() { 104 mConversationStore.loadConversationsFromDisk(); 105 mEventStore.loadFromDisk(); 106 } 107 108 /** Called when device is shutting down. */ saveToDisk()109 void saveToDisk() { 110 mConversationStore.saveConversationsToDisk(); 111 mEventStore.saveToDisk(); 112 } 113 114 @NonNull getPackageName()115 public String getPackageName() { 116 return mPackageName; 117 } 118 getUserId()119 public @UserIdInt int getUserId() { 120 return mUserId; 121 } 122 123 /** Iterates over all the conversations in this package. */ forAllConversations(@onNull Consumer<ConversationInfo> consumer)124 public void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { 125 mConversationStore.forAllConversations(consumer); 126 } 127 128 /** 129 * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link 130 * ConversationInfo} does not exist. 131 */ 132 @Nullable getConversationInfo(@onNull String shortcutId)133 public ConversationInfo getConversationInfo(@NonNull String shortcutId) { 134 return getConversationStore().getConversation(shortcutId); 135 } 136 137 /** 138 * Gets the combined {@link EventHistory} for a given shortcut ID. This returned {@link 139 * EventHistory} has events of all types, no matter whether they're annotated with shortcut ID, 140 * Locus ID, or phone number etc. 141 */ 142 @NonNull getEventHistory(@onNull String shortcutId)143 public EventHistory getEventHistory(@NonNull String shortcutId) { 144 AggregateEventHistoryImpl result = new AggregateEventHistoryImpl(); 145 146 ConversationInfo conversationInfo = mConversationStore.getConversation(shortcutId); 147 if (conversationInfo == null) { 148 return result; 149 } 150 151 EventHistory shortcutEventHistory = getEventStore().getEventHistory( 152 CATEGORY_SHORTCUT_BASED, shortcutId); 153 if (shortcutEventHistory != null) { 154 result.addEventHistory(shortcutEventHistory); 155 } 156 157 LocusId locusId = conversationInfo.getLocusId(); 158 if (locusId != null) { 159 EventHistory locusEventHistory = getEventStore().getEventHistory( 160 CATEGORY_LOCUS_ID_BASED, locusId.getId()); 161 if (locusEventHistory != null) { 162 result.addEventHistory(locusEventHistory); 163 } 164 } 165 166 String phoneNumber = conversationInfo.getContactPhoneNumber(); 167 if (TextUtils.isEmpty(phoneNumber)) { 168 return result; 169 } 170 if (isDefaultDialer()) { 171 EventHistory callEventHistory = getEventStore().getEventHistory( 172 CATEGORY_CALL, phoneNumber); 173 if (callEventHistory != null) { 174 result.addEventHistory(callEventHistory); 175 } 176 } 177 if (isDefaultSmsApp()) { 178 EventHistory smsEventHistory = getEventStore().getEventHistory( 179 CATEGORY_SMS, phoneNumber); 180 if (smsEventHistory != null) { 181 result.addEventHistory(smsEventHistory); 182 } 183 } 184 return result; 185 } 186 187 /** Gets the {@link EventHistory} for a given Activity class. */ 188 @NonNull getClassLevelEventHistory(String className)189 public EventHistory getClassLevelEventHistory(String className) { 190 EventHistory eventHistory = getEventStore().getEventHistory( 191 CATEGORY_CLASS_BASED, className); 192 return eventHistory != null ? eventHistory : new AggregateEventHistoryImpl(); 193 } 194 isDefaultDialer()195 public boolean isDefaultDialer() { 196 return mIsDefaultDialerPredicate.test(mPackageName); 197 } 198 isDefaultSmsApp()199 public boolean isDefaultSmsApp() { 200 return mIsDefaultSmsAppPredicate.test(mPackageName); 201 } 202 203 @NonNull getConversationStore()204 ConversationStore getConversationStore() { 205 return mConversationStore; 206 } 207 208 @NonNull getEventStore()209 EventStore getEventStore() { 210 return mEventStore; 211 } 212 213 /** 214 * Deletes all the data (including conversation, events and index) for the specified 215 * conversation shortcut ID. 216 */ deleteDataForConversation(String shortcutId)217 void deleteDataForConversation(String shortcutId) { 218 ConversationInfo conversationInfo = mConversationStore.deleteConversation(shortcutId); 219 if (conversationInfo == null) { 220 return; 221 } 222 mEventStore.deleteEventHistory(CATEGORY_SHORTCUT_BASED, shortcutId); 223 if (conversationInfo.getLocusId() != null) { 224 mEventStore.deleteEventHistory( 225 CATEGORY_LOCUS_ID_BASED, conversationInfo.getLocusId().getId()); 226 } 227 String phoneNumber = conversationInfo.getContactPhoneNumber(); 228 if (!TextUtils.isEmpty(phoneNumber)) { 229 if (isDefaultDialer()) { 230 mEventStore.deleteEventHistory(CATEGORY_CALL, phoneNumber); 231 } 232 if (isDefaultSmsApp()) { 233 mEventStore.deleteEventHistory(CATEGORY_SMS, phoneNumber); 234 } 235 } 236 } 237 238 /** Prunes the events and index data that don't have a associated conversation. */ pruneOrphanEvents()239 void pruneOrphanEvents() { 240 mEventStore.pruneOrphanEventHistories(CATEGORY_SHORTCUT_BASED, 241 key -> mConversationStore.getConversation(key) != null); 242 mEventStore.pruneOrphanEventHistories(CATEGORY_LOCUS_ID_BASED, 243 key -> mConversationStore.getConversationByLocusId(new LocusId(key)) != null); 244 if (isDefaultDialer()) { 245 mEventStore.pruneOrphanEventHistories(CATEGORY_CALL, 246 key -> mConversationStore.getConversationByPhoneNumber(key) != null); 247 } 248 if (isDefaultSmsApp()) { 249 mEventStore.pruneOrphanEventHistories(CATEGORY_SMS, 250 key -> mConversationStore.getConversationByPhoneNumber(key) != null); 251 } 252 } 253 onDestroy()254 void onDestroy() { 255 mEventStore.onDestroy(); 256 mConversationStore.onDestroy(); 257 FileUtils.deleteContentsAndDir(mPackageDataDir); 258 } 259 } 260