diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 60b3a19c4cf3c..9dc40c80f18b8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5344,6 +5344,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
+
+
+
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 0000000000000..58f0654c7b510
--- /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 a904b42f47dba..2edfd7487ec06 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> mUsageStatsQueryFutures = new SparseArray<>();
private final SparseArray mNotificationListeners =
new SparseArray<>();
+ private final SparseArray 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 installApps = new ArraySet<>();
+ mPackageManagerInternal.forEachInstalledPackage(
+ pkg -> installApps.add(pkg.getPackageName()), userData.getUserId());
+ List 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 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
@@ -412,6 +471,11 @@ public class DataManager {
return mNotificationListeners.get(userId);
}
+ @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 6b6bd7e3cfb0f..6bef1db5582ec 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 b267d667b4228..d770f912ea405 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 d6b7a863ca2d1..c8d44ac071062 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 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 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 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 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