diff options
| author | 2020-02-07 17:49:42 -0800 | |
|---|---|---|
| committer | 2020-02-13 12:45:42 -0800 | |
| commit | c4933eb5b7504c47fe80e6ef0de708694b325812 (patch) | |
| tree | 9600dbeaa7775f223d7f1656ff0360ef9b5743c7 | |
| parent | b82b55f30deb93bf83e0d8688762acc4e00bda78 (diff) | |
Add a JobScheduler service to prune the People Service data on a daily basis
- Refactor the class EventStore to simplify the event history access methods
- Add the prune methods to delete the data for:
- uninstalled packages
- event data that exceeds the retention period
- call events data for non-dialer packages
- SMS events data for non-SMS-app packages
- orphan events (the annotated locus ID or phone number have changed in the associated shortcut)
- Change the package-level events to class-level for the app share events
Bug: 146522621
Test: Manually verify the behavior on device \
atest com.android.server.people.data.DataManagerTest \
atest com.android.server.people.data.PackageDataTest
Change-Id: I3f17ed269fe5cb05f1d3696e44dfb0bed951678f
15 files changed, 592 insertions, 110 deletions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 60b3a19c4cf3..9dc40c80f18b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5344,6 +5344,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.people.data.DataMaintenanceService" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.autofill.AutofillCompatAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java index c5b868fbe99d..31fa4d192278 100644 --- a/services/core/java/com/android/server/people/PeopleServiceInternal.java +++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java @@ -17,6 +17,8 @@ package com.android.server.people; import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.CancellationSignal; import android.service.appprediction.IPredictionService; /** @@ -25,16 +27,23 @@ import android.service.appprediction.IPredictionService; public abstract class PeopleServiceInternal extends IPredictionService.Stub { /** + * Prunes the data for the specified user. Called by {@link + * com.android.server.people.data.DataMaintenanceService} when the device is idle. + */ + public abstract void pruneDataForUser(@UserIdInt int userId, + @NonNull CancellationSignal signal); + + /** * The number conversation infos will be dynamic, based on the currently installed apps on the * device. All of which should be combined into a single blob to be backed up. */ - public abstract byte[] backupConversationInfos(@NonNull int userId); + public abstract byte[] backupConversationInfos(@UserIdInt int userId); /** * Multiple conversation infos may exist in the restore payload, child classes are required to * manage the restoration based on how individual conversation infos were originally combined * during backup. */ - public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key, + public abstract void restoreConversationInfos(@UserIdInt int userId, @NonNull String key, @NonNull byte[] payload); } diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 2d18a2994135..663bf4f30708 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -17,6 +17,7 @@ package com.android.server.people; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTarget; @@ -24,6 +25,7 @@ import android.app.prediction.AppTargetEvent; import android.app.prediction.IPredictionCallback; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.os.CancellationSignal; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; @@ -138,6 +140,21 @@ public class PeopleService extends SystemService { }); } + @Override + public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) { + mDataManager.pruneDataForUser(userId, signal); + } + + @Override + public byte[] backupConversationInfos(@UserIdInt int userId) { + return new byte[0]; + } + + @Override + public void restoreConversationInfos(@UserIdInt int userId, @NonNull String key, + @NonNull byte[] payload) { + } + @VisibleForTesting SessionInfo getSessionInfo(AppPredictionSessionId sessionId) { return mSessions.get(sessionId); @@ -160,14 +177,5 @@ public class PeopleService extends SystemService { Slog.e(TAG, "Failed to calling callback" + e); } } - - @Override - public byte[] backupConversationInfos(int userId) { - return new byte[0]; - } - - @Override - public void restoreConversationInfos(int userId, String key, byte[] payload) { - } } } diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index ea36d38e5d4a..3afb209ae5cd 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -133,10 +133,11 @@ class ConversationStore { } @MainThread - synchronized void deleteConversation(@NonNull String shortcutId) { + @Nullable + synchronized ConversationInfo deleteConversation(@NonNull String shortcutId) { ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); if (conversationInfo == null) { - return; + return null; } LocusId locusId = conversationInfo.getLocusId(); @@ -159,6 +160,7 @@ class ConversationStore { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } scheduleUpdateConversationsOnDisk(); + return conversationInfo; } synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { diff --git a/services/people/java/com/android/server/people/data/DataMaintenanceService.java b/services/people/java/com/android/server/people/data/DataMaintenanceService.java new file mode 100644 index 000000000000..58f0654c7b51 --- /dev/null +++ b/services/people/java/com/android/server/people/data/DataMaintenanceService.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.data; + +import android.annotation.UserIdInt; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.CancellationSignal; + +import com.android.server.LocalServices; +import com.android.server.people.PeopleServiceInternal; + +import java.util.concurrent.TimeUnit; + +/** + * This service runs periodically to ensure the data consistency and the retention policy for a + * specific user's data. + */ +public class DataMaintenanceService extends JobService { + + private static final long JOB_RUN_INTERVAL = TimeUnit.HOURS.toMillis(24); + + /** This job ID must be unique within the system server. */ + private static final int BASE_JOB_ID = 0xC315BD7; // 204561367 + + static void scheduleJob(Context context, @UserIdInt int userId) { + int jobId = getJobId(userId); + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + if (jobScheduler.getPendingJob(jobId) == null) { + ComponentName component = new ComponentName(context, DataMaintenanceService.class); + JobInfo newJob = new JobInfo.Builder(jobId, component) + .setRequiresDeviceIdle(true) + .setPeriodic(JOB_RUN_INTERVAL) + .build(); + jobScheduler.schedule(newJob); + } + } + + static void cancelJob(Context context, @UserIdInt int userId) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.cancel(getJobId(userId)); + } + + private CancellationSignal mSignal; + + @Override + public boolean onStartJob(JobParameters params) { + int userId = getUserId(params.getJobId()); + mSignal = new CancellationSignal(); + new Thread(() -> { + PeopleServiceInternal peopleServiceInternal = + LocalServices.getService(PeopleServiceInternal.class); + peopleServiceInternal.pruneDataForUser(userId, mSignal); + jobFinished(params, mSignal.isCanceled()); + }).start(); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + if (mSignal != null) { + mSignal.cancel(); + } + return false; + } + + private static int getJobId(@UserIdInt int userId) { + return BASE_JOB_ID + userId; + } + + private static @UserIdInt int getUserId(int jobId) { + return jobId - BASE_JOB_ID; + } +} diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index a904b42f47db..2edfd7487ec0 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutManager.ShareShortcutInfo; @@ -40,6 +41,7 @@ import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.CancellationSignal; import android.os.Handler; import android.os.Process; import android.os.RemoteException; @@ -53,16 +55,20 @@ import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.text.TextUtils; import android.text.format.DateUtils; +import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ChooserActivity; +import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.telephony.SmsApplication; import com.android.server.LocalServices; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -94,10 +100,12 @@ public class DataManager { private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>(); private final SparseArray<NotificationListenerService> mNotificationListeners = new SparseArray<>(); + private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>(); private final ContentObserver mCallLogContentObserver; private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; + private PackageManagerInternal mPackageManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; @@ -120,6 +128,7 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); @@ -172,6 +181,10 @@ public class DataManager { // Should never occur for local calls. } + PackageMonitor packageMonitor = new PerUserPackageMonitor(); + packageMonitor.register(mContext, null, UserHandle.of(userId), true); + mPackageMonitors.put(userId, packageMonitor); + if (userId == UserHandle.USER_SYSTEM) { // The call log and MMS/SMS messages are shared across user profiles. So only need to // register the content observers once for the primary user. @@ -183,6 +196,8 @@ public class DataManager { MmsSms.CONTENT_URI, /* notifyForDescendants= */ false, mMmsSmsContentObserver, UserHandle.USER_SYSTEM); } + + DataMaintenanceService.scheduleJob(mContext, userId); } /** This method is called when a user is stopped. */ @@ -207,10 +222,15 @@ public class DataManager { // Should never occur for local calls. } } + if (mPackageMonitors.indexOfKey(userId) >= 0) { + mPackageMonitors.get(userId).unregister(); + } if (userId == UserHandle.USER_SYSTEM) { mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver); mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver); } + + DataMaintenanceService.cancelJob(mContext, userId); } /** @@ -274,9 +294,8 @@ public class DataManager { || TextUtils.isEmpty(mimeType)) { return; } - EventHistoryImpl eventHistory = - packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutInfo.getId()); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId()); @Event.EventType int eventType; if (mimeType.startsWith("text/")) { eventType = Event.TYPE_SHARE_TEXT; @@ -291,6 +310,45 @@ public class DataManager { } } + /** Prunes the data for the specified user. */ + public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) { + UserData userData = getUnlockedUserData(userId); + if (userData == null || signal.isCanceled()) { + return; + } + pruneUninstalledPackageData(userData); + + long currentTimeMillis = System.currentTimeMillis(); + userData.forAllPackages(packageData -> { + if (signal.isCanceled()) { + return; + } + packageData.getEventStore().pruneOldEvents(currentTimeMillis); + if (!packageData.isDefaultDialer()) { + packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL); + } + if (!packageData.isDefaultSmsApp()) { + packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS); + } + packageData.pruneOrphanEvents(); + }); + } + + private void pruneUninstalledPackageData(@NonNull UserData userData) { + Set<String> installApps = new ArraySet<>(); + mPackageManagerInternal.forEachInstalledPackage( + pkg -> installApps.add(pkg.getPackageName()), userData.getUserId()); + List<String> packagesToDelete = new ArrayList<>(); + userData.forAllPackages(packageData -> { + if (!installApps.contains(packageData.getPackageName())) { + packagesToDelete.add(packageData.getPackageName()); + } + }); + for (String packageName : packagesToDelete) { + userData.deletePackageData(packageName); + } + } + /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */ private List<ShortcutInfo> getShortcuts( @NonNull String packageName, @UserIdInt int userId, @@ -347,7 +405,8 @@ public class DataManager { || packageData.getConversationStore().getConversation(shortcutId) == null) { return null; } - return packageData.getEventStore().getOrCreateShortcutEventHistory(shortcutId); + return packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); } @VisibleForTesting @@ -413,6 +472,11 @@ public class DataManager { } @VisibleForTesting + PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) { + return mPackageMonitors.get(userId); + } + + @VisibleForTesting UserData getUserDataForTesting(@UserIdInt int userId) { return mUserDataArray.get(userId); } @@ -499,7 +563,8 @@ public class DataManager { return; } EventStore eventStore = defaultDialer.getEventStore(); - eventStore.getOrCreateCallEventHistory(phoneNumber).addEvent(event); + eventStore.getOrCreateEventHistory( + EventStore.CATEGORY_CALL, phoneNumber).addEvent(event); }); } } @@ -544,7 +609,8 @@ public class DataManager { return; } EventStore eventStore = defaultSmsApp.getEventStore(); - eventStore.getOrCreateSmsEventHistory(phoneNumber).addEvent(event); + eventStore.getOrCreateEventHistory( + EventStore.CATEGORY_SMS, phoneNumber).addEvent(event); }); } } @@ -670,6 +736,20 @@ public class DataManager { } } + private class PerUserPackageMonitor extends PackageMonitor { + + @Override + public void onPackageRemoved(String packageName, int uid) { + super.onPackageRemoved(packageName, uid); + + int userId = getChangingUserId(); + UserData userData = getUnlockedUserData(userId); + if (userData != null) { + userData.deletePackageData(packageName); + } + } + } + private class ShutdownBroadcastReceiver extends BroadcastReceiver { @Override diff --git a/services/people/java/com/android/server/people/data/EventHistoryImpl.java b/services/people/java/com/android/server/people/data/EventHistoryImpl.java index 6b6bd7e3cfb0..6bef1db5582e 100644 --- a/services/people/java/com/android/server/people/data/EventHistoryImpl.java +++ b/services/people/java/com/android/server/people/data/EventHistoryImpl.java @@ -78,6 +78,17 @@ class EventHistoryImpl implements EventHistory { mRecentEvents.add(event); } + void onDestroy() { + mEventIndexArray.clear(); + mRecentEvents.clear(); + // TODO: STOPSHIP: Delete the data files. + } + + /** Deletes the events data that exceeds the retention period. */ + void pruneOldEvents(long currentTimeMillis) { + // TODO: STOPSHIP: Delete the old events data files. + } + @VisibleForTesting static class Injector { diff --git a/services/people/java/com/android/server/people/data/EventList.java b/services/people/java/com/android/server/people/data/EventList.java index b267d667b422..d770f912ea40 100644 --- a/services/people/java/com/android/server/people/data/EventList.java +++ b/services/people/java/com/android/server/people/data/EventList.java @@ -69,6 +69,10 @@ class EventList { return result; } + void clear() { + mEvents.clear(); + } + /** Returns the first index whose timestamp is greater or equal to the provided timestamp. */ private int firstIndexOnOrAfter(long timestamp) { int result = mEvents.size(); diff --git a/services/people/java/com/android/server/people/data/EventStore.java b/services/people/java/com/android/server/people/data/EventStore.java index d6b7a863ca2d..c8d44ac07106 100644 --- a/services/people/java/com/android/server/people/data/EventStore.java +++ b/services/people/java/com/android/server/people/data/EventStore.java @@ -16,99 +16,129 @@ package com.android.server.people.data; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.LocusId; import android.util.ArrayMap; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; /** The store that stores and accesses the events data for a package. */ class EventStore { - private final EventHistoryImpl mPackageEventHistory = new EventHistoryImpl(); + /** The events that are queryable with a shortcut ID. */ + static final int CATEGORY_SHORTCUT_BASED = 0; - // Shortcut ID -> Event History - private final Map<String, EventHistoryImpl> mShortcutEventHistoryMap = new ArrayMap<>(); + /** The events that are queryable with a {@link android.content.LocusId}. */ + static final int CATEGORY_LOCUS_ID_BASED = 1; - // Locus ID -> Event History - private final Map<LocusId, EventHistoryImpl> mLocusEventHistoryMap = new ArrayMap<>(); + /** The phone call events that are queryable with a phone number. */ + static final int CATEGORY_CALL = 2; - // Phone Number -> Event History - private final Map<String, EventHistoryImpl> mCallEventHistoryMap = new ArrayMap<>(); + /** The SMS or MMS events that are queryable with a phone number. */ + static final int CATEGORY_SMS = 3; - // Phone Number -> Event History - private final Map<String, EventHistoryImpl> mSmsEventHistoryMap = new ArrayMap<>(); + /** The events that are queryable with an {@link android.app.Activity} class name. */ + static final int CATEGORY_CLASS_BASED = 4; - /** Gets the package level {@link EventHistory}. */ - @NonNull - EventHistory getPackageEventHistory() { - return mPackageEventHistory; - } + @IntDef(prefix = { "CATEGORY_" }, value = { + CATEGORY_SHORTCUT_BASED, + CATEGORY_LOCUS_ID_BASED, + CATEGORY_CALL, + CATEGORY_SMS, + CATEGORY_CLASS_BASED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface EventCategory {} - /** Gets the {@link EventHistory} for the specified {@code shortcutId} if exists. */ - @Nullable - EventHistory getShortcutEventHistory(String shortcutId) { - return mShortcutEventHistoryMap.get(shortcutId); - } + private final List<Map<String, EventHistoryImpl>> mEventHistoryMaps = new ArrayList<>(); - /** Gets the {@link EventHistory} for the specified {@code locusId} if exists. */ - @Nullable - EventHistory getLocusEventHistory(LocusId locusId) { - return mLocusEventHistoryMap.get(locusId); + EventStore() { + mEventHistoryMaps.add(CATEGORY_SHORTCUT_BASED, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_LOCUS_ID_BASED, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_CALL, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_SMS, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_CLASS_BASED, new ArrayMap<>()); } - /** Gets the phone call {@link EventHistory} for the specified {@code phoneNumber} if exists. */ - @Nullable - EventHistory getCallEventHistory(String phoneNumber) { - return mCallEventHistoryMap.get(phoneNumber); - } - - /** Gets the SMS {@link EventHistory} for the specified {@code phoneNumber} if exists. */ + /** + * Gets the {@link EventHistory} for the specified key if exists. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. + */ @Nullable - EventHistory getSmsEventHistory(String phoneNumber) { - return mSmsEventHistoryMap.get(phoneNumber); + EventHistory getEventHistory(@EventCategory int category, String key) { + return mEventHistoryMaps.get(category).get(key); } /** - * Gets the {@link EventHistoryImpl} for the specified {@code shortcutId} or creates a new - * instance and put it into the store if not exists. The caller needs to verify if a - * conversation with this shortcut ID exists before calling this method. + * Gets the {@link EventHistoryImpl} for the specified ID or creates a new instance and put it + * into the store if not exists. The caller needs to verify if the associated conversation + * exists before calling this method. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. */ @NonNull - EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { - return mShortcutEventHistoryMap.computeIfAbsent(shortcutId, key -> new EventHistoryImpl()); + EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) { + return mEventHistoryMaps.get(category).computeIfAbsent(key, k -> new EventHistoryImpl()); } /** - * Gets the {@link EventHistoryImpl} for the specified {@code locusId} or creates a new - * instance and put it into the store if not exists. The caller needs to ensure a conversation - * with this locus ID exists before calling this method. + * Deletes the events and index data for the specified key. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. */ - @NonNull - EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { - return mLocusEventHistoryMap.computeIfAbsent(locusId, key -> new EventHistoryImpl()); + void deleteEventHistory(@EventCategory int category, String key) { + EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key); + if (eventHistory != null) { + eventHistory.onDestroy(); + } } - /** - * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for call events - * or creates a new instance and put it into the store if not exists. The caller needs to ensure - * a conversation with this phone number exists and this package is the default dialer - * before calling this method. - */ - @NonNull - EventHistoryImpl getOrCreateCallEventHistory(String phoneNumber) { - return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + /** Deletes all the events and index data for the specified category from disk. */ + void deleteEventHistories(@EventCategory int category) { + mEventHistoryMaps.get(category).clear(); + // TODO: Implement this method to delete the data from disk. + } + + /** Deletes the events data that exceeds the retention period. */ + void pruneOldEvents(long currentTimeMillis) { + for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) { + for (EventHistoryImpl eventHistory : map.values()) { + eventHistory.pruneOldEvents(currentTimeMillis); + } + } } /** - * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for SMS events - * or creates a new instance and put it into the store if not exists. The caller needs to ensure - * a conversation with this phone number exists and this package is the default SMS app - * before calling this method. + * Prunes the event histories whose key (shortcut ID, locus ID or phone number) does not match + * any conversations. + * + * @param keyChecker Check whether there exists a conversation contains this key. */ - @NonNull - EventHistoryImpl getOrCreateSmsEventHistory(String phoneNumber) { - return mSmsEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + void pruneOrphanEventHistories(@EventCategory int category, Predicate<String> keyChecker) { + Set<String> keys = mEventHistoryMaps.get(category).keySet(); + List<String> keysToDelete = new ArrayList<>(); + for (String key : keys) { + if (!keyChecker.test(key)) { + keysToDelete.add(key); + } + } + Map<String, EventHistoryImpl> eventHistoryMap = mEventHistoryMaps.get(category); + for (String key : keysToDelete) { + EventHistoryImpl eventHistory = eventHistoryMap.remove(key); + if (eventHistory != null) { + eventHistory.onDestroy(); + } + } } } diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index f67699c28531..c55f97205bc5 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -16,6 +16,12 @@ package com.android.server.people.data; +import static com.android.server.people.data.EventStore.CATEGORY_CALL; +import static com.android.server.people.data.EventStore.CATEGORY_CLASS_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_LOCUS_ID_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_SHORTCUT_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_SMS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -89,11 +95,6 @@ public class PackageData { mConversationStore.forAllConversations(consumer); } - @NonNull - public EventHistory getPackageLevelEventHistory() { - return getEventStore().getPackageEventHistory(); - } - /** * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link * ConversationInfo} does not exist. @@ -117,14 +118,16 @@ public class PackageData { return result; } - EventHistory shortcutEventHistory = getEventStore().getShortcutEventHistory(shortcutId); + EventHistory shortcutEventHistory = getEventStore().getEventHistory( + CATEGORY_SHORTCUT_BASED, shortcutId); if (shortcutEventHistory != null) { result.addEventHistory(shortcutEventHistory); } LocusId locusId = conversationInfo.getLocusId(); if (locusId != null) { - EventHistory locusEventHistory = getEventStore().getLocusEventHistory(locusId); + EventHistory locusEventHistory = getEventStore().getEventHistory( + CATEGORY_LOCUS_ID_BASED, locusId.getId()); if (locusEventHistory != null) { result.addEventHistory(locusEventHistory); } @@ -135,13 +138,15 @@ public class PackageData { return result; } if (isDefaultDialer()) { - EventHistory callEventHistory = getEventStore().getCallEventHistory(phoneNumber); + EventHistory callEventHistory = getEventStore().getEventHistory( + CATEGORY_CALL, phoneNumber); if (callEventHistory != null) { result.addEventHistory(callEventHistory); } } if (isDefaultSmsApp()) { - EventHistory smsEventHistory = getEventStore().getSmsEventHistory(phoneNumber); + EventHistory smsEventHistory = getEventStore().getEventHistory( + CATEGORY_SMS, phoneNumber); if (smsEventHistory != null) { result.addEventHistory(smsEventHistory); } @@ -149,6 +154,14 @@ public class PackageData { return result; } + /** Gets the {@link EventHistory} for a given Activity class. */ + @NonNull + public EventHistory getClassLevelEventHistory(String className) { + EventHistory eventHistory = getEventStore().getEventHistory( + CATEGORY_CLASS_BASED, className); + return eventHistory != null ? eventHistory : new AggregateEventHistoryImpl(); + } + public boolean isDefaultDialer() { return mIsDefaultDialerPredicate.test(mPackageName); } @@ -167,6 +180,47 @@ public class PackageData { return mEventStore; } + /** + * Deletes all the data (including conversation, events and index) for the specified + * conversation shortcut ID. + */ + void deleteDataForConversation(String shortcutId) { + ConversationInfo conversationInfo = mConversationStore.deleteConversation(shortcutId); + if (conversationInfo == null) { + return; + } + mEventStore.deleteEventHistory(CATEGORY_SHORTCUT_BASED, shortcutId); + if (conversationInfo.getLocusId() != null) { + mEventStore.deleteEventHistory( + CATEGORY_LOCUS_ID_BASED, conversationInfo.getLocusId().getId()); + } + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (!TextUtils.isEmpty(phoneNumber)) { + if (isDefaultDialer()) { + mEventStore.deleteEventHistory(CATEGORY_CALL, phoneNumber); + } + if (isDefaultSmsApp()) { + mEventStore.deleteEventHistory(CATEGORY_SMS, phoneNumber); + } + } + } + + /** Prunes the events and index data that don't have a associated conversation. */ + void pruneOrphanEvents() { + mEventStore.pruneOrphanEventHistories(CATEGORY_SHORTCUT_BASED, + key -> mConversationStore.getConversation(key) != null); + mEventStore.pruneOrphanEventHistories(CATEGORY_LOCUS_ID_BASED, + key -> mConversationStore.getConversationByLocusId(new LocusId(key)) != null); + if (isDefaultDialer()) { + mEventStore.pruneOrphanEventHistories(CATEGORY_CALL, + key -> mConversationStore.getConversationByPhoneNumber(key) != null); + } + if (isDefaultSmsApp()) { + mEventStore.pruneOrphanEventHistories(CATEGORY_SMS, + key -> mConversationStore.getConversationByPhoneNumber(key) != null); + } + } + void onDestroy() { // TODO: STOPSHIP: Implements this method for the case of package being uninstalled. } diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java index 644c155b4158..bf09681e7027 100644 --- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java @@ -129,8 +129,8 @@ class UsageStatsQueryHelper { if (packageData.getConversationStore().getConversation(shortcutId) == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutId); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); eventHistory.addEvent(event); } @@ -138,8 +138,8 @@ class UsageStatsQueryHelper { if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( - locusId); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId()); eventHistory.addEvent(event); } @@ -151,8 +151,8 @@ class UsageStatsQueryHelper { if (conversationInfo == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( - conversationInfo.getShortcutId()); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, conversationInfo.getShortcutId()); eventHistory.addEvent(event); } } diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index aaa5db878e08..7ca4b6c76a36 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -104,6 +104,14 @@ class UserData { return mPackageDataMap.get(packageName); } + /** Deletes the specified package data. */ + void deletePackageData(@NonNull String packageName) { + PackageData packageData = mPackageDataMap.remove(packageName); + if (packageData != null) { + packageData.onDestroy(); + } + } + void setDefaultDialer(@Nullable String packageName) { mDefaultDialer = packageName; } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 6769faaa4c5d..0bb984ef164b 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -24,12 +24,14 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -41,6 +43,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; +import android.app.job.JobScheduler; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; @@ -49,12 +52,15 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; +import android.content.pm.parsing.AndroidPackage; import android.database.ContentObserver; import android.net.Uri; +import android.os.CancellationSignal; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; @@ -66,6 +72,7 @@ import android.telephony.TelephonyManager; import android.util.Range; import com.android.internal.app.ChooserActivity; +import com.android.internal.content.PackageMonitor; import com.android.server.LocalServices; import org.junit.After; @@ -84,6 +91,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Consumer; @RunWith(JUnit4.class) public final class DataManagerTest { @@ -101,12 +109,14 @@ public final class DataManagerTest { @Mock private Context mContext; @Mock private ShortcutServiceInternal mShortcutServiceInternal; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; + @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private ShortcutManager mShortcutManager; @Mock private UserManager mUserManager; @Mock private TelephonyManager mTelephonyManager; @Mock private TelecomManager mTelecomManager; @Mock private ContentResolver mContentResolver; @Mock private ScheduledExecutorService mExecutorService; + @Mock private JobScheduler mJobScheduler; @Mock private ScheduledFuture mScheduledFuture; @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; @@ -114,6 +124,7 @@ public final class DataManagerTest { private NotificationChannel mNotificationChannel; private DataManager mDataManager; private int mCallingUserId; + private CancellationSignal mCancellationSignal; private TestInjector mInjector; @Before @@ -124,6 +135,15 @@ public final class DataManagerTest { addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); + addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternal); + AndroidPackage androidPackage = mock(AndroidPackage.class); + when(androidPackage.getPackageName()).thenReturn(TEST_PKG_NAME); + doAnswer(ans -> { + Consumer<AndroidPackage> callback = (Consumer<AndroidPackage>) ans.getArguments()[0]; + callback.accept(androidPackage); + return null; + }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt()); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); Context originalContext = getInstrumentation().getTargetContext(); @@ -145,6 +165,10 @@ public final class DataManagerTest { when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class))) .thenReturn(TEST_PKG_NAME); + when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler); + when(mContext.getSystemServiceName(JobScheduler.class)).thenReturn( + Context.JOB_SCHEDULER_SERVICE); + when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any( TimeUnit.class))).thenReturn(mScheduledFuture); @@ -168,6 +192,8 @@ public final class DataManagerTest { mCallingUserId = USER_ID_PRIMARY; + mCancellationSignal = new CancellationSignal(); + mInjector = new TestInjector(); mDataManager = new DataManager(mContext, mInjector); mDataManager.initialize(); @@ -177,6 +203,7 @@ public final class DataManagerTest { public void tearDown() { LocalServices.removeServiceForTest(ShortcutServiceInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); } @Test @@ -454,6 +481,101 @@ public final class DataManagerTest { assertEquals(2, activeTimeSlots.size()); } + @Test + public void testDeleteUninstalledPackageDataOnPackageRemoved() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + + PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY); + Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_PRIMARY); + intent.setData(Uri.parse("package:" + TEST_PKG_NAME)); + packageMonitor.onReceive(mContext, intent); + assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + } + + @Test + public void testPruneUninstalledPackageData() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + + doAnswer(ans -> null).when(mPackageManagerInternal) + .forEachInstalledPackage(any(Consumer.class), anyInt()); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + } + + @Test + public void testPruneCallEventsFromNonDialer() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + long currentTimestamp = System.currentTimeMillis(); + mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER, + new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_CALL_OUTGOING)); + + List<Range<Long>> activeTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.CALL_EVENT_TYPES) + .getActiveTimeSlots())); + assertEquals(1, activeTimeSlots.size()); + + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultDialer(null); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + activeTimeSlots.clear(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.CALL_EVENT_TYPES) + .getActiveTimeSlots())); + assertTrue(activeTimeSlots.isEmpty()); + } + + @Test + public void testPruneSmsEventsFromNonDefaultSmsApp() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME); + + long currentTimestamp = System.currentTimeMillis(); + mInjector.mMmsQueryHelper.mEventConsumer.accept(PHONE_NUMBER, + new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_SMS_OUTGOING)); + + List<Range<Long>> activeTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.SMS_EVENT_TYPES) + .getActiveTimeSlots())); + assertEquals(1, activeTimeSlots.size()); + + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(null); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + activeTimeSlots.clear(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.SMS_EVENT_TYPES) + .getActiveTimeSlots())); + assertTrue(activeTimeSlots.isEmpty()); + } + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { LocalServices.removeServiceForTest(clazz); LocalServices.addService(clazz, mock); diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java index 1ddc21e4ea4d..e52cdf59847c 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java @@ -81,8 +81,10 @@ public final class PackageDataTest { @Test public void testGetEventHistory() { EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateLocusEventHistory(LOCUS_ID).addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); @@ -96,9 +98,10 @@ public final class PackageDataTest { mIsDefaultDialer = true; mIsDefaultSmsApp = true; EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); - eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); assertTrue(mPackageData.isDefaultDialer()); assertTrue(mPackageData.isDefaultSmsApp()); @@ -113,9 +116,10 @@ public final class PackageDataTest { @Test public void testGetEventHistoryNotDefaultDialerOrSmsApp() { EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); - eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); assertFalse(mPackageData.isDefaultDialer()); assertFalse(mPackageData.isDefaultSmsApp()); @@ -125,6 +129,61 @@ public final class PackageDataTest { assertEventEquals(mE1, events.get(0)); } + @Test + public void testDeleteConversationData() { + mIsDefaultDialer = true; + mIsDefaultSmsApp = true; + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); + + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + + mPackageData.deleteDataForConversation(SHORTCUT_ID); + + eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertTrue(events.isEmpty()); + } + + @Test + public void testPruneOrphanEvents() { + mIsDefaultDialer = true; + mIsDefaultSmsApp = true; + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); + + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + + ConversationInfo conversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setLocusId(null) + .setContactUri(null) + .setContactPhoneNumber(null) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .build(); + mPackageData.getConversationStore().addOrUpdate(conversationInfo); + mPackageData.pruneOrphanEvents(); + eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + // Only the shortcut based event is kept. All the other events are deleted. + assertEventEquals(mE1, events.get(0)); + } + private void assertEventEquals(Event expected, Event actual) { assertEquals(expected.getTimestamp(), actual.getTimestamp()); assertEquals(expected.getType(), actual.getType()); diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java index 01d9dc00cf47..b1cdea2060b8 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -288,14 +288,13 @@ public final class UsageStatsQueryHelperTest { @Override @NonNull - EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { - return mShortcutEventHistory; - } - - @Override - @NonNull - EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { - return mLocusEventHistory; + EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) { + if (category == EventStore.CATEGORY_SHORTCUT_BASED) { + return mShortcutEventHistory; + } else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) { + return mLocusEventHistory; + } + throw new UnsupportedOperationException(); } } |