Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7c866d4 authored by Song Hu's avatar Song Hu Committed by Android (Google) Code Review
Browse files

Merge "DO NOT MERGE Put parameterized weights on top two sharing shortcuts of...

Merge "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." into rvc-qpr-dev
parents 28c7e80a eedf47cb
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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.
     */
+2 −2
Original line number Diff line number Diff line
@@ -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()));
+75 −0
Original line number Diff line number Diff line
@@ -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
+147 −0
Original line number Diff line number Diff line
@@ -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;