diff options
5 files changed, 392 insertions, 130 deletions
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 6b97c98b0029..da971da46350 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -51,7 +51,6 @@ import android.provider.Telephony.MmsSms; import android.service.notification.NotificationListenerService; 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; @@ -275,40 +274,35 @@ public class DataManager { mContext.getPackageName(), intentFilter, callingUserId); } - /** Reports the {@link AppTargetEvent} from App Prediction Manager. */ - public void reportAppTargetEvent(@NonNull AppTargetEvent event, + /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */ + public void reportShareTargetEvent(@NonNull AppTargetEvent event, @Nullable IntentFilter intentFilter) { AppTarget appTarget = event.getTarget(); - ShortcutInfo shortcutInfo = appTarget != null ? appTarget.getShortcutInfo() : null; - if (shortcutInfo == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) { - return; - } - PackageData packageData = getPackage(appTarget.getPackageName(), - appTarget.getUser().getIdentifier()); - if (packageData == null) { + if (appTarget == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) { return; } + UserData userData = getUnlockedUserData(appTarget.getUser().getIdentifier()); + PackageData packageData = userData.getOrCreatePackageData(appTarget.getPackageName()); + String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null; + @Event.EventType int eventType = mimeTypeToShareEventType(mimeType); + EventHistoryImpl eventHistory; if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) { - String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null; - String shortcutId = shortcutInfo.getId(); - if (packageData.getConversationStore().getConversation(shortcutId) == null - || TextUtils.isEmpty(mimeType)) { + // Direct share event + if (appTarget.getShortcutInfo() == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( - EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId()); - @Event.EventType int eventType; - if (mimeType.startsWith("text/")) { - eventType = Event.TYPE_SHARE_TEXT; - } else if (mimeType.startsWith("image/")) { - eventType = Event.TYPE_SHARE_IMAGE; - } else if (mimeType.startsWith("video/")) { - eventType = Event.TYPE_SHARE_VIDEO; - } else { - eventType = Event.TYPE_SHARE_OTHER; + String shortcutId = appTarget.getShortcutInfo().getId(); + if (packageData.getConversationStore().getConversation(shortcutId) == null) { + addOrUpdateConversationInfo(appTarget.getShortcutInfo()); } - eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType)); + eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); + } else { + // App share event + eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_CLASS_BASED, appTarget.getClassName()); } + eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType)); } /** Prunes the data for the specified user. */ @@ -335,6 +329,17 @@ public class DataManager { }); } + private int mimeTypeToShareEventType(String mimeType) { + if (mimeType.startsWith("text/")) { + return Event.TYPE_SHARE_TEXT; + } else if (mimeType.startsWith("image/")) { + return Event.TYPE_SHARE_IMAGE; + } else if (mimeType.startsWith("video/")) { + return Event.TYPE_SHARE_VIDEO; + } + return Event.TYPE_SHARE_OTHER; + } + private void pruneUninstalledPackageData(@NonNull UserData userData) { Set<String> installApps = new ArraySet<>(); mPackageManagerInternal.forEachInstalledPackage( @@ -410,12 +415,13 @@ public class DataManager { EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); } + private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) { + return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0; + } + @VisibleForTesting @WorkerThread - void onShortcutAddedOrUpdated(@NonNull ShortcutInfo shortcutInfo) { - if (shortcutInfo.getPersons() == null || shortcutInfo.getPersons().length == 0) { - return; - } + void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) { UserData userData = getUnlockedUserData(shortcutInfo.getUserId()); if (userData == null) { return; @@ -431,24 +437,24 @@ public class DataManager { builder.setShortcutId(shortcutInfo.getId()); builder.setLocusId(shortcutInfo.getLocusId()); builder.setShortcutFlags(shortcutInfo.getFlags()); - - Person person = shortcutInfo.getPersons()[0]; - builder.setPersonImportant(person.isImportant()); - builder.setPersonBot(person.isBot()); - String contactUri = person.getUri(); - if (contactUri != null) { - ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); - if (helper.query(contactUri)) { - builder.setContactUri(helper.getContactUri()); - builder.setContactStarred(helper.isStarred()); - builder.setContactPhoneNumber(helper.getPhoneNumber()); + builder.setContactUri(null); + builder.setContactPhoneNumber(null); + builder.setContactStarred(false); + + if (shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0) { + Person person = shortcutInfo.getPersons()[0]; + builder.setPersonImportant(person.isImportant()); + builder.setPersonBot(person.isBot()); + String contactUri = person.getUri(); + if (contactUri != null) { + ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); + if (helper.query(contactUri)) { + builder.setContactUri(helper.getContactUri()); + builder.setContactStarred(helper.isStarred()); + builder.setContactPhoneNumber(helper.getPhoneNumber()); + } } - } else { - builder.setContactUri(null); - builder.setContactPhoneNumber(null); - builder.setContactStarred(false); } - conversationStore.addOrUpdate(builder.build()); } @@ -626,7 +632,9 @@ public class DataManager { List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId, /*shortcutIds=*/ null); for (ShortcutInfo shortcut : shortcuts) { - onShortcutAddedOrUpdated(shortcut); + if (isPersonShortcut(shortcut)) { + addOrUpdateConversationInfo(shortcut); + } } }); } diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java index 19cf8af5d66b..c89dadc3fbd6 100644 --- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java @@ -73,6 +73,7 @@ public class AppTargetPredictor { */ @MainThread public void onAppTargetEvent(AppTargetEvent event) { + mCallbackExecutor.execute(() -> reportAppTargetEvent(event)); } /** @@ -104,6 +105,11 @@ public class AppTargetPredictor { return mUpdatePredictionsMethod; } + /** To be overridden by the subclass to report app target event. */ + @WorkerThread + void reportAppTargetEvent(AppTargetEvent event) { + } + /** To be overridden by the subclass to predict the targets. */ @WorkerThread void predictTargets() { diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java index 90d821641149..8e5d75be12b7 100644 --- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -16,7 +16,6 @@ package com.android.server.people.prediction; -import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -28,15 +27,18 @@ import android.app.prediction.AppTargetId; import android.content.IntentFilter; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager.ShareShortcutInfo; +import android.util.Range; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ChooserActivity; import com.android.server.people.data.ConversationInfo; import com.android.server.people.data.DataManager; +import com.android.server.people.data.Event; import com.android.server.people.data.EventHistory; import com.android.server.people.data.PackageData; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -52,89 +54,139 @@ class ShareTargetPredictor extends AppTargetPredictor { ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY); } - @MainThread + /** Reports chosen history of direct/app share targets. */ + @WorkerThread @Override - public void onAppTargetEvent(AppTargetEvent event) { - getDataManager().reportAppTargetEvent(event, mIntentFilter); + void reportAppTargetEvent(AppTargetEvent event) { + getDataManager().reportShareTargetEvent(event, mIntentFilter); } + /** Provides prediction on direct share targets */ @WorkerThread @Override - protected void predictTargets() { - List<ShareTarget> shareTargets = getShareTargets(); - // TODO: Rank the share targets with the data in ShareTarget.mConversationData. - List<AppTarget> appTargets = new ArrayList<>(); - for (ShareTarget shareTarget : shareTargets) { - - ShortcutInfo shortcutInfo = shareTarget.getShareShortcutInfo().getShortcutInfo(); - AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId()); - String shareTargetClassName = - shareTarget.getShareShortcutInfo().getTargetComponent().getClassName(); - AppTarget appTarget = new AppTarget.Builder(appTargetId, shortcutInfo) - .setClassName(shareTargetClassName) - .build(); - appTargets.add(appTarget); - if (appTargets.size() >= getPredictionContext().getPredictedTargetCount()) { - break; - } + void predictTargets() { + List<ShareTarget> shareTargets = getDirectShareTargets(); + rankTargets(shareTargets); + List<AppTarget> res = new ArrayList<>(); + for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(), + shareTargets.size()); i++) { + res.add(shareTargets.get(i).getAppTarget()); } - updatePredictions(appTargets); + updatePredictions(res); } - @VisibleForTesting - List<ShareTarget> getShareTargets() { + /** Provides prediction on app share targets */ + @WorkerThread + @Override + void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { + List<ShareTarget> shareTargets = getAppShareTargets(targets); + rankTargets(shareTargets); + List<AppTarget> appTargetList = new ArrayList<>(); + shareTargets.forEach(t -> appTargetList.add(t.getAppTarget())); + callback.accept(appTargetList); + } + + private void rankTargets(List<ShareTarget> shareTargets) { + // Rank targets based on recency of sharing history only for the moment. + // TODO: Take more factors into ranking, e.g. frequency, mime type, foreground app. + Collections.sort(shareTargets, (t1, t2) -> { + if (t1.getEventHistory() == null) { + return 1; + } + if (t2.getEventHistory() == null) { + return -1; + } + Range<Long> timeSlot1 = t1.getEventHistory().getEventIndex( + Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot(); + Range<Long> timeSlot2 = t2.getEventHistory().getEventIndex( + Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot(); + if (timeSlot1 == null) { + return 1; + } else if (timeSlot2 == null) { + return -1; + } else { + return -Long.compare(timeSlot1.getUpper(), timeSlot2.getUpper()); + } + }); + } + + private List<ShareTarget> getDirectShareTargets() { List<ShareTarget> shareTargets = new ArrayList<>(); List<ShareShortcutInfo> shareShortcuts = getDataManager().getShareShortcuts(mIntentFilter, mCallingUserId); for (ShareShortcutInfo shareShortcut : shareShortcuts) { ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); + AppTarget appTarget = new AppTarget.Builder( + new AppTargetId(shortcutInfo.getId()), + shortcutInfo) + .setClassName(shareShortcut.getTargetComponent().getClassName()) + .build(); String packageName = shortcutInfo.getPackage(); int userId = shortcutInfo.getUserId(); PackageData packageData = getDataManager().getPackage(packageName, userId); - ConversationData conversationData = null; + ConversationInfo conversationInfo = null; + EventHistory eventHistory = null; if (packageData != null) { String shortcutId = shortcutInfo.getId(); - ConversationInfo conversationInfo = - packageData.getConversationInfo(shortcutId); - + conversationInfo = packageData.getConversationInfo(shortcutId); if (conversationInfo != null) { - EventHistory eventHistory = packageData.getEventHistory(shortcutId); - conversationData = new ConversationData( - packageName, userId, conversationInfo, eventHistory); + eventHistory = packageData.getEventHistory(shortcutId); } } - shareTargets.add(new ShareTarget(shareShortcut, conversationData)); + shareTargets.add(new ShareTarget(appTarget, eventHistory, conversationInfo)); } return shareTargets; } + private List<ShareTarget> getAppShareTargets(List<AppTarget> targets) { + List<ShareTarget> shareTargets = new ArrayList<>(); + for (AppTarget target : targets) { + PackageData packageData = getDataManager().getPackage(target.getPackageName(), + target.getUser().getIdentifier()); + shareTargets.add(new ShareTarget(target, + packageData == null ? null + : packageData.getClassLevelEventHistory(target.getClassName()), null)); + } + return shareTargets; + } + @VisibleForTesting static class ShareTarget { @NonNull - private final ShareShortcutInfo mShareShortcutInfo; + private final AppTarget mAppTarget; @Nullable - private final ConversationData mConversationData; - - private ShareTarget(@NonNull ShareShortcutInfo shareShortcutInfo, - @Nullable ConversationData conversationData) { - mShareShortcutInfo = shareShortcutInfo; - mConversationData = conversationData; + private final EventHistory mEventHistory; + @Nullable + private final ConversationInfo mConversationInfo; + + private ShareTarget(@NonNull AppTarget appTarget, + @Nullable EventHistory eventHistory, + @Nullable ConversationInfo conversationInfo) { + mAppTarget = appTarget; + mEventHistory = eventHistory; + mConversationInfo = conversationInfo; } @NonNull @VisibleForTesting - ShareShortcutInfo getShareShortcutInfo() { - return mShareShortcutInfo; + AppTarget getAppTarget() { + return mAppTarget; + } + + @Nullable + @VisibleForTesting + EventHistory getEventHistory() { + return mEventHistory; } @Nullable @VisibleForTesting - ConversationData getConversationData() { - return mConversationData; + ConversationInfo getConversationInfo() { + return mConversationInfo; } } } 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 f73a4b5776b6..b54317b768c5 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 @@ -100,6 +100,7 @@ public final class DataManagerTest { private static final int USER_ID_PRIMARY_MANAGED = 10; private static final int USER_ID_SECONDARY = 11; private static final String TEST_PKG_NAME = "pkg"; + private static final String TEST_CLASS_NAME = "class"; private static final String TEST_SHORTCUT_ID = "sc"; private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123"; private static final String PHONE_NUMBER = "+1234567890"; @@ -206,13 +207,13 @@ public final class DataManagerTest { mDataManager.onUserUnlocked(USER_ID_PRIMARY_MANAGED); mDataManager.onUserUnlocked(USER_ID_SECONDARY); - mDataManager.onShortcutAddedOrUpdated( + mDataManager.addOrUpdateConversationInfo( buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson(true, false))); - mDataManager.onShortcutAddedOrUpdated( + mDataManager.addOrUpdateConversationInfo( buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2", buildPerson(false, true))); - mDataManager.onShortcutAddedOrUpdated( + mDataManager.addOrUpdateConversationInfo( buildShortcutInfo("pkg_3", USER_ID_SECONDARY, "sc_3", buildPerson())); List<ConversationInfo> conversations = getConversationsInPrimary(); @@ -236,9 +237,9 @@ public final class DataManagerTest { @Test public void testAccessConversationForUnlockedUsersOnly() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); - mDataManager.onShortcutAddedOrUpdated( + mDataManager.addOrUpdateConversationInfo( buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson())); - mDataManager.onShortcutAddedOrUpdated( + mDataManager.addOrUpdateConversationInfo( buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2", buildPerson())); List<ConversationInfo> conversations = getConversationsInPrimary(); @@ -261,11 +262,12 @@ public final class DataManagerTest { } @Test - public void testReportAppTargetEvent() throws IntentFilter.MalformedMimeTypeException { + public void testReportAppTargetEvent_directSharing() + throws IntentFilter.MalformedMimeTypeException { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); AppTarget appTarget = new AppTarget.Builder(new AppTargetId(TEST_SHORTCUT_ID), shortcut) .build(); @@ -274,7 +276,55 @@ public final class DataManagerTest { .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) .build(); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); - mDataManager.reportAppTargetEvent(appTargetEvent, intentFilter); + mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter); + + List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut( + Event.SHARE_EVENT_TYPES); + assertEquals(1, activeShareTimeSlots.size()); + } + + @Test + public void testReportAppTargetEvent_directSharing_createConversation() + throws IntentFilter.MalformedMimeTypeException { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + null); + AppTarget appTarget = new AppTarget.Builder(new AppTargetId(TEST_SHORTCUT_ID), shortcut) + .build(); + AppTargetEvent appTargetEvent = + new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) + .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) + .build(); + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); + + mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter); + + List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut( + Event.SHARE_EVENT_TYPES); + assertEquals(1, activeShareTimeSlots.size()); + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertNotNull(conversationInfo); + assertEquals(conversationInfo.getShortcutId(), TEST_SHORTCUT_ID); + } + + @Test + public void testReportAppTargetEvent_appSharing() + throws IntentFilter.MalformedMimeTypeException { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + AppTarget appTarget = new AppTarget.Builder( + new AppTargetId(TEST_SHORTCUT_ID), + TEST_PKG_NAME, + UserHandle.of(USER_ID_PRIMARY)) + .setClassName(TEST_CLASS_NAME) + .build(); + AppTargetEvent appTargetEvent = + new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) + .build(); + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); + + mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter); List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut( Event.SHARE_EVENT_TYPES); @@ -288,7 +338,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); final String newPhoneNumber = "+1000000000"; mInjector.mContactsQueryHelper.mIsStarred = true; @@ -312,7 +362,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); @@ -330,7 +380,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); @@ -350,7 +400,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); @@ -375,7 +425,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); @@ -401,7 +451,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); NotificationListenerService listenerService = mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); @@ -430,7 +480,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); ContentObserver contentObserver = mDataManager.getCallLogContentObserverForTesting(); contentObserver.onChange(false); @@ -453,7 +503,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME); ContentObserver contentObserver = mDataManager.getMmsSmsContentObserverForTesting(); @@ -476,7 +526,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY); @@ -493,7 +543,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); doAnswer(ans -> null).when(mPackageManagerInternal) @@ -508,7 +558,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); long currentTimestamp = System.currentTimeMillis(); mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER, @@ -529,7 +579,7 @@ public final class DataManagerTest { ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); - mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.addOrUpdateConversationInfo(shortcut); mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME); long currentTimestamp = System.currentTimeMillis(); diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java index f498a9450c9e..c6cd34732acf 100644 --- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java +++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java @@ -16,16 +16,19 @@ package com.android.server.people.prediction; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetId; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -33,22 +36,26 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager.ShareShortcutInfo; import android.os.Bundle; import android.os.UserHandle; +import android.util.Range; import com.android.server.people.data.ConversationInfo; import com.android.server.people.data.DataManager; import com.android.server.people.data.EventHistory; +import com.android.server.people.data.EventIndex; import com.android.server.people.data.PackageData; -import com.android.server.people.prediction.ShareTargetPredictor.ShareTarget; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; @RunWith(JUnit4.class) public final class ShareTargetPredictorTest { @@ -57,17 +64,32 @@ public final class ShareTargetPredictorTest { private static final int NUM_PREDICTED_TARGETS = 5; private static final int USER_ID = 0; private static final String PACKAGE_1 = "pkg1"; - private static final String CLASS_1 = "cls1"; private static final String PACKAGE_2 = "pkg2"; + private static final String PACKAGE_3 = "pkg3"; + private static final String CLASS_1 = "cls1"; private static final String CLASS_2 = "cls2"; @Mock private Context mContext; @Mock private DataManager mDataManager; + @Mock private Consumer<List<AppTarget>> mUpdatePredictionsMethod; @Mock private PackageData mPackageData1; @Mock private PackageData mPackageData2; + @Mock private EventHistory mEventHistory1; + @Mock private EventHistory mEventHistory2; + @Mock private EventHistory mEventHistory3; + @Mock private EventHistory mEventHistory4; + @Mock private EventHistory mEventHistory5; + @Mock private EventHistory mEventHistory6; - private List<ShareShortcutInfo> mShareShortcuts = new ArrayList<>(); + @Mock private EventIndex mEventIndex1; + @Mock private EventIndex mEventIndex2; + @Mock private EventIndex mEventIndex3; + @Mock private EventIndex mEventIndex4; + @Mock private EventIndex mEventIndex5; + @Mock private EventIndex mEventIndex6; + @Captor private ArgumentCaptor<List<AppTarget>> mAppTargetCaptor; + private List<ShareShortcutInfo> mShareShortcuts = new ArrayList<>(); private ShareTargetPredictor mPredictor; @Before @@ -84,11 +106,11 @@ public final class ShareTargetPredictorTest { .setExtras(new Bundle()) .build(); mPredictor = new ShareTargetPredictor( - predictionContext, targets -> { }, mDataManager, USER_ID); + predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID); } @Test - public void testGetShareTargets() { + public void testPredictTargets() { mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); @@ -99,24 +121,148 @@ public final class ShareTargetPredictorTest { when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class)); // "sc4" does not have a ConversationInfo. - when(mPackageData1.getEventHistory(anyString())).thenReturn(mock(EventHistory.class)); - when(mPackageData2.getEventHistory(anyString())).thenReturn(mock(EventHistory.class)); + when(mPackageData1.getEventHistory("sc1")).thenReturn(mEventHistory1); + when(mPackageData1.getEventHistory("sc2")).thenReturn(mEventHistory2); + when(mPackageData2.getEventHistory("sc3")).thenReturn(mEventHistory3); + when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1); + when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2); + when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3); + when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L)); + when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L)); + when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L)); - List<ShareTarget> shareTargets = mPredictor.getShareTargets(); + mPredictor.predictTargets(); - assertEquals(4, shareTargets.size()); + verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture()); + List<AppTarget> res = mAppTargetCaptor.getValue(); + assertEquals(4, res.size()); + + assertEquals("sc3", res.get(0).getId().getId()); + assertEquals(CLASS_2, res.get(0).getClassName()); + assertEquals(PACKAGE_2, res.get(0).getPackageName()); + + assertEquals("sc2", res.get(1).getId().getId()); + assertEquals(CLASS_1, res.get(1).getClassName()); + assertEquals(PACKAGE_1, res.get(1).getPackageName()); + + assertEquals("sc1", res.get(2).getId().getId()); + assertEquals(CLASS_1, res.get(2).getClassName()); + assertEquals(PACKAGE_1, res.get(2).getPackageName()); + + assertEquals("sc4", res.get(3).getId().getId()); + assertEquals(CLASS_2, res.get(3).getClassName()); + assertEquals(PACKAGE_2, res.get(3).getPackageName()); + } + + @Test + public void testPredictTargets_reachTargetsLimit() { + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6")); + + when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc4")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData1.getConversationInfo("sc5")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc6")).thenReturn(mock(ConversationInfo.class)); + + when(mPackageData1.getEventHistory("sc1")).thenReturn(mEventHistory1); + when(mPackageData1.getEventHistory("sc2")).thenReturn(mEventHistory2); + when(mPackageData2.getEventHistory("sc3")).thenReturn(mEventHistory3); + when(mPackageData2.getEventHistory("sc4")).thenReturn(mEventHistory4); + when(mPackageData1.getEventHistory("sc5")).thenReturn(mEventHistory5); + when(mPackageData2.getEventHistory("sc6")).thenReturn(mEventHistory6); + + when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1); + when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2); + when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3); + when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4); + when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5); + when(mEventHistory6.getEventIndex(anySet())).thenReturn(mEventIndex6); + when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L)); + when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L)); + when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L)); + when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(4L, 5L)); + when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(5L, 6L)); + when(mEventIndex6.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(6L, 7L)); + + mPredictor.predictTargets(); + + verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture()); + List<AppTarget> res = mAppTargetCaptor.getValue(); + assertEquals(5, res.size()); + + assertEquals("sc6", res.get(0).getId().getId()); + assertEquals(CLASS_2, res.get(0).getClassName()); + assertEquals(PACKAGE_2, res.get(0).getPackageName()); + + assertEquals("sc5", res.get(1).getId().getId()); + assertEquals(CLASS_1, res.get(1).getClassName()); + assertEquals(PACKAGE_1, res.get(1).getPackageName()); + + assertEquals("sc4", res.get(2).getId().getId()); + assertEquals(CLASS_2, res.get(2).getClassName()); + assertEquals(PACKAGE_2, res.get(2).getPackageName()); + + assertEquals("sc3", res.get(3).getId().getId()); + assertEquals(CLASS_2, res.get(3).getClassName()); + assertEquals(PACKAGE_2, res.get(3).getPackageName()); + + assertEquals("sc2", res.get(4).getId().getId()); + assertEquals(CLASS_1, res.get(4).getClassName()); + assertEquals(PACKAGE_1, res.get(4).getPackageName()); + } + + @Test + public void testSortTargets() { + AppTarget appTarget1 = new AppTarget.Builder( + new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)) + .setClassName(CLASS_1) + .build(); + AppTarget appTarget2 = new AppTarget.Builder( + new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)) + .setClassName(CLASS_2) + .build(); + AppTarget appTarget3 = new AppTarget.Builder( + new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID)) + .setClassName(CLASS_1) + .build(); + AppTarget appTarget4 = new AppTarget.Builder( + new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID)) + .setClassName(CLASS_2) + .build(); + AppTarget appTarget5 = new AppTarget.Builder( + new AppTargetId("cls1#pkg3"), PACKAGE_3, UserHandle.of(USER_ID)) + .setClassName(CLASS_1) + .build(); - assertEquals("sc1", shareTargets.get(0).getShareShortcutInfo().getShortcutInfo().getId()); - assertNotNull(shareTargets.get(0).getConversationData()); + when(mPackageData1.getClassLevelEventHistory(CLASS_1)).thenReturn(mEventHistory1); + when(mPackageData1.getClassLevelEventHistory(CLASS_2)).thenReturn(mEventHistory2); + when(mPackageData2.getClassLevelEventHistory(CLASS_1)).thenReturn(mEventHistory3); + when(mPackageData2.getClassLevelEventHistory(CLASS_2)).thenReturn(mEventHistory4); + // PackageData of PACKAGE_3 is empty. + when(mDataManager.getPackage(PACKAGE_3, USER_ID)).thenReturn(null); - assertEquals("sc2", shareTargets.get(1).getShareShortcutInfo().getShortcutInfo().getId()); - assertNotNull(shareTargets.get(1).getConversationData()); + when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1); + when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2); + when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3); + when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4); + when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L)); + when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L)); + when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L)); + when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(4L, 5L)); - assertEquals("sc3", shareTargets.get(2).getShareShortcutInfo().getShortcutInfo().getId()); - assertNotNull(shareTargets.get(2).getConversationData()); + mPredictor.sortTargets( + List.of(appTarget1, appTarget2, appTarget3, appTarget4, appTarget5), + mUpdatePredictionsMethod); - assertEquals("sc4", shareTargets.get(3).getShareShortcutInfo().getShortcutInfo().getId()); - assertNull(shareTargets.get(3).getConversationData()); + verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture()); + assertThat(mAppTargetCaptor.getValue()).containsExactly( + appTarget4, appTarget3, appTarget2, appTarget1, appTarget5); } private ShareShortcutInfo buildShareShortcut( |