summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java110
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java144
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java11
3 files changed, 233 insertions, 32 deletions
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 436897848f55..cae1f3831b4a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -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();
- 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);
+ /**
+ * 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());
+ 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);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 67036fef30ba..c44b7d81868d 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -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());
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 44e56eaf0593..1d567c73f376 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -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;
@@ -85,6 +86,14 @@ public class ChooserWrapperActivity extends ChooserActivity {
}
@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) {
return sOverrides.previewThumbnail;
@@ -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;
}
}
}