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:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user