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

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

Merge "Surface DS targets as per their own score and ranking, remove...

Merge "Surface DS targets as per their own score and ranking, remove customized ranking logic (max shortcuts count limit per app, app share adjustment). This is guarded by the flag  "apply_sharing_app_limits_in_sysui"." into sc-dev
parents c6c74484 64515ec8
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -850,9 +850,7 @@ public class ChooserActivity extends ResolverActivity implements
            final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
                    new ArrayList<>();

            // Separate ChooserTargets ranking scores and ranked Shortcuts.
            List<AppTarget> shortcutResults = new ArrayList<>();
            List<AppTarget> chooserTargetScores = new ArrayList<>();
            for (AppTarget appTarget : resultList) {
                if (appTarget.getShortcutInfo() == null) {
                    continue;
+21 −32
Original line number Diff line number Diff line
@@ -33,9 +33,9 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;

@@ -46,14 +46,13 @@ import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ChooserListAdapter extends ResolverListAdapter {
    private static final String TAG = "ChooserListAdapter";
@@ -70,8 +69,6 @@ public class ChooserListAdapter extends ResolverListAdapter {

    private static final int MAX_SUGGESTED_APP_TARGETS = 4;
    private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
    private static final int MAX_SERVICE_TARGET_APP = 8;
    private static final int DEFAULT_DIRECT_SHARE_RANKING_SCORE = 1000;

    /** {@link #getBaseScore} */
    public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
@@ -85,17 +82,11 @@ public class ChooserListAdapter extends ResolverListAdapter {

    private int mNumShortcutResults = 0;
    private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
    private boolean mApplySharingAppLimits;

    // Reserve spots for incoming direct share targets by adding placeholders
    private ChooserTargetInfo
            mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
    private int mValidServiceTargetsNum = 0;
    private int mAvailableServiceTargetsNum = 0;
    private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
            mParkingDirectShareTargets = new HashMap<>();
    private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
    private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
    private Set<ComponentName> mShortcutComponents = new HashSet<>();
    private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
    private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();

@@ -174,6 +165,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
                if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
            }
        }
        mApplySharingAppLimits = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
                true);
    }

    AppPredictor getAppPredictor() {
@@ -209,10 +204,6 @@ public class ChooserListAdapter extends ResolverListAdapter {
    private void createPlaceHolders() {
        mNumShortcutResults = 0;
        mServiceTargets.clear();
        mValidServiceTargetsNum = 0;
        mParkingDirectShareTargets.clear();
        mPendingChooserTargetService.clear();
        mShortcutComponents.clear();
        for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
            mServiceTargets.add(mPlaceHolderTargetInfo);
        }
@@ -508,26 +499,28 @@ public class ChooserListAdapter extends ResolverListAdapter {
        if (targets.size() == 0) {
            return;
        }

        final float baseScore = getBaseScore(origTarget, targetType);
        Collections.sort(targets, mBaseTargetComparator);

        final boolean isShortcutResult =
                (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
                        || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
        final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
                : MAX_CHOOSER_TARGETS_PER_APP;
        final int targetsLimit = mApplySharingAppLimits ? Math.min(targets.size(), maxTargets)
                : targets.size();
        float lastScore = 0;
        boolean shouldNotify = false;
        for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
        for (int i = 0, count = targetsLimit; i < count; i++) {
            final ChooserTarget target = targets.get(i);
            float targetScore = target.getScore();
            if (mApplySharingAppLimits) {
                targetScore *= baseScore;
                if (i > 0 && targetScore >= lastScore) {
                    // Apply a decay so that the top app can't crowd out everything else.
                    // This incents ChooserTargetServices to define what's truly better.
                    targetScore = lastScore * 0.95f;
                }
            }
            UserHandle userHandle = getUserHandle();
            Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
            boolean isInserted = insertServiceTarget(new SelectableTargetInfo(contextAsUser,
@@ -544,7 +537,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
                Log.d(TAG, " => " + target.toString() + " score=" + targetScore
                        + " base=" + target.getScore()
                        + " lastScore=" + lastScore
                        + " baseScore=" + baseScore);
                        + " baseScore=" + baseScore
                        + " applyAppLimit=" + mApplySharingAppLimits);
            }

            lastScore = targetScore;
@@ -580,16 +574,11 @@ public class ChooserListAdapter extends ResolverListAdapter {
        if (target == null) {
            return CALLER_TARGET_SCORE_BOOST;
        }

        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
            return SHORTCUT_TARGET_SCORE_BOOST;
        }

        float score = super.getScore(target);
        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
                || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
            return score * SHORTCUT_TARGET_SCORE_BOOST;
        }

        return score;
    }

+8 −0
Original line number Diff line number Diff line
@@ -473,6 +473,14 @@ public final class SystemUiDeviceConfigFlags {
     */
    public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";

    /**
     * (boolean) If true, SysUI provides guardrails for app usage of Direct Share by enforcing
     * limits on number of targets per app & adjusting scores for apps providing many targets. If
     * false, this step is skipped. This should be true unless the ranking provider configured by
     * [some other flag] is expected to manage these incentives.
     */
    public static final String APPLY_SHARING_APP_LIMITS_IN_SYSUI =
            "apply_sharing_app_limits_in_sysui";

    /*
     * (long) The duration that the home button must be pressed before triggering Assist
+123 −3
Original line number Diff line number Diff line
@@ -154,8 +154,8 @@ public class ChooserActivityTest {
        sOverrides.reset();
        sOverrides.createPackageManager = mPackageManagerOverride;
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
                Boolean.toString(false),
                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
                Boolean.toString(true),
                true /* makeDefault*/);
    }

@@ -1017,7 +1017,7 @@ public class ChooserActivityTest {
        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
                is(SHORTCUT_TARGET_SCORE_BOOST));
                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
    }
@@ -1262,6 +1262,126 @@ public class ChooserActivityTest {
                .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(0));
    }

    @Test
    public void testShortcutTargetWithApplyAppLimits() throws InterruptedException {
        // Set up resources
        sOverrides.resources = Mockito.spy(
                InstrumentationRegistry.getInstrumentation().getContext().getResources());
        when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
                .thenReturn(1);
        Intent sendIntent = createSendTextIntent();
        // We need app targets for direct targets to get displayed
        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                Mockito.anyBoolean(),
                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
        // Create direct share target
        List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);

        // Start activity
        final ChooserWrapperActivity activity = mActivityRule
                .launchActivity(Intent.createChooser(sendIntent, null));

        // Insert the direct share target
        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
        List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
        directShareToShortcutInfos.put(serviceTargets.get(0),
                shortcutInfos.get(0).getShortcutInfo());
        directShareToShortcutInfos.put(serviceTargets.get(1),
                shortcutInfos.get(1).getShortcutInfo());
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> activity.getAdapter().addServiceResults(
                        activity.createTestDisplayResolveInfo(sendIntent,
                                ri,
                                "testLabel",
                                "testInfo",
                                sendIntent,
                                /* resolveInfoPresentationGetter */ null),
                        serviceTargets,
                        TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
                        directShareToShortcutInfos,
                        List.of())
        );
        // Thread.sleep shouldn't be a thing in an integration test but it's
        // necessary here because of the way the code is structured
        // TODO: restructure the tests b/129870719
        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);

        assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                activity.getAdapter().getCount(), is(3));
        assertThat("Chooser should have exactly one selectable direct target",
                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
        assertThat("The resolver info must match the resolver info used to create the target",
                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
        assertThat("The display label must match",
                activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
    }

    @Test
    public void testShortcutTargetWithoutApplyAppLimits() throws InterruptedException {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
                Boolean.toString(false),
                true /* makeDefault*/);
        // Set up resources
        sOverrides.resources = Mockito.spy(
                InstrumentationRegistry.getInstrumentation().getContext().getResources());
        when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
                .thenReturn(1);
        Intent sendIntent = createSendTextIntent();
        // We need app targets for direct targets to get displayed
        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                Mockito.anyBoolean(),
                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
        // Create direct share target
        List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);

        // Start activity
        final ChooserWrapperActivity activity = mActivityRule
                .launchActivity(Intent.createChooser(sendIntent, null));

        // Insert the direct share target
        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
        List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
        directShareToShortcutInfos.put(serviceTargets.get(0),
                shortcutInfos.get(0).getShortcutInfo());
        directShareToShortcutInfos.put(serviceTargets.get(1),
                shortcutInfos.get(1).getShortcutInfo());
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> activity.getAdapter().addServiceResults(
                        activity.createTestDisplayResolveInfo(sendIntent,
                                ri,
                                "testLabel",
                                "testInfo",
                                sendIntent,
                                /* resolveInfoPresentationGetter */ null),
                        serviceTargets,
                        TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
                        directShareToShortcutInfos,
                        List.of())
        );
        // Thread.sleep shouldn't be a thing in an integration test but it's
        // necessary here because of the way the code is structured
        // TODO: restructure the tests b/129870719
        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);

        assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
                activity.getAdapter().getCount(), is(4));
        assertThat("Chooser should have exactly two selectable direct target",
                activity.getAdapter().getSelectableServiceTargetCount(), is(2));
        assertThat("The resolver info must match the resolver info used to create the target",
                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
        assertThat("The display label must match",
                activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
        assertThat("The display label must match",
                activity.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
    }

    // This test is too long and too slow and should not be taken as an example for future tests.
    @Test
    public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()