DO NOT MERGE Put parameterized weights on top two sharing shortcuts of each app as per shortcuts native ranking in PeopleService Sharesheet model.

By default weights are 0 which ensures ranking same as what it is now.

Bug: 168212835
Test: atest com.android.server.people.prediction.SharesheetModelScorerTest
Change-Id: I02eaa5b6a448c33b51e5f4c6acaba93e7a2bd995
This commit is contained in:
Song Hu
2020-09-23 18:12:46 -07:00
parent d6af5b5d1e
commit eedf47cbf5
4 changed files with 238 additions and 2 deletions

View File

@@ -389,6 +389,20 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
/**
* (float) Weight bonus applied on top sharing shortcuts as per native ranking provided by apps.
* Its range need to be 0 ~ 1.
*/
public static final String TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER =
"top_native_ranked_sharing_shortcut_booster";
/**
* (float) Weight bonus applied on 2nd top sharing shortcuts as per native ranking provided by
* apps. Its range need to be 0 ~ 1.
*/
public static final String NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER =
"non_top_native_ranked_sharing_shortcut_booster";
/**
* (boolean) Whether to enable user-drag resizing for PIP.
*/

View File

@@ -86,8 +86,8 @@ class ShareTargetPredictor extends AppTargetPredictor {
return;
}
List<ShareTarget> shareTargets = getDirectShareTargets();
SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
System.currentTimeMillis());
SharesheetModelScorer.computeScoreForDirectShare(shareTargets,
getShareEventType(mIntentFilter), System.currentTimeMillis());
Collections.sort(shareTargets,
Comparator.comparing(ShareTarget::getScore, reverseOrder())
.thenComparing(t -> t.getAppTarget().getRank()));

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Range;
@@ -27,12 +28,14 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -46,6 +49,7 @@ class SharesheetModelScorer {
private static final String TAG = "SharesheetModelScorer";
private static final boolean DEBUG = false;
private static final Integer RECENCY_SCORE_COUNT = 6;
private static final Integer NATIVE_RANK_COUNT = 2;
private static final float RECENCY_INITIAL_BASE_SCORE = 0.4F;
private static final float RECENCY_SCORE_INITIAL_DECAY = 0.05F;
private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F;
@@ -174,6 +178,77 @@ class SharesheetModelScorer {
postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
}
/**
* Computes ranking score for direct sharing. Update
* {@link ShareTargetPredictor.ShareTargetScore}.
*/
static void computeScoreForDirectShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
int shareEventType, long now) {
computeScore(shareTargets, shareEventType, now);
promoteTopNativeRankedShortcuts(shareTargets);
}
/**
* Promotes top (NATIVE_RANK_COUNT) shortcuts for each package and class, as per shortcut native
* ranking provided by apps.
*/
private static void promoteTopNativeRankedShortcuts(
List<ShareTargetPredictor.ShareTarget> shareTargets) {
float topShortcutBonus = DeviceConfig.getFloat(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
0f);
float secondTopShortcutBonus = DeviceConfig.getFloat(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
0f);
// Populates a map which key is a packageName and className pair, value is a max heap
// containing top (NATIVE_RANK_COUNT) shortcuts as per shortcut native ranking provided
// by apps.
Map<Pair<String, String>, PriorityQueue<ShareTargetPredictor.ShareTarget>>
topNativeRankedShareTargetMap = new ArrayMap<>();
for (ShareTargetPredictor.ShareTarget shareTarget : shareTargets) {
Pair<String, String> key = new Pair<>(shareTarget.getAppTarget().getPackageName(),
shareTarget.getAppTarget().getClassName());
if (!topNativeRankedShareTargetMap.containsKey(key)) {
topNativeRankedShareTargetMap.put(key,
new PriorityQueue<>(NATIVE_RANK_COUNT,
Collections.reverseOrder(Comparator.comparingInt(
p -> p.getAppTarget().getRank()))));
}
PriorityQueue<ShareTargetPredictor.ShareTarget> rankMaxHeap =
topNativeRankedShareTargetMap.get(key);
if (rankMaxHeap.isEmpty() || shareTarget.getAppTarget().getRank()
< rankMaxHeap.peek().getAppTarget().getRank()) {
if (rankMaxHeap.size() == NATIVE_RANK_COUNT) {
rankMaxHeap.poll();
}
rankMaxHeap.offer(shareTarget);
}
}
for (PriorityQueue<ShareTargetPredictor.ShareTarget> maxHeap :
topNativeRankedShareTargetMap.values()) {
while (!maxHeap.isEmpty()) {
ShareTargetPredictor.ShareTarget target = maxHeap.poll();
float bonus = maxHeap.isEmpty() ? topShortcutBonus : secondTopShortcutBonus;
target.setScore(probOR(target.getScore(), bonus));
if (DEBUG) {
Slog.d(TAG, String.format(
"SharesheetModel: promote top shortcut as per native ranking,"
+ "packageName: %s, className: %s, shortcutId: %s, bonus:%.2f,"
+ "total:%.2f",
target.getAppTarget().getPackageName(),
target.getAppTarget().getClassName(),
target.getAppTarget().getShortcutInfo() != null
? target.getAppTarget().getShortcutInfo().getId() : null,
bonus,
target.getScore()));
}
}
}
}
private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
// Populates a map which key is package name and value is list of shareTargets descended

View File

@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -28,9 +29,13 @@ import static org.mockito.Mockito.when;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.Range;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
@@ -121,6 +126,13 @@ public final class SharesheetModelScorerTest {
private ShareTargetPredictor.ShareTarget mShareTarget5;
private ShareTargetPredictor.ShareTarget mShareTarget6;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget1;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget2;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget3;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget4;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget5;
private ShareTargetPredictor.ShareTarget mShareShortcutTarget6;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -154,6 +166,46 @@ public final class SharesheetModelScorerTest {
new AppTargetId("cls2#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
.setClassName(CLASS_2).build(),
null, null);
mShareShortcutTarget1 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg1#1"), buildShortcutInfo(PACKAGE_1, 0, "1"))
.setClassName(CLASS_1).setRank(2).build(),
mEventHistory1, null);
mShareShortcutTarget2 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg1#2"), buildShortcutInfo(PACKAGE_1, 0, "2"))
.setClassName(CLASS_1).setRank(1).build(),
mEventHistory2, null);
mShareShortcutTarget3 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg1#3"), buildShortcutInfo(PACKAGE_1, 0, "3"))
.setClassName(CLASS_1).setRank(0).build(),
mEventHistory3, null);
mShareShortcutTarget4 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg2#1"), buildShortcutInfo(PACKAGE_2, 0, "1"))
.setClassName(CLASS_1).setRank(2).build(),
mEventHistory4, null);
mShareShortcutTarget5 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg2#2"), buildShortcutInfo(PACKAGE_2, 0, "2"))
.setClassName(CLASS_1).setRank(1).build(),
mEventHistory5, null);
mShareShortcutTarget6 = new ShareTargetPredictor.ShareTarget(
new AppTarget.Builder(
new AppTargetId("cls1#pkg2#3"), buildShortcutInfo(PACKAGE_2, 0, "3"))
.setClassName(CLASS_1).setRank(3).build(),
null, null);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
Float.toString(0f),
true /* makeDefault*/);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
Float.toString(0f),
true /* makeDefault*/);
}
@Test
@@ -433,6 +485,101 @@ public final class SharesheetModelScorerTest {
assertEquals(0f, mShareTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForDirectShare() {
// Frequency and recency
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(mEventIndex1.getActiveTimeSlots()).thenReturn(
List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
when(mEventIndex4.getActiveTimeSlots()).thenReturn(
List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
// Frequency of the same mime type
when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
SharesheetModelScorer.computeScore(
List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3,
mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6),
Event.TYPE_SHARE_TEXT,
NOW);
// Verification
assertEquals(0.514f, mShareShortcutTarget1.getScore(), DELTA);
assertEquals(0.475125f, mShareShortcutTarget2.getScore(), DELTA);
assertEquals(0.33f, mShareShortcutTarget3.getScore(), DELTA);
assertEquals(0.4411f, mShareShortcutTarget4.getScore(), DELTA);
assertEquals(0f, mShareShortcutTarget5.getScore(), DELTA);
assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA);
}
@Test
public void testComputeScoreForDirectShare_promoteTopNativeRankedShortcuts() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
Float.toString(0.4f),
true /* makeDefault*/);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
Float.toString(0.3f),
true /* makeDefault*/);
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(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
SharesheetModelScorer.computeScoreForDirectShare(
List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3,
mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6),
Event.TYPE_SHARE_TEXT, 20);
assertEquals(0f, mShareShortcutTarget1.getScore(), DELTA);
assertEquals(0.3f, mShareShortcutTarget2.getScore(), DELTA);
assertEquals(0.4f, mShareShortcutTarget3.getScore(), DELTA);
assertEquals(0.3f, mShareShortcutTarget4.getScore(), DELTA);
assertEquals(0.4f, mShareShortcutTarget5.getScore(), DELTA);
assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA);
}
private static ShortcutInfo buildShortcutInfo(String packageName, int userId, String id) {
Context mockContext = mock(Context.class);
when(mockContext.getPackageName()).thenReturn(packageName);
when(mockContext.getUserId()).thenReturn(userId);
when(mockContext.getUser()).thenReturn(UserHandle.of(userId));
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id).setShortLabel(id);
return builder.build();
}
private static UsageEvents.Event createUsageEvent(String packageName) {
UsageEvents.Event e = new UsageEvents.Event();
e.mPackage = packageName;