Save and restore ShortcutInfoCompat#rank
Also converts ShortcutInfoCompat#rank to ChooserTarget#score in
backwards compatibility mode.
Bug: 140580980
Test: ChooserTargetServiceCompatTest.java
Test: ShortcutInfoCompatSaverTest.java
Test: ShareTarget test app
Change-Id: I1ae5f2f59aec08ed9028f105cb66aedd4a09e07c
diff --git a/sharetarget/build.gradle b/sharetarget/build.gradle
index 6961d0f..6579f81 100644
--- a/sharetarget/build.gradle
+++ b/sharetarget/build.gradle
@@ -25,8 +25,8 @@
}
dependencies {
- api("androidx.core:core:1.1.0")
- implementation("androidx.collection:collection:1.0.0")
+ api("androidx.core:core:1.2.0-beta01")
+ api("androidx.collection:collection:1.0.0")
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/sharetarget/integration-tests/testapp/build.gradle b/sharetarget/integration-tests/testapp/build.gradle
index dc418e8..bf9f767 100644
--- a/sharetarget/integration-tests/testapp/build.gradle
+++ b/sharetarget/integration-tests/testapp/build.gradle
@@ -22,8 +22,8 @@
}
dependencies {
- api("androidx.core:core:1.1.0")
- api("androidx.sharetarget:sharetarget:1.0.0-alpha02")
+ api("androidx.core:core:1.2.0-beta01")
+ api(project(":sharetarget"))
api("androidx.appcompat:appcompat:1.1.0")
api(CONSTRAINT_LAYOUT, { transitive = true })
}
diff --git a/sharetarget/integration-tests/testapp/src/main/java/androidx/sharetarget/testapp/MainActivity.java b/sharetarget/integration-tests/testapp/src/main/java/androidx/sharetarget/testapp/MainActivity.java
index 5406c6d..10afb4c 100644
--- a/sharetarget/integration-tests/testapp/src/main/java/androidx/sharetarget/testapp/MainActivity.java
+++ b/sharetarget/integration-tests/testapp/src/main/java/androidx/sharetarget/testapp/MainActivity.java
@@ -82,6 +82,7 @@
.setLongLived()
.setPerson(new Person.Builder().build())
.setCategories(categories1)
+ .setRank(2)
.build());
shortcuts.add(new ShortcutInfoCompat.Builder(this, "Person_Two_ID")
.setShortLabel("Person_Two")
@@ -89,7 +90,8 @@
.setIntent(intent)
.setLongLived()
.setPerson(new Person.Builder().build())
- .setCategories(categories2)
+ .setCategories(categories1)
+ .setRank(1)
.build());
ShortcutManagerCompat.addDynamicShortcuts(this, shortcuts);
diff --git a/sharetarget/src/androidTest/java/androidx/sharetarget/ChooserTargetServiceCompatTest.java b/sharetarget/src/androidTest/java/androidx/sharetarget/ChooserTargetServiceCompatTest.java
new file mode 100644
index 0000000..e4d5212
--- /dev/null
+++ b/sharetarget/src/androidTest/java/androidx/sharetarget/ChooserTargetServiceCompatTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sharetarget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.service.chooser.ChooserTarget;
+
+import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.sharetarget.ChooserTargetServiceCompat.ShortcutHolder;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ChooserTargetServiceCompatTest {
+ private Context mContext;
+
+ private IconCompat mTestIcon;
+ private Intent mTestIntent;
+ private ShortcutInfoCompatSaverImpl mShortcutSaver;
+
+ @Before
+ public void setup() throws Exception {
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mTestIntent = new Intent("TestIntent");
+ mTestIcon = IconCompat.createWithResource(mContext,
+ androidx.sharetarget.test.R.drawable.bmp_test);
+ mShortcutSaver = mock(ShortcutInfoCompatSaverImpl.class);
+ when(mShortcutSaver.getShortcutIcon(any())).thenReturn(mTestIcon);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ public void testConvertShortcutstoChooserTargets() {
+ ArrayList<ShortcutHolder> testShortcuts = new ArrayList<>();
+ testShortcuts.add(new ShortcutHolder(
+ new ShortcutInfoCompat.Builder(mContext, "shortcut1")
+ .setIntent(mTestIntent).setShortLabel("label1").setRank(3).build(),
+ new ComponentName("package1", "class1")));
+ testShortcuts.add(new ShortcutHolder(
+ new ShortcutInfoCompat.Builder(mContext, "shortcut2")
+ .setIntent(mTestIntent).setShortLabel("label2").setRank(7).build(),
+ new ComponentName("package2", "class2")));
+ testShortcuts.add(new ShortcutHolder(
+ new ShortcutInfoCompat.Builder(mContext, "shortcut3")
+ .setIntent(mTestIntent).setShortLabel("label3").setRank(1).build(),
+ new ComponentName("package3", "class3")));
+ testShortcuts.add(new ShortcutHolder(
+ new ShortcutInfoCompat.Builder(mContext, "shortcut4")
+ .setIntent(mTestIntent).setShortLabel("label4").setRank(3).build(),
+ new ComponentName("package4", "class4")));
+
+ // Need to clone to keep the original order for testing.
+ ArrayList<ShortcutHolder> clonedList = (ArrayList<ShortcutHolder>) testShortcuts.clone();
+ List<ChooserTarget> chooserTargets =
+ ChooserTargetServiceCompat.convertShortcutsToChooserTargets(
+ mShortcutSaver, clonedList);
+
+ int[] expectedOrder = {2, 0, 3, 1};
+ float[] expectedScores = {1.0f, 1.0f - 0.01f, 1.0f - 0.01f, 1.0f - 0.02f};
+
+ assertEquals(testShortcuts.size(), chooserTargets.size());
+ for (int i = 0; i < chooserTargets.size(); i++) {
+ ChooserTarget ct = chooserTargets.get(i);
+ ShortcutInfoCompat si = testShortcuts.get(expectedOrder[i]).getShortcut();
+ ComponentName cn = testShortcuts.get(expectedOrder[i]).getTargetClass();
+
+ assertEquals(si.getId(), ct.getIntentExtras().getString(
+ ShortcutManagerCompat.EXTRA_SHORTCUT_ID));
+ assertEquals(si.getShortLabel(), ct.getTitle());
+ assertTrue(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001);
+ assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
+ }
+ }
+}
diff --git a/sharetarget/src/androidTest/java/androidx/sharetarget/ShortcutInfoCompatSaverTest.java b/sharetarget/src/androidTest/java/androidx/sharetarget/ShortcutInfoCompatSaverTest.java
index c020a09..f3f5672 100644
--- a/sharetarget/src/androidTest/java/androidx/sharetarget/ShortcutInfoCompatSaverTest.java
+++ b/sharetarget/src/androidTest/java/androidx/sharetarget/ShortcutInfoCompatSaverTest.java
@@ -30,6 +30,7 @@
import android.graphics.Color;
import android.graphics.drawable.Icon;
+import androidx.annotation.NonNull;
import androidx.core.app.Person;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutInfoCompatSaver;
@@ -106,13 +107,14 @@
.setShortLabel("test short label 1")
.setLongLabel("test long label 1")
.setDisabledMessage("test disabled message 1")
- .setLongLived()
+ .setLongLived(true)
.setPersons(testPersons)
.setCategories(testCategories)
.setActivity(new ComponentName("test package", "test class"))
.setAlwaysBadged()
.setIcon(mTestResourceIcon)
.setIntents(testIntents)
+ .setRank(3)
.build());
mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, ID_SHORTCUT_BITMAP_ICON)
.setShortLabel("test short label 2")
@@ -138,6 +140,7 @@
.setPerson(testPersons[0])
.setCategories(testCategories)
.setIntents(testIntents)
+ .setRank(8)
.build());
mTestShortcuts.add(new ShortcutInfoCompat.Builder(mContext, "shortcut-no-category")
.setShortLabel("test short label 5")
@@ -194,6 +197,7 @@
assertEquals(expected.getDisabledMessage(), actual.getDisabledMessage());
assertEquals(expected.getLongLabel(), actual.getLongLabel());
assertEquals(expected.getShortLabel(), actual.getShortLabel());
+ assertEquals(expected.getRank(), actual.getRank());
if (expected.getActivity() == null) {
assertNull(actual.getActivity());
@@ -231,7 +235,8 @@
@Test
public void testGetInstance() {
- ShortcutInfoCompatSaver saver = ShortcutInfoCompatSaverImpl.getInstance(mContext);
+ ShortcutInfoCompatSaver<ListenableFuture<Void>> saver =
+ ShortcutInfoCompatSaverImpl.getInstance(mContext);
assertNotNull(saver);
assertEquals(saver, ShortcutInfoCompatSaverImpl.getInstance(mContext));
}
@@ -410,7 +415,7 @@
}
}, new Executor() {
@Override
- public void execute(Runnable command) {
+ public void execute(@NonNull Runnable command) {
// Run in the current thread
command.run();
}
diff --git a/sharetarget/src/main/java/androidx/sharetarget/ChooserTargetServiceCompat.java b/sharetarget/src/main/java/androidx/sharetarget/ChooserTargetServiceCompat.java
index f713377..90b91d1 100644
--- a/sharetarget/src/main/java/androidx/sharetarget/ChooserTargetServiceCompat.java
+++ b/sharetarget/src/main/java/androidx/sharetarget/ChooserTargetServiceCompat.java
@@ -26,14 +26,17 @@
import android.service.chooser.ChooserTargetService;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -53,7 +56,6 @@
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName,
IntentFilter matchedFilter) {
Context context = getApplicationContext();
- ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
// Retrieve share targets
List<ShareTargetCompat> targets = ShareTargetXmlParser.getShareTargets(context);
@@ -71,59 +73,99 @@
}
}
if (matchedTargets.isEmpty()) {
- return chooserTargets;
+ return Collections.emptyList();
}
// Retrieve shortcuts
- ShortcutInfoCompatSaverImpl shortcutSaver = ShortcutInfoCompatSaverImpl.getInstance(
- context);
+ ShortcutInfoCompatSaverImpl shortcutSaver =
+ ShortcutInfoCompatSaverImpl.getInstance(context);
List<ShortcutInfoCompat> shortcuts;
try {
shortcuts = shortcutSaver.getShortcuts();
} catch (Exception e) {
Log.e(TAG, "Failed to retrieve shortcuts: ", e);
- return chooserTargets;
+ return Collections.emptyList();
}
if (shortcuts == null || shortcuts.isEmpty()) {
- return chooserTargets;
+ return Collections.emptyList();
}
+ // List of matched shortcuts with their target component names
+ List<ShortcutHolder> matchedShortcuts = new ArrayList<>();
for (ShortcutInfoCompat shortcut : shortcuts) {
- ShareTargetCompat target = null;
for (ShareTargetCompat item : matchedTargets) {
- // Shortcut must have all share target categories (AND operation)
+ // Shortcut must have all the share target's categories (AND operation)
if (shortcut.getCategories().containsAll(Arrays.asList(item.mCategories))) {
- target = item;
+ matchedShortcuts.add(new ShortcutHolder(shortcut,
+ new ComponentName(context.getPackageName(), item.mTargetClass)));
break;
}
}
- if (target == null) {
- continue;
- }
+ }
+ return convertShortcutsToChooserTargets(shortcutSaver, matchedShortcuts);
+ }
- IconCompat icon;
+ @VisibleForTesting
+ @NonNull
+ static List<ChooserTarget> convertShortcutsToChooserTargets(
+ @NonNull ShortcutInfoCompatSaverImpl shortcutSaver,
+ @NonNull List<ShortcutHolder> matchedShortcuts) {
+ if (matchedShortcuts.isEmpty()) {
+ return new ArrayList<>();
+ }
+ Collections.sort(matchedShortcuts);
+
+ ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
+ float currentScore = 1.0f;
+ int lastRank = matchedShortcuts.get(0).getShortcut().getRank();
+ for (ShortcutHolder holder : matchedShortcuts) {
+ final ShortcutInfoCompat shortcut = holder.getShortcut();
+ IconCompat shortcutIcon;
try {
- icon = shortcutSaver.getShortcutIcon(shortcut.getId());
+ shortcutIcon = shortcutSaver.getShortcutIcon(shortcut.getId());
} catch (Exception e) {
Log.e(TAG, "Failed to retrieve shortcut icon: ", e);
- continue;
+ shortcutIcon = null;
}
+
Bundle extras = new Bundle();
extras.putString(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, shortcut.getId());
+
+ if (lastRank != shortcut.getRank()) {
+ currentScore -= 0.01f;
+ lastRank = shortcut.getRank();
+ }
chooserTargets.add(new ChooserTarget(
- // The name of this target.
shortcut.getShortLabel(),
- // The icon to represent this target.
- icon != null ? icon.toIcon() : 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.
- 0.5f,
- // The name of the component to be launched if this target is chosen.
- new ComponentName(context.getPackageName(), target.mTargetClass),
- // The extra values here will be merged into the Intent when this target is
- // chosen.
+ shortcutIcon == null ? null : shortcutIcon.toIcon(),
+ currentScore,
+ holder.getTargetClass(),
extras));
}
+
return chooserTargets;
}
+
+ static class ShortcutHolder implements Comparable<ShortcutHolder> {
+ private final ShortcutInfoCompat mShortcut;
+ private final ComponentName mTargetClass;
+
+ ShortcutHolder(ShortcutInfoCompat shortcut, ComponentName targetClass) {
+ mShortcut = shortcut;
+ mTargetClass = targetClass;
+ }
+
+ ShortcutInfoCompat getShortcut() {
+ return mShortcut;
+ }
+
+ ComponentName getTargetClass() {
+ return mTargetClass;
+ }
+
+ @Override
+ public int compareTo(ShortcutHolder other) {
+ return this.getShortcut().getRank() - other.getShortcut().getRank();
+ }
+ }
}
diff --git a/sharetarget/src/main/java/androidx/sharetarget/ShortcutsInfoSerialization.java b/sharetarget/src/main/java/androidx/sharetarget/ShortcutsInfoSerialization.java
index 97f9145..ef2ebc5 100644
--- a/sharetarget/src/main/java/androidx/sharetarget/ShortcutsInfoSerialization.java
+++ b/sharetarget/src/main/java/androidx/sharetarget/ShortcutsInfoSerialization.java
@@ -56,6 +56,7 @@
private static final String ATTR_SHORT_LABEL = "short_label";
private static final String ATTR_LONG_LABEL = "long_label";
private static final String ATTR_DISABLED_MSG = "disabled_message";
+ private static final String ATTR_RANK = "rank";
private static final String ATTR_ICON_RES_NAME = "icon_resource_name";
private static final String ATTR_ICON_BMP_PATH = "icon_bitmap_path";
@@ -157,6 +158,7 @@
return null;
}
+ int rank = Integer.parseInt(getAttributeValue(parser, ATTR_RANK));
CharSequence longLabel = getAttributeValue(parser, ATTR_LONG_LABEL);
CharSequence disabledMessage = getAttributeValue(parser, ATTR_DISABLED_MSG);
ComponentName activity = parseComponentName(parser);
@@ -188,7 +190,8 @@
}
ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(context, id)
- .setShortLabel(label);
+ .setShortLabel(label)
+ .setRank(rank);
if (!TextUtils.isEmpty(longLabel)) {
builder.setLongLabel(longLabel);
}
@@ -248,6 +251,7 @@
ShortcutInfoCompat shortcut = container.mShortcutInfo;
serializeAttribute(serializer, ATTR_ID, shortcut.getId());
serializeAttribute(serializer, ATTR_SHORT_LABEL, shortcut.getShortLabel().toString());
+ serializeAttribute(serializer, ATTR_RANK, Integer.toString(shortcut.getRank()));
if (!TextUtils.isEmpty(shortcut.getLongLabel())) {
serializeAttribute(serializer, ATTR_LONG_LABEL, shortcut.getLongLabel().toString());
}