summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Danning Chen <danningc@google.com> 2020-02-07 17:49:42 -0800
committer Trung Lam <lamtrung@google.com> 2020-02-13 12:45:42 -0800
commitc4933eb5b7504c47fe80e6ef0de708694b325812 (patch)
tree9600dbeaa7775f223d7f1656ff0360ef9b5743c7
parentb82b55f30deb93bf83e0d8688762acc4e00bda78 (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
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--services/core/java/com/android/server/people/PeopleServiceInternal.java13
-rw-r--r--services/people/java/com/android/server/people/PeopleService.java26
-rw-r--r--services/people/java/com/android/server/people/data/ConversationStore.java6
-rw-r--r--services/people/java/com/android/server/people/data/DataMaintenanceService.java92
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java92
-rw-r--r--services/people/java/com/android/server/people/data/EventHistoryImpl.java11
-rw-r--r--services/people/java/com/android/server/people/data/EventList.java4
-rw-r--r--services/people/java/com/android/server/people/data/EventStore.java150
-rw-r--r--services/people/java/com/android/server/people/data/PackageData.java72
-rw-r--r--services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java12
-rw-r--r--services/people/java/com/android/server/people/data/UserData.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java122
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java15
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();
}
}