diff options
author | Song Hu <songhu@google.com> | 2020-09-23 18:12:46 -0700 |
---|---|---|
committer | Song Hu <songhu@google.com> | 2020-09-24 22:18:49 -0700 |
commit | eedf47cbf5836730e7076d920722930a544af846 (patch) | |
tree | 97a1b30f5184d7dadd551f696ba44b82e1115da0 | |
parent | d6af5b5d1e5986c2d63dfc89d996c899416cf9f8 (diff) |
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
4 files changed, 238 insertions, 2 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index da26930ca727..b4cd145ca374 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -390,6 +390,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. */ public static final String PIP_USER_RESIZE = "pip_user_resize"; 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 236ac8407faa..9e6cf845d144 100644 --- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -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())); diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java index c77843cfb044..d4a502da56c7 100644 --- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java +++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java index 45fff48ade55..605878d85bce 100644 --- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java @@ -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; |