diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 094b89d4a4467..896fa43cd11b1 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -62,6 +62,13 @@ public final class ShortcutInfo implements Parcelable {
private static final String ANDROID_PACKAGE_NAME = "android";
+ private static final int IMPLICIT_RANK_MASK = 0x7fffffff;
+
+ private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK;
+
+ /** @hide */
+ public static final int RANK_NOT_SET = Integer.MAX_VALUE;
+
/** @hide */
public static final int FLAG_DYNAMIC = 1 << 0;
@@ -193,6 +200,15 @@ public final class ShortcutInfo implements Parcelable {
private int mRank;
+ /**
+ * Internally used for auto-rank-adjustment.
+ *
+ * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing.
+ * The rest of the bits are used to denote the order in which shortcuts are passed to
+ * APIs, which is used to preserve the argument order when ranks are tie.
+ */
+ private int mImplicitRank;
+
@Nullable
private PersistableBundle mExtras;
@@ -544,7 +560,8 @@ public final class ShortcutInfo implements Parcelable {
/**
* Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
- * will be overwritten. The timestamp will be updated.
+ * will be overwritten. The timestamp will *not* be updated to be consistent with other
+ * setters (and also the clock is not injectable in this file).
*
* - Flags will not change
* - mBitmapPath will not change
@@ -603,14 +620,12 @@ public final class ShortcutInfo implements Parcelable {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
- if (source.mRank != 0) {
+ if (source.mRank != RANK_NOT_SET) {
mRank = source.mRank;
}
if (source.mExtras != null) {
mExtras = source.mExtras;
}
-
- updateTimestamp();
}
/**
@@ -665,7 +680,7 @@ public final class ShortcutInfo implements Parcelable {
private Intent mIntent;
- private int mRank;
+ private int mRank = RANK_NOT_SET;
private PersistableBundle mExtras;
@@ -825,15 +840,18 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
- Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set.");
+ Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
return this;
}
/**
- * TODO javadoc.
+ * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app
+ * to sort shortcuts.
*/
@NonNull
public Builder setRank(int rank) {
+ Preconditions.checkArgument((0 <= rank),
+ "Rank cannot be negative or bigger than MAX_RANK");
mRank = rank;
return this;
}
@@ -1014,12 +1032,57 @@ public final class ShortcutInfo implements Parcelable {
}
/**
- * TODO Javadoc
+ * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
+ * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts.
+ *
+ *
Because manifest shortcuts and dynamic shortcuts have overlapping ranks,
+ * when a launcher application shows shortcuts for an activity, it should first show
+ * the manifest shortcuts followed by the dynamic shortcuts. Within each of those categories,
+ * shortcuts should be sorted by rank in ascending order.
+ *
+ *
"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all
+ * have rank 0, because there's no sorting for them.
*/
public int getRank() {
return mRank;
}
+ /** @hide */
+ public boolean hasRank() {
+ return mRank != RANK_NOT_SET;
+ }
+
+ /** @hide */
+ public void setRank(int rank) {
+ mRank = rank;
+ }
+
+ /** @hide */
+ public void clearImplicitRankAndRankChangedFlag() {
+ mImplicitRank = 0;
+ }
+
+ /** @hide */
+ public void setImplicitRank(int rank) {
+ // Make sure to keep RANK_CHANGED_BIT.
+ mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK);
+ }
+
+ /** @hide */
+ public int getImplicitRank() {
+ return mImplicitRank & IMPLICIT_RANK_MASK;
+ }
+
+ /** @hide */
+ public void setRankChanged() {
+ mImplicitRank |= RANK_CHANGED_BIT;
+ }
+
+ /** @hide */
+ public boolean isRankChanged() {
+ return (mImplicitRank & RANK_CHANGED_BIT) != 0;
+ }
+
/**
* Optional values that application can set.
*/
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 934545ad24618..ace14acda16df 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -19,10 +19,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.os.PersistableBundle;
@@ -51,8 +49,6 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
-import sun.misc.Resource;
-
/**
* Package information used by {@link ShortcutService}.
* User information used by {@link ShortcutService}.
@@ -63,6 +59,7 @@ import sun.misc.Resource;
*/
class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
+ private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
static final String TAG_ROOT = "package";
private static final String TAG_INTENT_EXTRAS = "intent-extras";
@@ -303,12 +300,17 @@ class ShortcutPackage extends ShortcutPackageItem {
* Remove all dynamic shortcuts.
*/
public void deleteAllDynamicShortcuts() {
+ final long now = mShortcutUser.mService.injectCurrentTimeMillis();
+
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic()) {
changed = true;
+
+ si.setTimestamp(now);
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
}
}
if (changed) {
@@ -356,10 +358,14 @@ class ShortcutPackage extends ShortcutPackageItem {
ensureNotImmutable(oldShortcut);
}
if (oldShortcut.isPinned()) {
+
+ oldShortcut.setRank(0);
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
}
+ oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
+
return oldShortcut;
} else {
deleteShortcutInner(shortcutId);
@@ -829,7 +835,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (!a.isManifestShortcut() && b.isManifestShortcut()) {
return 1;
}
- return a.getRank() - b.getRank();
+ return Integer.compare(a.getRank(), b.getRank());
};
/**
@@ -837,8 +843,6 @@ class ShortcutPackage extends ShortcutPackageItem {
* contain "floating" shortcuts because they don't belong on any activities.
*/
private ArrayMap> sortShortcutsToActivities() {
- final int maxShortcuts = mShortcutUser.mService.getMaxActivityShortcuts();
-
final ArrayMap> activitiesToShortcuts
= new ArrayMap<>();
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
@@ -851,7 +855,7 @@ class ShortcutPackage extends ShortcutPackageItem {
ArrayList list = activitiesToShortcuts.get(activity);
if (list == null) {
- list = new ArrayList<>(maxShortcuts * 2);
+ list = new ArrayList<>();
activitiesToShortcuts.put(activity, list);
}
list.add(si);
@@ -976,6 +980,96 @@ class ShortcutPackage extends ShortcutPackageItem {
}
}
+ /** Clears the implicit ranks for all shortcuts. */
+ public void clearAllImplicitRanks() {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ si.clearImplicitRankAndRankChangedFlag();
+ }
+ }
+
+ /**
+ * Used to sort shortcuts for rank auto-adjusting.
+ */
+ final Comparator mShortcutRankComparator = (ShortcutInfo a, ShortcutInfo b) -> {
+ // First, sort by rank.
+ int ret = Integer.compare(a.getRank(), b.getRank());
+ if (ret != 0) {
+ return ret;
+ }
+ // When ranks are tie, then prioritize the ones that have just been assigned new ranks.
+ // e.g. when there are 3 shortcuts, "s1" "s2" and "s3" with rank 0, 1, 2 respectively,
+ // adding a shortcut "s4" with rank 1 will "insert" it between "s1" and "s2", because
+ // "s2" and "s4" have the same rank 1 but s4 has isRankChanged() set.
+ // Similarly, updating s3's rank to 1 will insert it between s1 and s2.
+ if (a.isRankChanged() != b.isRankChanged()) {
+ return a.isRankChanged() ? -1 : 1;
+ }
+ // If they're still tie, sort by implicit rank -- i.e. preserve the order in which
+ // they're passed to the API.
+ ret = Integer.compare(a.getImplicitRank(), b.getImplicitRank());
+ if (ret != 0) {
+ return ret;
+ }
+ // If they're stil tie, just sort by their IDs.
+ // This may happen with updateShortcuts() -- see
+ // the testUpdateShortcuts_noManifestShortcuts() test.
+ return a.getId().compareTo(b.getId());
+ };
+
+ /**
+ * Re-calculate the ranks for all shortcuts.
+ */
+ public void adjustRanks() {
+ final ShortcutService s = mShortcutUser.mService;
+ final long now = s.injectCurrentTimeMillis();
+
+ // First, clear ranks for floating shortcuts.
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.isFloating()) {
+ if (si.getRank() != 0) {
+ si.setTimestamp(now);
+ si.setRank(0);
+ }
+ }
+ }
+
+ // Then adjust ranks. Ranks are unique for each activity, so we first need to sort
+ // shortcuts to each activity.
+ // Then sort the shortcuts within each activity with mShortcutRankComparator, and
+ // assign ranks from 0.
+ final ArrayMap> all =
+ sortShortcutsToActivities();
+ for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
+ final ArrayList list = all.valueAt(outer);
+
+ // Sort by ranks and other signals.
+ Collections.sort(list, mShortcutRankComparator);
+
+ int rank = 0;
+
+ final int size = list.size();
+ for (int i = 0; i < size; i++) {
+ final ShortcutInfo si = list.get(i);
+ if (si.isManifestShortcut()) {
+ // Don't adjust ranks for manifest shortcuts.
+ continue;
+ }
+ // At this point, it must be dynamic.
+ if (!si.isDynamic()) {
+ s.wtf("Non-dynamic shortcut found.");
+ continue;
+ }
+ final int thisRank = rank++;
+ if (si.getRank() != thisRank) {
+ si.setTimestamp(now);
+ si.setRank(thisRank);
+ }
+ }
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
@@ -1087,7 +1181,6 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
si.getDisabledMessageResName());
ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
- ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
@@ -1097,6 +1190,10 @@ class ShortcutPackage extends ShortcutPackageItem {
~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_DYNAMIC));
} else {
+ // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
+ // as dynamic.
+ ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
+
ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId());
ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName());
@@ -1272,35 +1369,74 @@ class ShortcutPackage extends ShortcutPackageItem {
sortShortcutsToActivities();
// Make sure each activity won't have more than max shortcuts.
- for (int i = all.size() - 1; i >= 0; i--) {
- if (all.valueAt(i).size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
+ for (int outer = all.size() - 1; outer >= 0; outer--) {
+ final ArrayList list = all.valueAt(outer);
+ if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
failed = true;
- Log.e(TAG, "Package " + getPackageName() + ": activity " + all.keyAt(i)
- + " has " + all.valueAt(i).size() + " shortcuts.");
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ + " has " + all.valueAt(outer).size() + " shortcuts.");
}
+
+ // Sort by rank.
+ Collections.sort(list, (a, b) -> Integer.compare(a.getRank(), b.getRank()));
+
+ // Split into two arrays for each kind.
+ final ArrayList dynamicList = new ArrayList<>(list);
+ dynamicList.removeIf((si) -> !si.isDynamic());
+
+ final ArrayList manifestList = new ArrayList<>(list);
+ dynamicList.removeIf((si) -> !si.isManifestShortcut());
+
+ verifyRanksSequential(dynamicList);
+ verifyRanksSequential(manifestList);
}
+ // Verify each shortcut's status.
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (!(si.isManifestShortcut() || si.isDynamic() || si.isPinned())) {
failed = true;
- Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
+ if (si.isManifestShortcut() && si.isDynamic()) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " is both dynamic and manifest at the same time.");
+ }
if (si.getActivity() == null) {
failed = true;
- Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
failed = true;
- Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
+ if (si.isFloating() && si.getRank() != 0) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " is floating, but has rank=" + si.getRank());
+ }
}
if (failed) {
throw new IllegalStateException("See logcat for errors");
}
}
+
+ private boolean verifyRanksSequential(List list) {
+ boolean failed = false;
+
+ for (int i = 0; i < list.size(); i++) {
+ final ShortcutInfo si = list.get(i);
+ if (si.getRank() != i) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " rank=" + si.getRank() + " but expected to be "+ i);
+ }
+ }
+ return failed;
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index 470d4afe64448..ec19927ad75a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -103,7 +103,7 @@ public class ShortcutParser {
}
if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
final ShortcutInfo si = parseShortcutAttributes(
- service, attrs, packageName, activity, userId, rank++);
+ service, attrs, packageName, activity, userId, rank);
if (ShortcutService.DEBUG) {
Slog.d(TAG, "Shortcut=" + si);
}
@@ -128,6 +128,7 @@ public class ShortcutParser {
}
result.add(si);
numShortcuts++;
+ rank++;
}
continue;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 57694028d86dc..16191687770c4 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -114,7 +114,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -223,7 +222,7 @@ public class ShortcutService extends IShortcutService.Stub {
String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
/**
- * Key name for the max dynamic shortcuts per app. (int)
+ * Key name for the max dynamic shortcuts per activity. (int)
*/
String KEY_MAX_SHORTCUTS = "max_shortcuts";
@@ -1479,6 +1478,12 @@ public class ShortcutService extends IShortcutService.Stub {
return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
}
+ private void assignImplicitRanks(List shortcuts) {
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ shortcuts.get(i).setImplicitRank(i);
+ }
+ }
+
// === APIs ===
@Override
@@ -1501,10 +1506,9 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
- // Validate the shortcuts.
- for (int i = 0; i < size; i++) {
- fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
- }
+ // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+ ps.clearAllImplicitRanks();
+ assignImplicitRanks(newShortcuts);
// First, remove all un-pinned; dynamic shortcuts
ps.deleteAllDynamicShortcuts();
@@ -1514,6 +1518,9 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutInfo newShortcut = newShortcuts.get(i);
ps.addOrUpdateDynamicShortcut(newShortcut);
}
+
+ // Lastly, adjust the ranks.
+ ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1542,41 +1549,58 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
+ // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+ ps.clearAllImplicitRanks();
+ assignImplicitRanks(newShortcuts);
+
for (int i = 0; i < size; i++) {
final ShortcutInfo source = newShortcuts.get(i);
fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
final ShortcutInfo target = ps.findShortcutById(source.getId());
- if (target != null) {
- if (target.isEnabled() != source.isEnabled()) {
- Slog.w(TAG,
- "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
- }
+ if (target == null) {
+ continue;
+ }
- final boolean replacingIcon = (source.getIcon() != null);
- if (replacingIcon) {
- removeIcon(userId, target);
- }
+ if (target.isEnabled() != source.isEnabled()) {
+ Slog.w(TAG,
+ "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
+ }
- if (source.getActivity() != null &&
- !source.getActivity().equals(target.getActivity())) {
- // TODO When activity is changing, check the dynamic count.
- }
+ // When updating the rank, we need to insert between existing ranks, so set
+ // this setRankChanged, and also copy the implicit rank fo adjustRanks().
+ if (source.hasRank()) {
+ target.setRankChanged();
+ target.setImplicitRank(source.getImplicitRank());
+ }
- // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
- target.copyNonNullFieldsFrom(source);
+ final boolean replacingIcon = (source.getIcon() != null);
+ if (replacingIcon) {
+ removeIcon(userId, target);
+ }
- if (replacingIcon) {
- saveIconAndFixUpShortcut(userId, target);
- }
+ if (source.getActivity() != null &&
+ !source.getActivity().equals(target.getActivity())) {
+ // TODO When activity is changing, check the dynamic count.
+ }
- // When we're updating any resource related fields, re-extract the res names and
- // the values.
- if (replacingIcon || source.hasStringResources()) {
- fixUpShortcutResourceNamesAndValues(target);
- }
+ // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
+ target.copyNonNullFieldsFrom(source);
+ target.setTimestamp(injectCurrentTimeMillis());
+
+ if (replacingIcon) {
+ saveIconAndFixUpShortcut(userId, target);
+ }
+
+ // When we're updating any resource related fields, re-extract the res names and
+ // the values.
+ if (replacingIcon || source.hasStringResources()) {
+ fixUpShortcutResourceNamesAndValues(target);
}
}
+
+ // Lastly, adjust the ranks.
+ ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1600,6 +1624,10 @@ public class ShortcutService extends IShortcutService.Stub {
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
+ // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
+ ps.clearAllImplicitRanks();
+ assignImplicitRanks(newShortcuts);
+
// Throttling.
if (!ps.tryApiCall()) {
return false;
@@ -1610,9 +1638,16 @@ public class ShortcutService extends IShortcutService.Stub {
// Validate the shortcut.
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
+ // When ranks are changing, we need to insert between ranks, so set the
+ // "rank changed" flag.
+ newShortcut.setRankChanged();
+
// Add it.
ps.addOrUpdateDynamicShortcut(newShortcut);
}
+
+ // Lastly, adjust the ranks.
+ ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1637,6 +1672,9 @@ public class ShortcutService extends IShortcutService.Stub {
disabledMessage, disabledMessageResId,
/* overrideImmutable=*/ false);
}
+
+ // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
+ ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1677,6 +1715,9 @@ public class ShortcutService extends IShortcutService.Stub {
ps.deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
+
+ // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
+ ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -2328,6 +2369,7 @@ public class ShortcutService extends IShortcutService.Stub {
} finally {
logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
}
+ verifyStates();
}
private void handlePackageAdded(String packageName, @UserIdInt int userId) {
@@ -2339,6 +2381,7 @@ public class ShortcutService extends IShortcutService.Stub {
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.handlePackageAddedOrUpdated(packageName);
}
+ verifyStates();
}
private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
@@ -2354,6 +2397,7 @@ public class ShortcutService extends IShortcutService.Stub {
user.handlePackageAddedOrUpdated(packageName);
}
}
+ verifyStates();
}
private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
@@ -2362,6 +2406,8 @@ public class ShortcutService extends IShortcutService.Stub {
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
+
+ verifyStates();
}
private void handlePackageDataCleared(String packageName, int packageUserId) {
@@ -2370,6 +2416,8 @@ public class ShortcutService extends IShortcutService.Stub {
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
+
+ verifyStates();
}
// === PackageManager interaction ===
diff --git a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
new file mode 100644
index 0000000000000..3d6eb222ce5b4
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 2f11967cdb020..69152d405ac05 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -742,6 +742,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
if (mService != null) {
// Flush all the unsaved data from the previous instance.
mService.saveDirtyInfo();
+
+ // Make sure everything is consistent.
+ mService.verifyStates();
}
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index fe2d1eca1cf1f..fc1ebbec86116 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -45,6 +45,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.filterByActivity;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
@@ -71,7 +72,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -79,7 +79,6 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
-import android.os.Process;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -969,23 +968,34 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Set up shortcuts.
setCaller(CALLING_PACKAGE_1);
- final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 5000);
- final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 1000);
+ final ShortcutInfo s1_1 = makeShortcut("s1");
+ final ShortcutInfo s1_2 = makeShortcut("s2");
assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+ // Because setDynamicShortcuts will update the timestamps when ranks are changing,
+ // we explicitly set timestamps here.
+ getCallerShortcut("s1").setTimestamp(5000);
+ getCallerShortcut("s2").setTimestamp(1000);
+
setCaller(CALLING_PACKAGE_2);
- final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
- final ShortcutInfo s2_3 = makeShortcutWithTimestampWithActivity("s3", 3000,
+ final ShortcutInfo s2_2 = makeShortcut("s2");
+ final ShortcutInfo s2_3 = makeShortcutWithActivity("s3",
makeComponent(ShortcutActivity2.class));
- final ShortcutInfo s2_4 = makeShortcutWithTimestampWithActivity("s4", 500,
+ final ShortcutInfo s2_4 = makeShortcutWithActivity("s4",
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
+ getCallerShortcut("s2").setTimestamp(1500);
+ getCallerShortcut("s3").setTimestamp(3000);
+ getCallerShortcut("s4").setTimestamp(500);
+
setCaller(CALLING_PACKAGE_3);
- final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000);
+ final ShortcutInfo s3_2 = makeShortcut("s3");
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
+ getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+
setCaller(LAUNCHER_1);
// Get dynamic
@@ -4606,7 +4616,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_0));
}
-
public void testManifestShortcut_publishOnBroadcast() {
// First, no packages are installed.
uninstallPackage(USER_0, CALLING_PACKAGE_1);
@@ -4677,6 +4686,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2", "ms3", "ms4", "ms5");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1", "ms2", "ms3", "ms4", "ms5");
assertEmpty(mManager.getPinnedShortcuts());
});
@@ -4710,6 +4723,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1", "ms2");
assertShortcutIds(assertAllImmutable(assertAllPinned(
mManager.getPinnedShortcuts())),
"ms2", "ms3");
@@ -4733,6 +4750,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1", "ms2");
assertEmpty(mManager.getPinnedShortcuts());
});
@@ -4741,6 +4762,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1", "ms2");
assertShortcutIds(assertAllImmutable(assertAllPinned(
mManager.getPinnedShortcuts())),
"ms2", "ms3");
@@ -4749,10 +4774,43 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertAllDisabled(list(getCallerShortcut("ms3")));
});
+ // Multiple activities.
+ // Add shortcuts on activity 2 for package 2.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5_alt);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
+ R.xml.shortcut_5_reverse);
+
+ updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5",
+ "ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+ // Make sure they have the correct ranks, regardless of their ID's alphabetical order.
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()))
+ .haveRanksInOrder("ms5", "ms4", "ms3", "ms2", "ms1");
+ });
+
// Package 2 now has no manifest shortcuts.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
R.xml.shortcut_0);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
+ R.xml.shortcut_0);
updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
@@ -5128,7 +5186,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// ShortcutActivity2 has two shortcuts, ms1 and ms2.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
- R.xml.shortcut_2);
+ R.xml.shortcut_5);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
@@ -5136,13 +5194,14 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
- "ms1", "ms2");
+ "ms1", "ms2", "ms3", "ms4", "ms5");
// ms1 should belong to ShortcutActivity.
ShortcutInfo si = getCallerShortcut("ms1");
assertEquals(R.string.shortcut_title1, si.getTitleResId());
assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
si.getActivity());
+ assertEquals(0, si.getRank());
// ms2 should belong to ShortcutActivity*2*.
si = getCallerShortcut("ms2");
@@ -5150,8 +5209,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
si.getActivity());
+ // Also check the ranks
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()))
+ .haveRanksInOrder("ms1");
+ assertWith(getCallerShortcuts()).selectManifest()
+ .selectByActivity(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()))
+ .haveRanksInOrder("ms2", "ms3", "ms4", "ms5");
+
// Make sure there's no other dangling shortcuts.
- assertShortcutIds(getCallerShortcuts(), "ms1", "ms2");
+ assertShortcutIds(getCallerShortcuts(), "ms1", "ms2", "ms3", "ms4", "ms5");
});
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index ea44462c94f34..8e32d6a6afb2b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -533,6 +533,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
.setActivity(new ComponentName("x", "y")).build());
assertEquals("text", si.getText());
+ assertEquals(123, si.getRank());
assertEquals(new ComponentName("x", "y"), si.getActivity());
si = sorig.clone(/* flags=*/ 0);
@@ -627,16 +628,6 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb2).build());
assertEquals("text", si.getText());
assertEquals(99, si.getExtras().getInt("x"));
-
- // Make sure the timestamp gets updated too.
-
- final long timestamp = si.getLastChangedTimestamp();
- Thread.sleep(2);
-
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setTitle("xyz").build());
-
- assertTrue(si.getLastChangedTimestamp() > timestamp);
}
public void testShortcutInfoCopyNonNullFieldsFrom_resId() throws InterruptedException {
@@ -764,16 +755,6 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb2).build());
assertEquals(11, si.getTextResId());
assertEquals(99, si.getExtras().getInt("x"));
-
- // Make sure the timestamp gets updated too.
-
- final long timestamp = si.getLastChangedTimestamp();
- Thread.sleep(2);
-
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setTitle("xyz").build());
-
- assertTrue(si.getLastChangedTimestamp() > timestamp);
}
public void testShortcutInfoSaveAndLoad() throws InterruptedException {
@@ -797,7 +778,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
- mManager.addDynamicShortcuts(list(sorig));
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .build();
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
Thread.sleep(2);
final long now = System.currentTimeMillis();
@@ -822,7 +811,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getRank());
+ assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
@@ -830,6 +819,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertNotNull(si.getBitmapPath()); // Something should be set.
assertEquals(0, si.getIconResourceId());
assertTrue(si.getLastChangedTimestamp() < now);
+
+ // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
+ // to test it.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
+ assertEquals(1, si.getRank());
}
public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
@@ -852,7 +846,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
- mManager.addDynamicShortcuts(list(sorig));
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .build();
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
Thread.sleep(2);
final long now = System.currentTimeMillis();
@@ -880,7 +882,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getRank());
+ assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES
@@ -888,6 +890,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertNull(si.getBitmapPath());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
assertTrue(si.getLastChangedTimestamp() < now);
+
+ // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
+ // to test it.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
+ assertEquals(1, si.getRank());
}
public void testShortcutInfoSaveAndLoad_forBackup() {
@@ -911,11 +918,19 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
- mManager.addDynamicShortcuts(list(sorig));
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .build();
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
// Dynamic shortcuts won't be backed up, so we need to pin it.
setCaller(LAUNCHER_1, USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
// Do backup & restore.
backupAndRestore();
@@ -935,12 +950,16 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getRank());
+ assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
+
+ // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
+ assertEquals(0, si.getRank());
}
public void testShortcutInfoSaveAndLoad_forBackup_resId() {
@@ -963,11 +982,19 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
- mManager.addDynamicShortcuts(list(sorig));
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .build();
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
// Dynamic shortcuts won't be backed up, so we need to pin it.
setCaller(LAUNCHER_1, USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
// Do backup & restore.
backupAndRestore();
@@ -990,13 +1017,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getRank());
+ assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
+
+ // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
+ assertEquals(0, si.getRank());
}
public void testThrottling() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
new file mode 100644
index 0000000000000..eb4db7a0f4af5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.pm;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.ComponentName;
+import android.content.pm.ShortcutInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.servicestests.R;
+import com.android.server.pm.ShortcutService.ConfigConstants;
+
+/**
+ * Tests related to shortcut rank auto-adjustment.
+ */
+@SmallTest
+public class ShortcutManagerTest3 extends BaseShortcutManagerTest {
+
+ private static final String CALLING_PACKAGE = CALLING_PACKAGE_1;
+
+ private static final ComponentName A1 = new ComponentName(CALLING_PACKAGE,
+ ShortcutActivity.class.getName());
+
+ private static final ComponentName A2 = new ComponentName(CALLING_PACKAGE,
+ ShortcutActivity2.class.getName());
+
+ private static final ComponentName A3 = new ComponentName(CALLING_PACKAGE,
+ ShortcutActivity3.class.getName());
+
+ private ShortcutInfo shortcut(String id, ComponentName activity, int rank) {
+ return makeShortcutWithActivityAndRank(id, activity, rank);
+ }
+
+ private ShortcutInfo shortcut(String id, ComponentName activity) {
+ return makeShortcutWithActivityAndRank(id, activity, ShortcutInfo.RANK_NOT_SET);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // We don't need throttling during this test class, and also relax the max cap.
+ mService.updateConfigurationLocked(
+ ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
+ + ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
+ );
+
+ setCaller(CALLING_PACKAGE, USER_0);
+ }
+
+ private void publishManifestShortcuts(ComponentName activity, int resId) {
+ addManifestShortcutResource(activity, resId);
+ updatePackageVersion(CALLING_PACKAGE, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE, USER_0));
+ }
+
+ public void testSetDynamicShortcuts_noManifestShortcuts() {
+ mManager.setDynamicShortcuts(list(
+ shortcut("s1", A1)
+ ));
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s1");
+
+ assertTrue(mManager.setDynamicShortcuts(list(
+ shortcut("s5", A1),
+ shortcut("s4", A1),
+ shortcut("s3", A1)
+ )));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s4", "s3");
+
+ // RANK_NOT_SET is always the last.
+ assertTrue(mManager.setDynamicShortcuts(list(
+ shortcut("s5", A1),
+ shortcut("s4", A1, 5),
+ shortcut("s3", A1, 3),
+ shortcut("s2", A1)
+ )));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s3", "s4", "s5", "s2");
+
+ // Same rank, preserve the argument order.
+ assertTrue(mManager.setDynamicShortcuts(list(
+ shortcut("s5", A1, 5),
+ shortcut("s4", A1, 0),
+ shortcut("s3", A1, 5)
+ )));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s4", "s5", "s3");
+
+ // Multiple activities.
+ assertTrue(mManager.setDynamicShortcuts(list(
+ shortcut("s5", A1),
+ shortcut("s4", A2),
+ shortcut("s3", A3)
+ )));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5");
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("s4");
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
+ .haveRanksInOrder("s3");
+
+ assertTrue(mManager.setDynamicShortcuts(list(
+ shortcut("s5", A1, 5),
+ shortcut("s4", A1),
+ shortcut("s3", A1, 5),
+ shortcut("x5", A2, 5),
+ shortcut("x4", A2),
+ shortcut("x3", A2, 1)
+ )));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s4");
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x5", "x4");
+
+ // Clear. Make sure it wouldn't lead to invalid internals state.
+ // (ShortcutService.verifyStates() will do so internally.)
+ assertTrue(mManager.setDynamicShortcuts(list()));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1).isEmpty();
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2).isEmpty();
+ }
+
+ private void runTestWithManifestShortcuts(Runnable r) {
+ publishManifestShortcuts(A1, R.xml.shortcut_5_alt);
+ publishManifestShortcuts(A2, R.xml.shortcut_1);
+
+ assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
+ .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+ assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
+ .haveRanksInOrder("ms1");
+
+ // Existence of manifest shortcuts shouldn't affect dynamic shortcut ranks,
+ // so running another test here should pass.
+ r.run();
+
+ // And dynamic shortcut tests shouldn't affect manifest shortcuts, so repeat the
+ // same check.
+ assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
+ .haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
+
+ assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
+ .haveRanksInOrder("ms1");
+ }
+
+ public void testSetDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+ }
+
+ public void testAddDynamicShortcuts_noManifestShortcuts() {
+ mManager.addDynamicShortcuts(list(
+ shortcut("s1", A1)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s1");
+
+ //------------------------------------------------------
+ long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.addDynamicShortcuts(list(
+ shortcut("s5", A1, 0),
+ shortcut("s4", A1),
+ shortcut("s2", A1, 3),
+ shortcut("x1", A2),
+ shortcut("x3", A2, 2),
+ shortcut("x2", A2, 2),
+ shortcut("s3", A1, 0)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s1", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s5", "s3", "s1", "s2", "s4", "x3", "x2", "x1");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.addDynamicShortcuts(list(
+ shortcut("s1", A1, 1)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s1", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s1", "s3");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.addDynamicShortcuts(list(
+ shortcut("s1", A1, 1),
+
+ // This is add, not update, so the following means s5 will have NO_RANK,
+ // which puts it at the end.
+ shortcut("s5", A1),
+ shortcut("s3", A1, 0),
+
+ // s10 also has NO_RANK, so it'll be put at the end, even after "s5" as we preserve
+ // the argument order.
+ shortcut("s10", A1),
+
+ // Note we're changing the activity for x2.
+ shortcut("x2", A1, 0),
+ shortcut("x10", A2)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s3", "x2", "s1", "s2", "s4", "s5", "s10");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x1", "x10");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s3", "x2", "s1", "s5", "s10", "x1", "x10");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ // Change the activities again.
+ mManager.addDynamicShortcuts(list(
+ shortcut("s1", A2),
+ shortcut("s2", A2, 999)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s3", "x2", "s4", "s5", "s10");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x1", "x10", "s2", "s1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s1", "s2", "s4", "s5", "s10");
+ }
+
+ public void testAddDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+ }
+
+ public void testUpdateShortcuts_noManifestShortcuts() {
+ mManager.addDynamicShortcuts(list(
+ shortcut("s5", A1, 0),
+ shortcut("s4", A1),
+ shortcut("s2", A1, 3),
+ shortcut("x1", A2),
+ shortcut("x3", A2, 2),
+ shortcut("x2", A2, 2),
+ shortcut("s3", A1, 0)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ //------------------------------------------------------
+ long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.updateShortcuts(list());
+ // Same order.
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .isEmpty();
+
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
+ });
+ // Still same order.
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.updateShortcuts(list(
+ shortcut("s4", A1, 1),
+
+ // Rank not changing, should keep the same positions.
+ // c.f. in case of addDynamicShortcuts, this means "put them at the end".
+ shortcut("s3", A1),
+ shortcut("x2", A2)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s4", "s3", "s2");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s4", "s3", "s2", "x2");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.updateShortcuts(list(
+ shortcut("s4", A1, 0),
+
+ // Change the activity without specifying a rank -> keep the same rank.
+ shortcut("s5", A2),
+
+ // Change the activity without specifying a rank -> assign a new rank.
+ shortcut("x2", A1, 2),
+
+ // "xx" doesn't exist, so it'll be ignored.
+ shortcut("xx", A1, 0)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s4", "x2", "s3", "s2");
+
+ // Interesting case: both x3 and s5 originally had rank=0, and in this case s5 has moved
+ // to A2 without changing the rank. So they're tie for the new rank, as well as
+ // the "rank changed" bit. Also in this case, "s5" won't have an implicit order, since
+ // its rank isn't changing. So we sort them by ID, thus s5 comes before x3.
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("s5", "x3", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s4", "x2", "s5", "x3");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.updateShortcuts(list(
+ shortcut("s3", A3)));
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s4", "x2", "s2");
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("s5", "x3", "x1");
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
+ .haveRanksInOrder("s3");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s3", "s2");
+ }
+
+ public void testUpdateShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> testUpdateShortcuts_noManifestShortcuts());
+ }
+
+ public void testDeleteDynamicShortcuts_noManifestShortcuts() {
+ mManager.addDynamicShortcuts(list(
+ shortcut("s5", A1, 0),
+ shortcut("s4", A1),
+ shortcut("s2", A1, 3),
+ shortcut("x1", A2),
+ shortcut("x3", A2, 2),
+ shortcut("x2", A2, 2),
+ shortcut("s3", A1, 0)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ //------------------------------------------------------
+ long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.removeDynamicShortcuts(list());
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .isEmpty();
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(
+ CALLING_PACKAGE, list("s2", "s4", "x1", "x2"), HANDLE_USER_0);
+ });
+ // Still same order.
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.removeDynamicShortcuts(list("s3", "x1", "xxxx"));
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s2", "s4");
+ }
+
+ public void testDeleteDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> testDeleteDynamicShortcuts_noManifestShortcuts());
+ }
+
+ public void testDisableShortcuts_noManifestShortcuts() {
+ mManager.addDynamicShortcuts(list(
+ shortcut("s5", A1, 0),
+ shortcut("s4", A1),
+ shortcut("s2", A1, 3),
+ shortcut("x1", A2),
+ shortcut("x3", A2, 2),
+ shortcut("x2", A2, 2),
+ shortcut("s3", A1, 0)
+ ));
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ //------------------------------------------------------
+ long lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.disableShortcuts(list());
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s3", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2", "x1");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .isEmpty();
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.disableShortcuts(list("s3", "x1", "xxxx"));
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s2", "s4");
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
+ });
+ // Still same order.
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s2", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x3", "x2");
+
+ //------------------------------------------------------
+ lastApiTime = ++mInjectedCurrentTimeMillis;
+
+ mManager.disableShortcuts(list("s2", "x3"));
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
+ .haveRanksInOrder("s5", "s4");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
+ .haveRanksInOrder("x2");
+
+ assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
+ .haveIds("s4", "x2");
+ }
+
+ public void testDisableShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> testDisableShortcuts_noManifestShortcuts());
+ }
+
+}
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 04c7a042ef386..7ba4c6830a5c3 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -58,9 +58,13 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -72,10 +76,12 @@ import java.util.function.Predicate;
public class ShortcutManagerTestUtils {
private static final String TAG = "ShortcutManagerUtils";
- private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
+ private static final boolean ENABLE_DUMPSYS = false; // DO NOT SUBMIT WITH true
private static final int STANDARD_TIMEOUT_SEC = 5;
+ private static final String[] EMPTY_STRINGS = new String[0];
+
private ShortcutManagerTestUtils() {
}
@@ -228,7 +234,7 @@ public class ShortcutManagerTestUtils {
}
public static Set hashSet(Set in) {
- return new HashSet(in);
+ return new LinkedHashSet<>(in);
}
public static Set set(T... values) {
@@ -240,7 +246,7 @@ public class ShortcutManagerTestUtils {
}
public static Set set(Function converter, List values) {
- final HashSet ret = new HashSet<>();
+ final LinkedHashSet ret = new LinkedHashSet<>();
for (V v : values) {
ret.add(converter.apply(v));
}
@@ -258,15 +264,21 @@ public class ShortcutManagerTestUtils {
return list;
}
+ public static List filter(List list, Predicate p) {
+ final ArrayList ret = new ArrayList<>(list);
+ ret.removeIf(si -> !p.test(si));
+ return ret;
+ }
+
public static List filterByActivity(List list,
ComponentName activity) {
- final ArrayList ret = new ArrayList<>();
- for (ShortcutInfo si : list) {
- if (si.getActivity().equals(activity) && (si.isManifestShortcut() || si.isDynamic())) {
- ret.add(si);
- }
- }
- return ret;
+ return filter(list, si ->
+ (si.getActivity().equals(activity)
+ && (si.isManifestShortcut() || si.isDynamic())));
+ }
+
+ public static List changedSince(List list, long time) {
+ return filter(list, si -> si.getLastChangedTimestamp() >= time);
}
public static void assertExpectException(Class extends Throwable> expectedExceptionType,
@@ -305,8 +317,8 @@ public class ShortcutManagerTestUtils {
public static List assertShortcutIds(List actualShortcuts,
String... expectedIds) {
- final HashSet expected = new HashSet<>(list(expectedIds));
- final HashSet actual = new HashSet<>();
+ final SortedSet expected = new TreeSet<>(list(expectedIds));
+ final SortedSet actual = new TreeSet<>();
for (ShortcutInfo s : actualShortcuts) {
actual.add(s.getId());
}
@@ -316,6 +328,17 @@ public class ShortcutManagerTestUtils {
return actualShortcuts;
}
+ public static List assertShortcutIdsOrdered(List actualShortcuts,
+ String... expectedIds) {
+ final ArrayList expected = new ArrayList<>(list(expectedIds));
+ final ArrayList actual = new ArrayList<>();
+ for (ShortcutInfo s : actualShortcuts) {
+ actual.add(s.getId());
+ }
+ assertEquals(expected, actual);
+ return actualShortcuts;
+ }
+
public static List assertAllHaveIntents(
List actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
@@ -482,7 +505,7 @@ public class ShortcutManagerTestUtils {
}
public static void assertAllUnique(Collection list) {
- final Set