summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java14
-rw-r--r--services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java4
-rw-r--r--services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java147
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;