Merge \"ShortcutManger: Auto-adjust ranks.\" into nyc-mr1-dev
am: 70a91541e9
Change-Id: I42d2d2a954bd2f52bd7b75cfd051bab0def100da
This commit is contained in:
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>"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.
|
||||
*/
|
||||
|
||||
@@ -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<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
|
||||
final int maxShortcuts = mShortcutUser.mService.getMaxActivityShortcuts();
|
||||
|
||||
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
|
||||
= new ArrayMap<>();
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
@@ -851,7 +855,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
|
||||
ArrayList<ShortcutInfo> 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<ShortcutInfo> 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<ComponentName, ArrayList<ShortcutInfo>> all =
|
||||
sortShortcutsToActivities();
|
||||
for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
|
||||
final ArrayList<ShortcutInfo> 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<ShortcutInfo> 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<ShortcutInfo> dynamicList = new ArrayList<>(list);
|
||||
dynamicList.removeIf((si) -> !si.isDynamic());
|
||||
|
||||
final ArrayList<ShortcutInfo> 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<ShortcutInfo> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ShortcutInfo> 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 ===
|
||||
|
||||
53
services/tests/servicestests/res/xml/shortcut_5_reverse.xml
Normal file
53
services/tests/servicestests/res/xml/shortcut_5_reverse.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<shortcut
|
||||
android:shortcutId="ms5"
|
||||
android:enabled="true"
|
||||
android:shortcutIcon="@drawable/icon1"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutLongLabel="@string/shortcut_text1"
|
||||
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
|
||||
android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
|
||||
android:shortcutIntentAction="action1"
|
||||
android:shortcutIntentData="http://a.b.c/1"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms4"
|
||||
android:enabled="true"
|
||||
android:shortcutIcon="@drawable/icon2"
|
||||
android:shortcutShortLabel="@string/shortcut_title2"
|
||||
android:shortcutLongLabel="@string/shortcut_text2"
|
||||
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
|
||||
android:shortcutCategories="android.shortcut.conversation"
|
||||
android:shortcutIntentAction="action2"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms3"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms2"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms1"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
</shortcuts>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <T> Set<T> hashSet(Set<T> in) {
|
||||
return new HashSet<T>(in);
|
||||
return new LinkedHashSet<>(in);
|
||||
}
|
||||
|
||||
public static <T> Set<T> set(T... values) {
|
||||
@@ -240,7 +246,7 @@ public class ShortcutManagerTestUtils {
|
||||
}
|
||||
|
||||
public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
|
||||
final HashSet<T> ret = new HashSet<>();
|
||||
final LinkedHashSet<T> ret = new LinkedHashSet<>();
|
||||
for (V v : values) {
|
||||
ret.add(converter.apply(v));
|
||||
}
|
||||
@@ -258,15 +264,21 @@ public class ShortcutManagerTestUtils {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
|
||||
final ArrayList<ShortcutInfo> ret = new ArrayList<>(list);
|
||||
ret.removeIf(si -> !p.test(si));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list,
|
||||
ComponentName activity) {
|
||||
final ArrayList<ShortcutInfo> 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<ShortcutInfo> changedSince(List<ShortcutInfo> 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<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
|
||||
String... expectedIds) {
|
||||
final HashSet<String> expected = new HashSet<>(list(expectedIds));
|
||||
final HashSet<String> actual = new HashSet<>();
|
||||
final SortedSet<String> expected = new TreeSet<>(list(expectedIds));
|
||||
final SortedSet<String> actual = new TreeSet<>();
|
||||
for (ShortcutInfo s : actualShortcuts) {
|
||||
actual.add(s.getId());
|
||||
}
|
||||
@@ -316,6 +328,17 @@ public class ShortcutManagerTestUtils {
|
||||
return actualShortcuts;
|
||||
}
|
||||
|
||||
public static List<ShortcutInfo> assertShortcutIdsOrdered(List<ShortcutInfo> actualShortcuts,
|
||||
String... expectedIds) {
|
||||
final ArrayList<String> expected = new ArrayList<>(list(expectedIds));
|
||||
final ArrayList<String> actual = new ArrayList<>();
|
||||
for (ShortcutInfo s : actualShortcuts) {
|
||||
actual.add(s.getId());
|
||||
}
|
||||
assertEquals(expected, actual);
|
||||
return actualShortcuts;
|
||||
}
|
||||
|
||||
public static List<ShortcutInfo> assertAllHaveIntents(
|
||||
List<ShortcutInfo> actualShortcuts) {
|
||||
for (ShortcutInfo s : actualShortcuts) {
|
||||
@@ -482,7 +505,7 @@ public class ShortcutManagerTestUtils {
|
||||
}
|
||||
|
||||
public static <T> void assertAllUnique(Collection<T> list) {
|
||||
final Set<Object> set = new HashSet<>();
|
||||
final Set<Object> set = new LinkedHashSet<>();
|
||||
for (T item : list) {
|
||||
if (set.contains(item)) {
|
||||
fail("Duplicate item found: " + item + " (in the list: " + list + ")");
|
||||
@@ -594,6 +617,15 @@ public class ShortcutManagerTestUtils {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final Comparator<ShortcutInfo> sRankComparator =
|
||||
(ShortcutInfo a, ShortcutInfo b) -> Integer.compare(a.getRank(), b.getRank());
|
||||
|
||||
public static List<ShortcutInfo> sortedByRank(List<ShortcutInfo> shortcuts) {
|
||||
final ArrayList<ShortcutInfo> ret = new ArrayList<>(shortcuts);
|
||||
Collections.sort(ret, sRankComparator);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void waitUntil(String message, BooleanSupplier condition) {
|
||||
waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
|
||||
}
|
||||
@@ -612,4 +644,78 @@ public class ShortcutManagerTestUtils {
|
||||
}
|
||||
fail("Timed out for: " + message);
|
||||
}
|
||||
|
||||
public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) {
|
||||
return new ShortcutListAsserter(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* New style assertion that allows chained calls.
|
||||
*/
|
||||
public static class ShortcutListAsserter {
|
||||
private final List<ShortcutInfo> mList;
|
||||
|
||||
ShortcutListAsserter(List<ShortcutInfo> list) {
|
||||
mList = new ArrayList<>(list);
|
||||
}
|
||||
|
||||
public ShortcutListAsserter selectDynamic() {
|
||||
return new ShortcutListAsserter(
|
||||
filter(mList, ShortcutInfo::isDynamic));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter selectManifest() {
|
||||
return new ShortcutListAsserter(
|
||||
filter(mList, ShortcutInfo::isManifestShortcut));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter selectPinned() {
|
||||
return new ShortcutListAsserter(
|
||||
filter(mList, ShortcutInfo::isPinned));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter selectByActivity(ComponentName activity) {
|
||||
return new ShortcutListAsserter(
|
||||
ShortcutManagerTestUtils.filterByActivity(mList, activity));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter selectByChangedSince(long time) {
|
||||
return new ShortcutListAsserter(
|
||||
ShortcutManagerTestUtils.changedSince(mList, time));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter toSortByRank() {
|
||||
return new ShortcutListAsserter(
|
||||
ShortcutManagerTestUtils.sortedByRank(mList));
|
||||
}
|
||||
|
||||
public ShortcutListAsserter haveIds(String... expectedIds) {
|
||||
assertShortcutIds(mList, expectedIds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter haveIdsOrdered(String... expectedIds) {
|
||||
assertShortcutIdsOrdered(mList, expectedIds);
|
||||
return this;
|
||||
}
|
||||
|
||||
private ShortcutListAsserter haveSequentialRanks() {
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
assertEquals("Rank not sequential", i, mList.get(i).getRank());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter haveRanksInOrder(String... expectedIds) {
|
||||
toSortByRank()
|
||||
.haveSequentialRanks()
|
||||
.haveIdsOrdered(expectedIds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter isEmpty() {
|
||||
assertEquals(0, mList.size());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user