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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.WorkerThread; 23 import android.net.Uri; 24 import android.util.ArrayMap; 25 26 import com.android.internal.annotations.GuardedBy; 27 28 import java.io.File; 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.concurrent.ScheduledExecutorService; 36 import java.util.function.Predicate; 37 38 /** The store that stores and accesses the events data for a package. */ 39 class EventStore { 40 41 /** The events that are queryable with a shortcut ID. */ 42 static final int CATEGORY_SHORTCUT_BASED = 0; 43 44 /** The events that are queryable with a {@link android.content.LocusId}. */ 45 static final int CATEGORY_LOCUS_ID_BASED = 1; 46 47 /** The phone call events that are queryable with a phone number. */ 48 static final int CATEGORY_CALL = 2; 49 50 /** The SMS or MMS events that are queryable with a phone number. */ 51 static final int CATEGORY_SMS = 3; 52 53 /** The events that are queryable with an {@link android.app.Activity} class name. */ 54 static final int CATEGORY_CLASS_BASED = 4; 55 56 @IntDef(prefix = { "CATEGORY_" }, value = { 57 CATEGORY_SHORTCUT_BASED, 58 CATEGORY_LOCUS_ID_BASED, 59 CATEGORY_CALL, 60 CATEGORY_SMS, 61 CATEGORY_CLASS_BASED, 62 }) 63 @Retention(RetentionPolicy.SOURCE) 64 @interface EventCategory {} 65 66 @GuardedBy("this") 67 private final List<Map<String, EventHistoryImpl>> mEventHistoryMaps = new ArrayList<>(); 68 private final List<File> mEventsCategoryDirs = new ArrayList<>(); 69 private final ScheduledExecutorService mScheduledExecutorService; 70 EventStore(@onNull File packageDir, @NonNull ScheduledExecutorService scheduledExecutorService)71 EventStore(@NonNull File packageDir, 72 @NonNull ScheduledExecutorService scheduledExecutorService) { 73 mEventHistoryMaps.add(CATEGORY_SHORTCUT_BASED, new ArrayMap<>()); 74 mEventHistoryMaps.add(CATEGORY_LOCUS_ID_BASED, new ArrayMap<>()); 75 mEventHistoryMaps.add(CATEGORY_CALL, new ArrayMap<>()); 76 mEventHistoryMaps.add(CATEGORY_SMS, new ArrayMap<>()); 77 mEventHistoryMaps.add(CATEGORY_CLASS_BASED, new ArrayMap<>()); 78 79 File eventDir = new File(packageDir, "event"); 80 mEventsCategoryDirs.add(CATEGORY_SHORTCUT_BASED, new File(eventDir, "shortcut")); 81 mEventsCategoryDirs.add(CATEGORY_LOCUS_ID_BASED, new File(eventDir, "locus")); 82 mEventsCategoryDirs.add(CATEGORY_CALL, new File(eventDir, "call")); 83 mEventsCategoryDirs.add(CATEGORY_SMS, new File(eventDir, "sms")); 84 mEventsCategoryDirs.add(CATEGORY_CLASS_BASED, new File(eventDir, "class")); 85 86 mScheduledExecutorService = scheduledExecutorService; 87 } 88 89 /** 90 * Loads existing {@link EventHistoryImpl}s from disk. This should be called when device powers 91 * on and user is unlocked. 92 */ 93 @WorkerThread loadFromDisk()94 synchronized void loadFromDisk() { 95 for (@EventCategory int category = 0; category < mEventsCategoryDirs.size(); 96 category++) { 97 File categoryDir = mEventsCategoryDirs.get(category); 98 Map<String, EventHistoryImpl> existingEventHistoriesImpl = 99 EventHistoryImpl.eventHistoriesImplFromDisk(categoryDir, 100 mScheduledExecutorService); 101 mEventHistoryMaps.get(category).putAll(existingEventHistoriesImpl); 102 } 103 } 104 105 /** 106 * Flushes all {@link EventHistoryImpl}s to disk. Should be called when device is shutting down. 107 */ saveToDisk()108 synchronized void saveToDisk() { 109 for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) { 110 for (EventHistoryImpl eventHistory : map.values()) { 111 eventHistory.saveToDisk(); 112 } 113 } 114 } 115 116 /** 117 * Gets the {@link EventHistory} for the specified key if exists. 118 * 119 * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class 120 * name. 121 */ 122 @Nullable getEventHistory(@ventCategory int category, String key)123 synchronized EventHistory getEventHistory(@EventCategory int category, String key) { 124 return mEventHistoryMaps.get(category).get(key); 125 } 126 127 /** 128 * Gets the {@link EventHistoryImpl} for the specified ID or creates a new instance and put it 129 * into the store if not exists. The caller needs to verify if the associated conversation 130 * exists before calling this method. 131 * 132 * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class 133 * name. 134 */ 135 @NonNull getOrCreateEventHistory(@ventCategory int category, String key)136 synchronized EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) { 137 return mEventHistoryMaps.get(category).computeIfAbsent(key, 138 k -> new EventHistoryImpl( 139 new File(mEventsCategoryDirs.get(category), Uri.encode(key)), 140 mScheduledExecutorService)); 141 } 142 143 /** 144 * Deletes the events and index data for the specified key. 145 * 146 * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class 147 * name. 148 */ deleteEventHistory(@ventCategory int category, String key)149 synchronized void deleteEventHistory(@EventCategory int category, String key) { 150 EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key); 151 if (eventHistory != null) { 152 eventHistory.onDestroy(); 153 } 154 } 155 156 /** Deletes all the events and index data for the specified category from disk. */ deleteEventHistories(@ventCategory int category)157 synchronized void deleteEventHistories(@EventCategory int category) { 158 for (EventHistoryImpl eventHistory : mEventHistoryMaps.get(category).values()) { 159 eventHistory.onDestroy(); 160 } 161 mEventHistoryMaps.get(category).clear(); 162 } 163 164 /** Deletes the events data that exceeds the retention period. */ pruneOldEvents()165 synchronized void pruneOldEvents() { 166 for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) { 167 for (EventHistoryImpl eventHistory : map.values()) { 168 eventHistory.pruneOldEvents(); 169 } 170 } 171 } 172 173 /** 174 * Prunes the event histories whose key (shortcut ID, locus ID or phone number) does not match 175 * any conversations. 176 * 177 * @param keyChecker Check whether there exists a conversation contains this key. 178 */ pruneOrphanEventHistories(@ventCategory int category, Predicate<String> keyChecker)179 synchronized void pruneOrphanEventHistories(@EventCategory int category, 180 Predicate<String> keyChecker) { 181 Set<String> keys = mEventHistoryMaps.get(category).keySet(); 182 List<String> keysToDelete = new ArrayList<>(); 183 for (String key : keys) { 184 if (!keyChecker.test(key)) { 185 keysToDelete.add(key); 186 } 187 } 188 Map<String, EventHistoryImpl> eventHistoryMap = mEventHistoryMaps.get(category); 189 for (String key : keysToDelete) { 190 EventHistoryImpl eventHistory = eventHistoryMap.remove(key); 191 if (eventHistory != null) { 192 eventHistory.onDestroy(); 193 } 194 } 195 } 196 onDestroy()197 synchronized void onDestroy() { 198 for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) { 199 for (EventHistoryImpl eventHistory : map.values()) { 200 eventHistory.onDestroy(); 201 } 202 } 203 } 204 } 205