Implement sharesheet ranking in PeopleService. Ranking is only based on

recency of past sharing for the moment.

Bug: 149822311
Test: atest com.android.server.people.data.DataManagerTest
Test: atest com.android.server.people.data.ShareTargetPredictorTest
Change-Id: I90d1b3f488b34fede7d577dc04832b976f4a1bbb
This commit is contained in:
Song Hu
2020-02-19 06:17:58 -08:00
parent abe60fe376
commit ba132e4f26
5 changed files with 394 additions and 132 deletions

View File

@@ -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());
builder.setContactUri(null);
builder.setContactPhoneNumber(null);
builder.setContactStarred(false);
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());
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);
}
}
});
}

View File

@@ -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() {

View File

@@ -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
@Override
public void onAppTargetEvent(AppTargetEvent event) {
getDataManager().reportAppTargetEvent(event, mIntentFilter);
}
/** Reports chosen history of direct/app 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;
}
}
updatePredictions(appTargets);
void reportAppTargetEvent(AppTargetEvent event) {
getDataManager().reportShareTargetEvent(event, mIntentFilter);
}
@VisibleForTesting
List<ShareTarget> getShareTargets() {
/** Provides prediction on direct share targets */
@WorkerThread
@Override
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(res);
}
/** 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 final EventHistory mEventHistory;
@Nullable
private final ConversationInfo mConversationInfo;
private ShareTarget(@NonNull ShareShortcutInfo shareShortcutInfo,
@Nullable ConversationData conversationData) {
mShareShortcutInfo = shareShortcutInfo;
mConversationData = conversationData;
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
ConversationData getConversationData() {
return mConversationData;
EventHistory getEventHistory() {
return mEventHistory;
}
@Nullable
@VisibleForTesting
ConversationInfo getConversationInfo() {
return mConversationInfo;
}
}
}

View File

@@ -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();

View File

@@ -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;
@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("sc1", shareTargets.get(0).getShareShortcutInfo().getShortcutInfo().getId());
assertNotNull(shareTargets.get(0).getConversationData());
assertEquals("sc3", res.get(0).getId().getId());
assertEquals(CLASS_2, res.get(0).getClassName());
assertEquals(PACKAGE_2, res.get(0).getPackageName());
assertEquals("sc2", shareTargets.get(1).getShareShortcutInfo().getShortcutInfo().getId());
assertNotNull(shareTargets.get(1).getConversationData());
assertEquals("sc2", res.get(1).getId().getId());
assertEquals(CLASS_1, res.get(1).getClassName());
assertEquals(PACKAGE_1, res.get(1).getPackageName());
assertEquals("sc3", shareTargets.get(2).getShareShortcutInfo().getShortcutInfo().getId());
assertNotNull(shareTargets.get(2).getConversationData());
assertEquals("sc1", res.get(2).getId().getId());
assertEquals(CLASS_1, res.get(2).getClassName());
assertEquals(PACKAGE_1, res.get(2).getPackageName());
assertEquals("sc4", shareTargets.get(3).getShareShortcutInfo().getShortcutInfo().getId());
assertNull(shareTargets.get(3).getConversationData());
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();
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);
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));
mPredictor.sortTargets(
List.of(appTarget1, appTarget2, appTarget3, appTarget4, appTarget5),
mUpdatePredictionsMethod);
verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
assertThat(mAppTargetCaptor.getValue()).containsExactly(
appTarget4, appTarget3, appTarget2, appTarget1, appTarget5);
}
private ShareShortcutInfo buildShareShortcut(