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
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -24,17 +26,24 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
@Nullable
|
||||
EventHistory getSmsEventHistory(String phoneNumber) {
|
||||
return mSmsEventHistoryMap.get(phoneNumber);
|
||||
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 {@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 EventHistory} for the specified key if exists.
|
||||
*
|
||||
* @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());
|
||||
@Nullable
|
||||
EventHistory getEventHistory(@EventCategory int category, String key) {
|
||||
return mEventHistoryMaps.get(category).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 getOrCreateLocusEventHistory(LocusId locusId) {
|
||||
return mLocusEventHistoryMap.computeIfAbsent(locusId, 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 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.
|
||||
* 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 getOrCreateCallEventHistory(String phoneNumber) {
|
||||
return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl());
|
||||
void deleteEventHistory(@EventCategory int category, String key) {
|
||||
EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key);
|
||||
if (eventHistory != null) {
|
||||
eventHistory.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user