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

Commit 30e495d0 authored by Mehdi Alizadeh's avatar Mehdi Alizadeh Committed by Android (Google) Code Review
Browse files

Merge changes I41e08b86,I7760f37d into qt-qpr1-dev

* changes:
  Use ShortcutInfo#rank to order Sharing Shortcuts in ShareSheet
  Adds tests for AppPrediction service availability check
parents fd485887 707c0cf3
Loading
Loading
Loading
Loading
+78 −32
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -729,7 +730,13 @@ public class ChooserActivity extends ResolverActivity {
    /**
     * Returns true if app prediction service is defined and the component exists on device.
     */
    private boolean isAppPredictionServiceAvailable() {
    @VisibleForTesting
    public boolean isAppPredictionServiceAvailable() {
        if (getPackageManager().getAppPredictionServicePackageName() == null) {
            // Default AppPredictionService is not defined.
            return false;
        }

        final String appPredictionServiceName =
                getString(R.string.config_defaultAppPredictionService);
        if (appPredictionServiceName == null) {
@@ -1584,25 +1591,19 @@ public class ChooserActivity extends ResolverActivity {
        // ShareShortcutInfos directly.
        boolean resultMessageSent = false;
        for (int i = 0; i < driList.size(); i++) {
            List<ChooserTarget> chooserTargets = new ArrayList<>();
            List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
            for (int j = 0; j < resultList.size(); j++) {
                if (driList.get(i).getResolvedComponentName().equals(
                            resultList.get(j).getTargetComponent())) {
                    ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
                    // Incoming results are ordered but without a score. Create a score
                    // based on the index in order to be sorted appropriately when joined
                    // with legacy direct share api results.
                    float score = Math.max(1.0f - (0.05f * j), 0.0f);
                    ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
                    chooserTargets.add(chooserTarget);
                    if (mDirectShareAppTargetCache != null && appTargets != null) {
                        mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
                    matchingShortcuts.add(resultList.get(j));
                }
            }
            }
            if (chooserTargets.isEmpty()) {
            if (matchingShortcuts.isEmpty()) {
                continue;
            }
            List<ChooserTarget> chooserTargets = convertToChooserTarget(
                    matchingShortcuts, resultList, appTargets, shortcutType);

            final Message msg = Message.obtain();
            msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
            msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
@@ -1640,23 +1641,69 @@ public class ChooserActivity extends ResolverActivity {
        return false;
    }

    private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
                                                 float score) {
        ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
    /**
     * Converts a list of ShareShortcutInfos to ChooserTargets.
     * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
     *                         share intent filter.
     * @param allShortcuts List of all the shortcuts from all the packages on the device that are
     *                    returned for the current sharing action.
     * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
     * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
     *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
     * @return A list of ChooserTargets sorted by score in descending order.
     */
    @VisibleForTesting
    @NonNull
    public List<ChooserTarget> convertToChooserTarget(
            @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
            @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
            @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
        // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
        // list instead of the actual rank value when converting a rank to a score.
        List<Integer> scoreList = new ArrayList<>();
        if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
            for (int i = 0; i < matchingShortcuts.size(); i++) {
                int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
                if (!scoreList.contains(shortcutRank)) {
                    scoreList.add(shortcutRank);
                }
            }
            Collections.sort(scoreList);
        }

        List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
        for (int i = 0; i < matchingShortcuts.size(); i++) {
            ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
            int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));

            float score;
            if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
                // Incoming results are ordered. Create a score based on index in the original list.
                score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
            } else {
                // Create a score based on the rank of the shortcut.
                int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
                score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
            }

            Bundle extras = new Bundle();
            extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
        return new ChooserTarget(
                // The name of this target.
                shortcutInfo.getShortLabel(),
                // Don't load the icon until it is selected to be shown
                null,
                // The ranking score for this target (0.0-1.0); the system will omit items with low
                // scores when there are too many Direct Share items.
                score,
                // The name of the component to be launched if this target is chosen.
                shareShortcut.getTargetComponent().clone(),
                // The extra values here will be merged into the Intent when this target is chosen.
                extras);
            ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
                    null, // Icon will be loaded later if this target is selected to be shown.
                    score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);

            chooserTargetList.add(chooserTarget);
            if (mDirectShareAppTargetCache != null && allAppTargets != null) {
                mDirectShareAppTargetCache.put(chooserTarget,
                        allAppTargets.get(indexInAllShortcuts));
            }
        }

        // Sort ChooserTargets by score in descending order
        Comparator<ChooserTarget> byScore =
                (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
        Collections.sort(chooserTargetList, byScore);
        return chooserTargetList;
    }

    private String convertServiceName(String packageName, String serviceName) {
@@ -1748,8 +1795,7 @@ public class ChooserActivity extends ResolverActivity {
        if (!mIsAppPredictorComponentAvailable) {
            return null;
        }
        if (mAppPredictor == null
                    && getPackageManager().getAppPredictionServicePackageName() != null) {
        if (mAppPredictor == null) {
            final IntentFilter filter = getTargetIntentFilter();
            Bundle extras = new Bundle();
            extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+144 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -51,6 +52,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -807,6 +810,108 @@ public class ChooserActivityTest {
                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
    }

    /**
     * The case when AppPrediction service is not defined in PackageManager is already covered
     * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
     * case when the prediction service is defined but the component is not available on the device.
     */
    @Test
    public void testIsAppPredictionServiceAvailable() {
        Intent sendIntent = createSendTextIntent();
        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                Mockito.anyBoolean(),
                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);

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

        if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
        } else {
            assertThat(activity.isAppPredictionServiceAvailable(), is(true));

            sOverrides.resources = Mockito.spy(activity.getResources());
            when(sOverrides.resources.getString(R.string.config_defaultAppPredictionService))
                    .thenReturn("ComponentNameThatDoesNotExist");

            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
        }
    }

    @Test
    public void testConvertToChooserTarget_predictionService() {
        Intent sendIntent = createSendTextIntent();
        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                Mockito.anyBoolean(),
                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);

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

        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);

        int[] expectedOrderAllShortcuts = {0, 1, 2, 3};
        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.98f, 0.97f};

        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
                null, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
                expectedOrderAllShortcuts, expectedScoreAllShortcuts);

        List<ShareShortcutInfo> subset = new ArrayList<>();
        subset.add(shortcuts.get(1));
        subset.add(shortcuts.get(2));
        subset.add(shortcuts.get(3));

        int[] expectedOrderSubset = {1, 2, 3};
        float[] expectedScoreSubset = {0.99f, 0.98f, 0.97f};

        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
                TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
                expectedOrderSubset, expectedScoreSubset);
    }

    @Test
    public void testConvertToChooserTarget_shortcutManager() {
        Intent sendIntent = createSendTextIntent();
        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                Mockito.anyBoolean(),
                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);

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

        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);

        int[] expectedOrderAllShortcuts = {2, 0, 3, 1};
        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.99f, 0.98f};

        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
                null, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
                expectedOrderAllShortcuts, expectedScoreAllShortcuts);

        List<ShareShortcutInfo> subset = new ArrayList<>();
        subset.add(shortcuts.get(1));
        subset.add(shortcuts.get(2));
        subset.add(shortcuts.get(3));

        int[] expectedOrderSubset = {2, 3, 1};
        float[] expectedScoreSubset = {1.0f, 0.99f, 0.98f};

        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
                TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
                expectedOrderSubset, expectedScoreSubset);
    }

    // This test is too long and too slow and should not be taken as an example for future tests.
    @Test
    public void testDirectTargetSelectionLogging() throws InterruptedException {
@@ -1103,4 +1208,43 @@ public class ChooserActivityTest {

        return bitmap;
    }

    private List<ShareShortcutInfo> createShortcuts(Context context) {
        Intent testIntent = new Intent("TestIntent");

        List<ShareShortcutInfo> shortcuts = new ArrayList<>();
        shortcuts.add(new ShareShortcutInfo(
                new ShortcutInfo.Builder(context, "shortcut1")
                        .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0  2
                new ComponentName("package1", "class1")));
        shortcuts.add(new ShareShortcutInfo(
                new ShortcutInfo.Builder(context, "shortcut2")
                        .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1  3
                new ComponentName("package2", "class2")));
        shortcuts.add(new ShareShortcutInfo(
                new ShortcutInfo.Builder(context, "shortcut3")
                        .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2  0
                new ComponentName("package3", "class3")));
        shortcuts.add(new ShareShortcutInfo(
                new ShortcutInfo.Builder(context, "shortcut4")
                        .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3  2
                new ComponentName("package4", "class4")));

        return shortcuts;
    }

    private void assertCorrectShortcutToChooserTargetConversion(List<ShareShortcutInfo> shortcuts,
            List<ChooserTarget> chooserTargets, int[] expectedOrder, float[] expectedScores) {
        assertEquals(expectedOrder.length, chooserTargets.size());
        for (int i = 0; i < chooserTargets.size(); i++) {
            ChooserTarget ct = chooserTargets.get(i);
            ShortcutInfo si = shortcuts.get(expectedOrder[i]).getShortcutInfo();
            ComponentName cn = shortcuts.get(expectedOrder[i]).getTargetComponent();

            assertEquals(si.getId(), ct.getIntentExtras().getString(Intent.EXTRA_SHORTCUT_ID));
            assertEquals(si.getShortLabel(), ct.getTitle());
            assertThat(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001, is(true));
            assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
        }
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -84,6 +85,14 @@ public class ChooserWrapperActivity extends ChooserActivity {
        return super.getPackageManager();
    }

    @Override
    public Resources getResources() {
        if (sOverrides.resources != null) {
            return sOverrides.resources;
        }
        return super.getResources();
    }

    @Override
    protected Bitmap loadThumbnail(Uri uri, Size size) {
        if (sOverrides.previewThumbnail != null) {
@@ -145,6 +154,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
        public Bitmap previewThumbnail;
        public MetricsLogger metricsLogger;
        public int alternateProfileSetting;
        public Resources resources;

        public void reset() {
            onSafelyStartCallback = null;
@@ -157,6 +167,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
            resolverListController = mock(ResolverListController.class);
            metricsLogger = mock(MetricsLogger.class);
            alternateProfileSetting = 0;
            resources = null;
        }
    }
}