Merge "Improve shortcut backup & restore."
diff --git a/api/current.txt b/api/current.txt
index ae6587d..a2e242d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11000,6 +11000,7 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
+ method public int getDisabledReason();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
@@ -11018,6 +11019,13 @@
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+ field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+ field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+ field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+ field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+ field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+ field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+ field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 2bf045f..1e2ce4fa 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11723,6 +11723,7 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
+ method public int getDisabledReason();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
@@ -11741,6 +11742,13 @@
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+ field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+ field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+ field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+ field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+ field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+ field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+ field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 25da763..3c61448 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11081,6 +11081,7 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
+ method public int getDisabledReason();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
@@ -11097,8 +11098,16 @@
method public boolean isEnabled();
method public boolean isImmutable();
method public boolean isPinned();
+ method public boolean isVisibleToPublisher();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+ field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+ field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+ field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+ field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+ field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+ field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+ field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 6b9c753..2f78161 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.TaskStackBuilder;
import android.content.ComponentName;
@@ -100,6 +101,13 @@
/** @hide When this is set, the bitmap icon is waiting to be saved. */
public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
+ /**
+ * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+ * installed yet.
+ * @hide
+ */
+ public static final int FLAG_SHADOW = 1 << 12;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -158,6 +166,91 @@
public @interface CloneFlags {}
/**
+ * Shortcut is not disabled.
+ */
+ public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+ /**
+ * Shortcut has been disabled by the publisher app with the
+ * {@link ShortcutManager#disableShortcuts(List)} API.
+ */
+ public static final int DISABLED_REASON_BY_APP = 1;
+
+ /**
+ * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+ * no longer exists.)
+ */
+ public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+ /**
+ * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+ * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+ * ({@link #isVisibleToPublisher()} will be false.)
+ */
+ private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+ /**
+ * Shortcut has been restored from the previous device, but the publisher app on the current
+ * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+ * the same version or higher.
+ */
+ public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+ /**
+ * Shortcut has not been restored because the publisher app does not support backup and restore.
+ */
+ public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+ /**
+ * Shortcut has not been restored because the publisher app's signature has changed.
+ */
+ public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+ /**
+ * Shortcut has not been restored for unknown reason.
+ */
+ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+ /** @hide */
+ @IntDef(value = {
+ DISABLED_REASON_NOT_DISABLED,
+ DISABLED_REASON_BY_APP,
+ DISABLED_REASON_APP_CHANGED,
+ DISABLED_REASON_VERSION_LOWER,
+ DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+ DISABLED_REASON_SIGNATURE_MISMATCH,
+ DISABLED_REASON_OTHER_RESTORE_ISSUE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisabledReason{}
+
+ /** @hide */
+ public static String getDisabledReasonLabel(@DisabledReason int disabledReason) {
+ switch (disabledReason) {
+ case DISABLED_REASON_NOT_DISABLED:
+ return "[Not disabled]";
+ case DISABLED_REASON_BY_APP:
+ return "[Disabled: by app]";
+ case DISABLED_REASON_APP_CHANGED:
+ return "[Disabled: app changed]";
+ case DISABLED_REASON_VERSION_LOWER:
+ return "[Disabled: lower version]";
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return "[Disabled: backup not supported]";
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return "[Disabled: signature mismatch]";
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return "[Disabled: unknown restore issue]";
+ }
+ return "[Disabled: unknown reason:" + disabledReason + "]";
+ }
+
+ /** @hide */
+ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+ return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+ }
+
+ /**
* Shortcut category for messaging related actions, such as chat.
*/
public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -240,6 +333,11 @@
private final int mUserId;
+ /** @hide */
+ public static final int VERSION_CODE_UNKNOWN = -1;
+
+ private int mDisabledReason;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -352,6 +450,7 @@
mActivity = source.mActivity;
mFlags = source.mFlags;
mLastChangedTimestamp = source.mLastChangedTimestamp;
+ mDisabledReason = source.mDisabledReason;
// Just always keep it since it's cheep.
mIconResId = source.mIconResId;
@@ -615,13 +714,23 @@
/**
* @hide
+ *
+ * @isUpdating set true if it's "update", as opposed to "replace".
*/
- public void ensureUpdatableWith(ShortcutInfo source) {
+ public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+ if (isUpdating) {
+ Preconditions.checkState(isVisibleToPublisher(),
+ "[Framework BUG] Invisible shortcuts can't be updated");
+ }
Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
Preconditions.checkState(mId.equals(source.mId), "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package name must match");
- Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+
+ if (isVisibleToPublisher()) {
+ // Don't do this check for restore-blocked shortcuts.
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+ }
}
/**
@@ -638,7 +747,7 @@
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
- ensureUpdatableWith(source);
+ ensureUpdatableWith(source, /*isUpdating=*/ true);
if (source.mActivity != null) {
mActivity = source.mActivity;
@@ -1169,6 +1278,19 @@
return mDisabledMessageResId;
}
+ /** @hide */
+ public void setDisabledReason(@DisabledReason int reason) {
+ mDisabledReason = reason;
+ }
+
+ /**
+ * Returns why a shortcut has been disabled.
+ */
+ @DisabledReason
+ public int getDisabledReason() {
+ return mDisabledReason;
+ }
+
/**
* Return the shortcut's categories.
*
@@ -1403,6 +1525,21 @@
return hasFlags(FLAG_IMMUTABLE);
}
+ /** @hide */
+ public boolean isDynamicVisible() {
+ return isDynamic() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isPinnedVisible() {
+ return isPinned() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isManifestVisible() {
+ return isDeclaredInManifest() && isVisibleToPublisher();
+ }
+
/**
* Return if a shortcut is immutable, in which case it cannot be modified with any of
* {@link ShortcutManager} APIs.
@@ -1491,6 +1628,18 @@
}
/**
+ * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+ * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+ * to launchers though.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleToPublisher() {
+ return !isDisabledForRestoreIssue(mDisabledReason);
+ }
+
+ /**
* Return whether a shortcut only contains "key" information only or not. If true, only the
* following fields are available.
* <ul>
@@ -1668,6 +1817,7 @@
mFlags = source.readInt();
mIconResId = source.readInt();
mLastChangedTimestamp = source.readLong();
+ mDisabledReason = source.readInt();
if (source.readInt() == 0) {
return; // key information only.
@@ -1711,6 +1861,7 @@
dest.writeInt(mFlags);
dest.writeInt(mIconResId);
dest.writeLong(mLastChangedTimestamp);
+ dest.writeInt(mDisabledReason);
if (hasKeyFieldsOnly()) {
dest.writeInt(0);
@@ -1808,6 +1959,11 @@
sb.append(", flags=0x");
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
+ if ((mFlags & FLAG_SHADOW) != 0) {
+ // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+ // we don't have an isXxx for this.
+ sb.append("Sdw");
+ }
if (!isEnabled()) {
sb.append("Dis");
}
@@ -1848,7 +2004,9 @@
sb.append("packageName=");
sb.append(mPackageName);
- sb.append(", activity=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("activity=");
sb.append(mActivity);
addIndentOrComma(sb, indent);
@@ -1883,6 +2041,11 @@
addIndentOrComma(sb, indent);
+ sb.append("disabledReason=");
+ sb.append(getDisabledReasonLabel(mDisabledReason));
+
+ addIndentOrComma(sb, indent);
+
sb.append("categories=");
sb.append(mCategories);
@@ -1953,7 +2116,7 @@
CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
- int flags, int iconResId, String iconResName, String bitmapPath) {
+ int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -1978,5 +2141,6 @@
mIconResId = iconResId;
mIconResName = iconResName;
mBitmapPath = bitmapPath;
+ mDisabledReason = disabledReason;
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index f922ad1..cedf476 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -83,11 +83,16 @@
return mOwnerUserId;
}
+ @Override
+ protected boolean canRestoreAnyVersion() {
+ // Launcher's pinned shortcuts can be restored to an older version.
+ return true;
+ }
+
/**
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
- @Override
- protected void onRestoreBlocked() {
+ private void onRestoreBlocked() {
final ArrayList<PackageWithUser> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
@@ -101,15 +106,21 @@
}
@Override
- protected void onRestored() {
- // Nothing to do.
+ protected void onRestored(int restoreBlockReason) {
+ // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or
+ // DISABLED_REASON_BACKUP_NOT_SUPPORTED.
+ // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version
+ // code for launchers.
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ onRestoreBlocked();
+ }
}
/**
* Pin the given shortcuts, replacing the current pinned ones.
*/
public void pinShortcuts(@UserIdInt int packageUserId,
- @NonNull String packageName, @NonNull List<String> ids) {
+ @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
final ShortcutPackage packageShortcuts =
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
@@ -124,8 +135,12 @@
} else {
final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
- // Pin shortcuts. Make sure only pin the ones that were visible to the caller.
- // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+ // Actually pin shortcuts.
+ // This logic here is to make sure a launcher cannot pin a shortcut that is floating
+ // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+ // In this case, technically the shortcut doesn't exist to this launcher, so it can't
+ // pin it.
+ // (Maybe unnecessarily strict...)
final ArraySet<String> newSet = new ArraySet<>();
@@ -135,8 +150,10 @@
if (si == null) {
continue;
}
- if (si.isDynamic() || si.isManifestShortcut()
- || (prevSet != null && prevSet.contains(id))) {
+ if (si.isDynamic()
+ || si.isManifestShortcut()
+ || (prevSet != null && prevSet.contains(id))
+ || forPinRequest) {
newSet.add(id);
}
}
@@ -155,7 +172,7 @@
}
/**
- * Return true if the given shortcut is pinned by this launcher.
+ * Return true if the given shortcut is pinned by this launcher.<code></code>
*/
public boolean hasPinned(ShortcutInfo shortcut) {
final ArraySet<String> pinned =
@@ -164,10 +181,10 @@
}
/**
- * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)}
+ * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)}
*/
public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
- String id) {
+ String id, boolean forPinRequest) {
final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId);
final ArrayList<String> pinnedList;
if (pinnedSet != null) {
@@ -178,21 +195,21 @@
}
pinnedList.add(id);
- pinShortcuts(packageUserId, packageName, pinnedList);
+ pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest);
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
- public void ensureVersionInfo() {
+ public void ensurePackageInfo() {
final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
getPackageName(), getPackageUserId());
if (pi == null) {
Slog.w(TAG, "Package not found: " + getPackageName());
return;
}
- getPackageInfo().updateVersionInfo(pi);
+ getPackageInfo().updateFromPackageInfo(pi);
}
/**
@@ -201,6 +218,10 @@
@Override
public void saveToXml(XmlSerializer out, boolean forBackup)
throws IOException {
+ if (forBackup && !getPackageInfo().isBackupAllowed()) {
+ // If an launcher app doesn't support backup&restore, then nothing to do.
+ return;
+ }
final int size = mPinnedShortcuts.size();
if (size == 0) {
return; // Nothing to write.
@@ -209,7 +230,7 @@
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
- getPackageInfo().saveToXml(out);
+ getPackageInfo().saveToXml(out, forBackup);
for (int i = 0; i < size; i++) {
final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 6fc1e73..a4bec1d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -84,6 +84,7 @@
private static final String ATTR_DISABLED_MESSAGE = "dmessage";
private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
+ private static final String ATTR_DISABLED_REASON = "disabled-reason";
private static final String ATTR_INTENT_LEGACY = "intent";
private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
private static final String ATTR_RANK = "rank";
@@ -156,13 +157,25 @@
}
@Override
- protected void onRestoreBlocked() {
- // Can't restore due to version/signature mismatch. Remove all shortcuts.
- mShortcuts.clear();
+ protected boolean canRestoreAnyVersion() {
+ return false;
}
@Override
- protected void onRestored() {
+ protected void onRestored(int restoreBlockReason) {
+ // Shortcuts have been restored.
+ // - Unshadow all shortcuts.
+ // - Set disabled reason.
+ // - Disable if needed.
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ ShortcutInfo si = mShortcuts.valueAt(i);
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
+
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ }
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -176,31 +189,47 @@
return mShortcuts.get(id);
}
- private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
- if (shortcut != null && shortcut.isImmutable()) {
+ public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
+ ShortcutInfo si = findShortcutById(id);
+ return si != null && !si.isVisibleToPublisher();
+ }
+
+ public boolean isShortcutExistsAndVisibleToPublisher(String id) {
+ ShortcutInfo si = findShortcutById(id);
+ return si != null && si.isVisibleToPublisher();
+ }
+
+ private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
+ if (shortcut != null && shortcut.isImmutable()
+ && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
throw new IllegalArgumentException(
"Manifest shortcut ID=" + shortcut.getId()
+ " may not be manipulated via APIs");
}
}
- public void ensureNotImmutable(@NonNull String id) {
- ensureNotImmutable(mShortcuts.get(id));
+ public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
+ ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
}
- public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+ public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
+ boolean ignoreInvisible) {
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ensureNotImmutable(shortcutIds.get(i));
+ ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
}
}
- public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+ public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
+ boolean ignoreInvisible) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
- ensureNotImmutable(shortcuts.get(i).getId());
+ ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
}
}
- private ShortcutInfo deleteShortcutInner(@NonNull String id) {
+ /**
+ * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
+ */
+ private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
mShortcutUser.mService.removeIconLocked(shortcut);
@@ -210,10 +239,14 @@
return shortcut;
}
- private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+ /**
+ * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
+ * even if it's invisible.
+ */
+ private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
final ShortcutService s = mShortcutUser.mService;
- deleteShortcutInner(newShortcut.getId());
+ forceDeleteShortcutInner(newShortcut.getId());
// Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
@@ -222,11 +255,12 @@
}
/**
- * Add a shortcut, or update one with the same ID, with taking over existing flags.
+ * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
+ * invisible.
*
* It checks the max number of dynamic shortcuts.
*/
- public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+ public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
Preconditions.checkArgument(newShortcut.isEnabled(),
"add/setDynamicShortcuts() cannot publish disabled shortcuts");
@@ -242,7 +276,7 @@
} else {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
- oldShortcut.ensureUpdatableWith(newShortcut);
+ oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
wasPinned = oldShortcut.isPinned();
}
@@ -252,7 +286,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
- addShortcutInner(newShortcut);
+ forceReplaceShortcutInner(newShortcut);
}
/**
@@ -273,7 +307,7 @@
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
- deleteShortcutInner(removeList.get(i));
+ forceDeleteShortcutInner(removeList.get(i));
}
}
}
@@ -281,13 +315,13 @@
/**
* Remove all dynamic shortcuts.
*/
- public void deleteAllDynamicShortcuts() {
+ public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
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()) {
+ if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
changed = true;
si.setTimestamp(now);
@@ -307,9 +341,10 @@
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
- public boolean deleteDynamicWithId(@NonNull String shortcutId) {
+ public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
final ShortcutInfo removed = deleteOrDisableWithId(
- shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+ shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
+ ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
return removed == null;
}
@@ -320,9 +355,11 @@
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
- private boolean disableDynamicWithId(@NonNull String shortcutId) {
+ private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
+ int disabledReason) {
final ShortcutInfo disabled = deleteOrDisableWithId(
- shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
+ shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
+ disabledReason);
return disabled == null;
}
@@ -331,9 +368,10 @@
* is pinned, it'll remain as a pinned shortcut but will be disabled.
*/
public void disableWithId(@NonNull String shortcutId, String disabledMessage,
- int disabledMessageResId, boolean overrideImmutable) {
+ int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
+ int disabledReason) {
final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
- overrideImmutable);
+ overrideImmutable, ignoreInvisible, disabledReason);
if (disabled != null) {
if (disabledMessage != null) {
@@ -348,14 +386,18 @@
@Nullable
private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
- boolean overrideImmutable) {
+ boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
+ Preconditions.checkState(
+ (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
+ "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
- if (oldShortcut == null || !oldShortcut.isEnabled()) {
+ if (oldShortcut == null || !oldShortcut.isEnabled()
+ && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
return null; // Doesn't exist or already disabled.
}
if (!overrideImmutable) {
- ensureNotImmutable(oldShortcut);
+ ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
}
if (oldShortcut.isPinned()) {
@@ -363,6 +405,10 @@
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+ // Do not overwrite the disabled reason if one is alreay set.
+ if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ oldShortcut.setDisabledReason(disabledReason);
+ }
}
oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
@@ -373,7 +419,7 @@
return oldShortcut;
} else {
- deleteShortcutInner(shortcutId);
+ forceDeleteShortcutInner(shortcutId);
return null;
}
}
@@ -381,11 +427,25 @@
public void enableWithId(@NonNull String shortcutId) {
final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
if (shortcut != null) {
- ensureNotImmutable(shortcut);
+ ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
}
}
+ public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
+ final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+ Preconditions.checkNotNull(source);
+
+ mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
+
+ shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+
+ forceReplaceShortcutInner(shortcut);
+
+ adjustRanks();
+ }
+
/**
* Called after a launcher updates the pinned set. For each shortcut in this package,
* set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
@@ -693,7 +753,27 @@
getPackageInfo().getVersionCode(), pi.versionCode));
}
- getPackageInfo().updateVersionInfo(pi);
+ getPackageInfo().updateFromPackageInfo(pi);
+ final int newVersionCode = getPackageInfo().getVersionCode();
+
+ // See if there are any shortcuts that were prevented restoring because the app was of a
+ // lower version, and re-enable them.
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+ continue;
+ }
+ if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
+ si.getId(), getPackageInfo().getBackupSourceVersionCode()));
+ }
+ continue;
+ }
+ Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ }
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
@@ -713,7 +793,8 @@
Slog.w(TAG, String.format(
"%s is no longer main activity. Disabling shorcut %s.",
getPackageName(), si.getId()));
- if (disableDynamicWithId(si.getId())) {
+ if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
+ ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
continue; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
@@ -809,7 +890,7 @@
// Note even if enabled=false, we still need to update all fields, so do it
// regardless.
- addShortcutInner(newShortcut); // This will clean up the old one too.
+ forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
if (!newDisabled && toDisableList != null) {
// Still alive, don't remove.
@@ -831,7 +912,8 @@
final String id = toDisableList.valueAt(i);
disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
- /* overrideImmutable=*/ true);
+ /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
+ ShortcutInfo.DISABLED_REASON_APP_CHANGED);
}
removeOrphans();
}
@@ -869,7 +951,7 @@
service.wtf("Found manifest shortcuts in excess list.");
continue;
}
- deleteDynamicWithId(shortcut.getId());
+ deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
}
}
@@ -1075,7 +1157,7 @@
if (ret != 0) {
return ret;
}
- // If they're stil tie, just sort by their IDs.
+ // If they're still tie, just sort by their IDs.
// This may happen with updateShortcuts() -- see
// the testUpdateShortcuts_noManifestShortcuts() test.
return a.getId().compareTo(b.getId());
@@ -1257,25 +1339,34 @@
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
- getPackageInfo().saveToXml(out);
+ getPackageInfo().saveToXml(out, forBackup);
for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup);
+ saveShortcut(out, mShortcuts.valueAt(j), forBackup,
+ getPackageInfo().isBackupAllowed());
}
out.endTag(null, TAG_ROOT);
}
- private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
+ private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
+ boolean appSupportsBackup)
throws IOException, XmlPullParserException {
final ShortcutService s = mShortcutUser.mService;
if (forBackup) {
if (!(si.isPinned() && si.isEnabled())) {
- return; // We only backup pinned shortcuts that are enabled.
+ // We only backup pinned shortcuts that are enabled.
+ // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
+ // to a lower version code, will not be ported to a new device.
+ return;
}
}
+ final boolean shouldBackupDetails =
+ !forBackup // It's not backup
+ || appSupportsBackup; // Or, it's a backup and app supports backup.
+
// Note: at this point no shortcuts should have bitmaps pending save, but if they do,
// just remove the bitmap.
if (si.isIconPendingSave()) {
@@ -1292,20 +1383,31 @@
ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
- si.getDisabledMessageResourceId());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
- si.getDisabledMessageResName());
+ if (shouldBackupDetails) {
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
+ si.getDisabledMessageResourceId());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
+ si.getDisabledMessageResName());
+ }
+ ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
// Don't write icon information. Also drop the dynamic flag.
- ShortcutService.writeAttr(out, ATTR_FLAGS,
- si.getFlags() &
- ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
+
+ int flags = si.getFlags() &
+ ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
- | ShortcutInfo.FLAG_DYNAMIC));
+ | ShortcutInfo.FLAG_DYNAMIC);
+ ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
+
+ // Set the publisher version code at every backup.
+ final int packageVersionCode = getPackageInfo().getVersionCode();
+ if (packageVersionCode == 0) {
+ s.wtf("Package version code should be available at this point.");
+ // However, 0 is a valid version code, so we just go ahead with it...
+ }
} else {
// When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
// as dynamic.
@@ -1317,26 +1419,28 @@
ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
}
- {
- final Set<String> cat = si.getCategories();
- if (cat != null && cat.size() > 0) {
- out.startTag(null, TAG_CATEGORIES);
- XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
- NAME_CATEGORIES, out);
- out.endTag(null, TAG_CATEGORIES);
+ if (shouldBackupDetails) {
+ {
+ final Set<String> cat = si.getCategories();
+ if (cat != null && cat.size() > 0) {
+ out.startTag(null, TAG_CATEGORIES);
+ XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
+ NAME_CATEGORIES, out);
+ out.endTag(null, TAG_CATEGORIES);
+ }
}
- }
- final Intent[] intentsNoExtras = si.getIntentsNoExtras();
- final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
- }
+ final Intent[] intentsNoExtras = si.getIntentsNoExtras();
+ final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+ }
out.endTag(null, TAG_SHORTCUT);
}
@@ -1356,6 +1460,7 @@
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1369,10 +1474,11 @@
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
continue;
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName,
- shortcutUser.getUserId());
+ shortcutUser.getUserId(), fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
ret.mShortcuts.put(si.getId(), si);
@@ -1385,7 +1491,8 @@
}
private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
- @UserIdInt int userId) throws IOException, XmlPullParserException {
+ @UserIdInt int userId, boolean fromBackup)
+ throws IOException, XmlPullParserException {
String id;
ComponentName activityComponent;
// Icon icon;
@@ -1398,6 +1505,7 @@
String disabledMessage;
int disabledMessageResId;
String disabledMessageResName;
+ int disabledReason;
Intent intentLegacy;
PersistableBundle intentPersistableExtrasLegacy = null;
ArrayList<Intent> intents = new ArrayList<>();
@@ -1408,6 +1516,7 @@
int iconResId;
String iconResName;
String bitmapPath;
+ int backupVersionCode;
ArraySet<String> categories = null;
id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
@@ -1424,6 +1533,7 @@
ATTR_DISABLED_MESSAGE_RES_ID);
disabledMessageResName = ShortcutService.parseStringAttribute(parser,
ATTR_DISABLED_MESSAGE_RES_NAME);
+ disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
@@ -1480,6 +1590,19 @@
intents.add(intentLegacy);
}
+
+ if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+ && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
+ // We didn't used to have the disabled reason, so if a shortcut is disabled
+ // and has no reason, we assume it was disabled by publisher.
+ disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
+ }
+
+ // All restored shortcuts are initially "shadow".
+ if (fromBackup) {
+ flags |= ShortcutInfo.FLAG_SHADOW;
+ }
+
return new ShortcutInfo(
userId, id, packageName, activityComponent, /* icon =*/ null,
title, titleResId, titleResName, text, textResId, textResName,
@@ -1487,7 +1610,7 @@
categories,
intents.toArray(new Intent[intents.size()]),
rank, extras, lastChangedTimestamp, flags,
- iconResId, iconResName, bitmapPath);
+ iconResId, iconResName, bitmapPath, disabledReason);
}
private static Intent parseIntent(XmlPullParser parser)
@@ -1602,6 +1725,20 @@
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
+ if (si.isEnabled()
+ != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " isEnabled() and getDisabledReason() disagree: "
+ + si.isEnabled() + " vs " + si.getDisabledReason());
+ }
+ if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+ && (getPackageInfo().getBackupSourceVersionCode()
+ == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " RESTORED_VERSION_LOWER with no backup source version code.");
+ }
if (s.isDummyMainActivity(si.getActivity())) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index e5a2f5a..3a9bbc8 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -45,32 +46,45 @@
static final String TAG_ROOT = "package-info";
private static final String ATTR_VERSION = "version";
private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
+ private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
+ private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
+ private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
private static final String ATTR_SHADOW = "shadow";
private static final String TAG_SIGNATURE = "signature";
private static final String ATTR_SIGNATURE_HASH = "hash";
- private static final int VERSION_UNKNOWN = -1;
-
/**
* When true, this package information was restored from the previous device, and the app hasn't
* been installed yet.
*/
private boolean mIsShadow;
- private int mVersionCode = VERSION_UNKNOWN;
+ private int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+ private int mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
private long mLastUpdateTime;
private ArrayList<byte[]> mSigHashes;
+ // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
+ // mBackupAllowed will always start with false, and will have been updated before making a
+ // backup next time, which works file.
+ // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
+ // we use this boolean to control dumpsys.
+ private boolean mBackupAllowedInitialized;
+ private boolean mBackupAllowed;
+ private boolean mBackupSourceBackupAllowed;
+
private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
ArrayList<byte[]> sigHashes, boolean isShadow) {
mVersionCode = versionCode;
mLastUpdateTime = lastUpdateTime;
mIsShadow = isShadow;
mSigHashes = sigHashes;
+ mBackupAllowed = false; // By default, we assume false.
+ mBackupSourceBackupAllowed = false;
}
public static ShortcutPackageInfo newEmpty() {
- return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+ return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
new ArrayList<>(0), /* isShadow */ false);
}
@@ -86,15 +100,33 @@
return mVersionCode;
}
+ public int getBackupSourceVersionCode() {
+ return mBackupSourceVersionCode;
+ }
+
+ @VisibleForTesting
+ public boolean isBackupSourceBackupAllowed() {
+ return mBackupSourceBackupAllowed;
+ }
+
public long getLastUpdateTime() {
return mLastUpdateTime;
}
- /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
- public void updateVersionInfo(@NonNull PackageInfo pi) {
+ public boolean isBackupAllowed() {
+ return mBackupAllowed;
+ }
+
+ /**
+ * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
+ * from a {@link PackageInfo}.
+ */
+ public void updateFromPackageInfo(@NonNull PackageInfo pi) {
if (pi != null) {
mVersionCode = pi.versionCode;
mLastUpdateTime = pi.lastUpdateTime;
+ mBackupAllowed = ShortcutService.shouldBackupApp(pi);
+ mBackupAllowedInitialized = true;
}
}
@@ -102,23 +134,24 @@
return mSigHashes.size() > 0;
}
- public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
- if (!s.shouldBackupApp(target)) {
- // "allowBackup" was true when backed up, but now false.
- Slog.w(TAG, "Can't restore: package no longer allows backup");
- return false;
+ //@DisabledReason
+ public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
+ if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
+ Slog.w(TAG, "Can't restore: Package signature mismatch");
+ return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
}
- if (target.versionCode < mVersionCode) {
+ if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
+ // "allowBackup" was true when backed up, but now false.
+ Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
+ return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
+ }
+ if (!anyVersionOkay && (currentPackage.versionCode < mBackupSourceVersionCode)) {
Slog.w(TAG, String.format(
"Can't restore: package current version %d < backed up version %d",
- target.versionCode, mVersionCode));
- return false;
+ currentPackage.versionCode, mBackupSourceVersionCode));
+ return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
}
- if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
- Slog.w(TAG, "Can't restore: Package signature mismatch");
- return false;
- }
- return true;
+ return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
}
@VisibleForTesting
@@ -132,6 +165,8 @@
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
+ ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
+ ret.mBackupSourceVersionCode = pi.versionCode;
return ret;
}
@@ -151,13 +186,19 @@
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
}
- public void saveToXml(XmlSerializer out) throws IOException {
+ public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
+ ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
+
+ ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
+ ShortcutService.writeAttr(out,
+ ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
+
for (int i = 0; i < mSigHashes.size(); i++) {
out.startTag(null, TAG_SIGNATURE);
@@ -171,7 +212,9 @@
public void loadFromXml(XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
- final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
+ // Don't use the version code from the backup file.
+ final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION,
+ ShortcutInfo.VERSION_CODE_UNKNOWN);
final long lastUpdateTime = ShortcutService.parseLongAttribute(
parser, ATTR_LAST_UPDATE_TIME);
@@ -180,6 +223,20 @@
final boolean shadow =
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
+ // We didn't used to save these attributes, and all backed up shortcuts were from
+ // apps that support backups, so the default values take this fact into consideration.
+ final int backupSourceVersion = ShortcutService.parseIntAttribute(parser,
+ ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
+
+ // Note the only time these "true" default value is used is when restoring from an old
+ // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
+ // a backup file were from apps that support backup, so we can just use "true" as the
+ // default.
+ final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
+ parser, ATTR_BACKUP_ALLOWED, true);
+ final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
+ parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
+
final ArrayList<byte[]> hashes = new ArrayList<>();
final int outerDepth = parser.getDepth();
@@ -207,11 +264,28 @@
ShortcutService.warnForInvalidTag(depth, tag);
}
- // Successfully loaded; replace the feilds.
- mVersionCode = versionCode;
+ // Successfully loaded; replace the fields.
+ if (fromBackup) {
+ mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+ mBackupSourceVersionCode = versionCode;
+ mBackupSourceBackupAllowed = backupAllowed;
+ } else {
+ mVersionCode = versionCode;
+ mBackupSourceVersionCode = backupSourceVersion;
+ mBackupSourceBackupAllowed = backupSourceBackupAllowed;
+ }
mLastUpdateTime = lastUpdateTime;
mIsShadow = shadow;
mSigHashes = hashes;
+
+ // Note we don't restore it from the file because it didn't used to be saved.
+ // We always start by assuming backup is disabled for the current package,
+ // and this field will have been updated before we actually create a backup, at the same
+ // time when we update the version code.
+ // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
+ // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
+ mBackupAllowed = false;
+ mBackupAllowedInitialized = false;
}
public void dump(PrintWriter pw, String prefix) {
@@ -223,6 +297,7 @@
pw.print(prefix);
pw.print(" IsShadow: ");
pw.print(mIsShadow);
+ pw.print(mIsShadow ? " (not installed)" : " (installed)");
pw.println();
pw.print(prefix);
@@ -230,6 +305,25 @@
pw.print(mVersionCode);
pw.println();
+ if (mBackupAllowedInitialized) {
+ pw.print(prefix);
+ pw.print(" Backup Allowed: ");
+ pw.print(mBackupAllowed);
+ pw.println();
+ }
+
+ if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
+ pw.print(prefix);
+ pw.print(" Backup source version: ");
+ pw.print(mBackupSourceVersionCode);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Backup source backup allowed: ");
+ pw.print(mBackupSourceBackupAllowed);
+ pw.println();
+ }
+
pw.print(prefix);
pw.print(" Last package update time: ");
pw.print(mLastUpdateTime);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index e59d69f..97b7b59 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.util.Preconditions;
@@ -101,51 +102,42 @@
final ShortcutService s = mShortcutUser.mService;
if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+ Slog.d(TAG, String.format("Package still not installed: %s/u%d",
mPackageName, mPackageUserId));
}
return; // Not installed, no need to restore yet.
}
- boolean blockRestore = false;
- if (!mPackageInfo.hasSignatures()) {
- s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
- + " but signatures not found in the restore data.");
- blockRestore = true;
- }
- if (!blockRestore) {
- final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
- if (!mPackageInfo.canRestoreTo(s, pi)) {
- // Package is now installed, but can't restore. Let the subclass do the cleanup.
- blockRestore = true;
- }
- }
- if (blockRestore) {
- onRestoreBlocked();
- } else {
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
- mPackageUserId, getOwnerUserId()));
- }
+ int restoreBlockReason;
+ int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
- onRestored();
+ if (!mPackageInfo.hasSignatures()) {
+ s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId
+ + " but signatures not found in the restore data.");
+ restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+ } else {
+ final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
+ currentVersionCode = pi.versionCode;
+ restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion());
}
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d",
+ mPackageName, mPackageUserId, currentVersionCode,
+ ShortcutInfo.getDisabledReasonLabel(restoreBlockReason),
+ getOwnerUserId()));
+ }
+
+ onRestored(restoreBlockReason);
+
// Either way, it's no longer a shadow.
mPackageInfo.setShadow(false);
s.scheduleSaveUser(mPackageUserId);
}
- /**
- * Called when the new package can't be restored because it has a lower version number
- * or different signatures.
- */
- protected abstract void onRestoreBlocked();
+ protected abstract boolean canRestoreAnyVersion();
- /**
- * Called when the new package is successfully restored.
- */
- protected abstract void onRestored();
+ protected abstract void onRestored(int restoreBlockReason);
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index 3cf4200..866c46c 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -337,6 +337,9 @@
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
| ShortcutInfo.FLAG_IMMUTABLE
| ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+ final int disabledReason =
+ enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+ : ShortcutInfo.DISABLED_REASON_BY_APP;
// Note we don't need to set resource names here yet. They'll be set when they're about
// to be published.
@@ -363,6 +366,7 @@
flags,
iconResId,
null, // icon res name
- null); // bitmap path
+ null, // bitmap path
+ disabledReason);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 8a8128d..3e44de9 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -300,10 +300,12 @@
final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
final boolean existsAlready = existing != null;
+ final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();
if (DEBUG) {
Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage()
+ " existsAlready=" + existsAlready
+ + " existingIsVisible=" + existingIsVisible
+ " shortcut=" + inShortcut.toInsecureString());
}
@@ -378,7 +380,6 @@
// manifest shortcut.)
Preconditions.checkArgument(shortcutInfo.isEnabled(),
"Shortcut ID=" + shortcutInfo + " already exists but disabled.");
-
}
private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
@@ -463,7 +464,7 @@
launcher.attemptToRestoreIfNeededAndSave();
if (launcher.hasPinned(original)) {
if (DEBUG) {
- Slog.d(TAG, "Shortcut " + original + " already pinned.");
+ Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too.
}
return true;
}
@@ -497,7 +498,7 @@
if (original.getActivity() == null) {
original.setActivity(mService.getDummyMainActivity(appPackageName));
}
- ps.addOrUpdateDynamicShortcut(original);
+ ps.addOrReplaceDynamicShortcut(original);
}
// Pin the shortcut.
@@ -505,13 +506,14 @@
Slog.d(TAG, "Pinning " + shortcutId);
}
- launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId);
+ launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
+ /*forPinRequest=*/ true);
if (current == null) {
if (DEBUG) {
Slog.d(TAG, "Removing " + shortcutId + " as dynamic");
}
- ps.deleteDynamicWithId(shortcutId);
+ ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false);
}
ps.adjustRanks(); // Shouldn't be needed, but just in case.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 27560c5f..9d86796 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -553,6 +553,9 @@
public Lifecycle(Context context) {
super(context);
+ if (DEBUG) {
+ Binder.LOG_RUNTIME_EXCEPTION = true;
+ }
mService = new ShortcutService(context);
}
@@ -738,6 +741,10 @@
return parseLongAttribute(parser, attribute) == 1;
}
+ static boolean parseBooleanAttribute(XmlPullParser parser, String attribute, boolean def) {
+ return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1;
+ }
+
static int parseIntAttribute(XmlPullParser parser, String attribute) {
return (int) parseLongAttribute(parser, attribute);
}
@@ -835,6 +842,8 @@
static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
if (value) {
writeAttr(out, name, "1");
+ } else {
+ writeAttr(out, name, "0");
}
}
@@ -1689,7 +1698,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
fillInDefaultActivity(newShortcuts);
@@ -1709,12 +1718,12 @@
}
// First, remove all un-pinned; dynamic shortcuts
- ps.deleteAllDynamicShortcuts();
+ ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i);
- ps.addOrUpdateDynamicShortcut(newShortcut);
+ ps.addOrReplaceDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
@@ -1740,7 +1749,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
// For update, don't fill in the default activity. Having null activity means
// "don't update the activity" here.
@@ -1761,7 +1770,9 @@
fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
final ShortcutInfo target = ps.findShortcutById(source.getId());
- if (target == null) {
+
+ // Invisible shortcuts can't be updated.
+ if (target == null || !target.isVisibleToPublisher()) {
continue;
}
@@ -1808,7 +1819,7 @@
}
@Override
- public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+ public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
@@ -1820,7 +1831,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
fillInDefaultActivity(newShortcuts);
@@ -1845,7 +1856,7 @@
newShortcut.setRankChanged();
// Add it.
- ps.addOrUpdateDynamicShortcut(newShortcut);
+ ps.addOrReplaceDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
@@ -1901,6 +1912,22 @@
Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
"Calling application must have a foreground activity or a foreground service");
+ // If it's a pin shortcut request, and there's already a shortcut with the same ID
+ // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by
+ // someone already), then we just replace the existing one with this new one,
+ // and then proceed the rest of the process.
+ if (shortcut != null) {
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
+ packageName, userId);
+ final String id = shortcut.getId();
+ if (ps.isShortcutExistsAndInvisibleToPublisher(id)) {
+
+ ps.updateInvisibleShortcutForPinRequestWith(shortcut);
+
+ packageShortcutsChanged(packageName, userId);
+ }
+ }
+
// Send request to the launcher, if supported.
ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
userId, resultIntent);
@@ -1922,15 +1949,21 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
final String disabledMessageString =
(disabledMessage == null) ? null : disabledMessage.toString();
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.disableWithId(id,
disabledMessageString, disabledMessageResId,
- /* overrideImmutable=*/ false);
+ /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true,
+ ShortcutInfo.DISABLED_REASON_BY_APP);
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1951,10 +1984,15 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.enableWithId((String) shortcutIds.get(i));
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.enableWithId(id);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1973,11 +2011,15 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.deleteDynamicWithId(
- Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1996,7 +2038,7 @@
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.deleteAllDynamicShortcuts();
+ ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
}
packageShortcutsChanged(packageName, userId);
@@ -2013,7 +2055,7 @@
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isDynamic);
+ ShortcutInfo::isDynamicVisible);
}
}
@@ -2027,7 +2069,7 @@
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isManifestShortcut);
+ ShortcutInfo::isManifestVisible);
}
}
@@ -2041,7 +2083,7 @@
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isPinned);
+ ShortcutInfo::isPinnedVisible);
}
}
@@ -2513,7 +2555,7 @@
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
launcher.attemptToRestoreIfNeededAndSave();
- launcher.pinShortcuts(userId, packageName, shortcutIds);
+ launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
}
packageShortcutsChanged(packageName, userId);
@@ -3343,7 +3385,7 @@
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
- boolean shouldBackupApp(PackageInfo pi) {
+ static boolean shouldBackupApp(PackageInfo pi) {
return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
}
@@ -3371,7 +3413,7 @@
// Set the version code for the launchers.
// We shouldn't do this for publisher packages, because we don't want to update the
// version code without rescanning the manifest.
- user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
+ user.forAllLaunchers(launcher -> launcher.ensurePackageInfo());
// Save to the filesystem.
scheduleSaveUser(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 55e6d28..48eccd0 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -364,9 +364,6 @@
private void saveShortcutPackageItem(XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
- if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
- return; // Don't save.
- }
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
diff --git a/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml b/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml
new file mode 100644
index 0000000..266ab99
--- /dev/null
+++ b/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<user>
+ <launcher-pins package-name="com.android.launcher.1" launcher-user="0">
+ <package-info version="9" last_udpate_time="1230768000000">
+ <signature hash="0X8l7PvMeFf3vr6kaTCL4LJYCUPpbROjrZihNnXEv8I" />
+ </package-info>
+ <package package-name="com.android.test.1" package-user="0">
+ <pin value="s1" />
+ </package>
+ </launcher-pins>
+ <package name="com.android.test.1" call-count="0" last-reset="1506991428317">
+ <package-info version="8" last_udpate_time="1506534668998">
+ <signature hash="zDmdc5A/Bu5pQDKrBTjwVjT/fhzl6OUKwzCocUhPNM8" />
+ </package-info>
+ <shortcut id="s1" activity="com.android.test.1/com.example.android.shortcutsample.Main" title="Leak wakelock" titleid="0" textid="0" dmessageid="0" timestamp="1507674156622" flags="130">
+ <intent intent-base="#Intent;action=com.example.android.shortcutsample.LEAK;launchFlags=0x1000c000;component=com.example.android.shortcutsample/.Main;end" />
+ </shortcut>
+ </package>
+</user>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_altalt.xml b/services/tests/servicestests/res/xml/shortcut_5_altalt.xml
new file mode 100644
index 0000000..1476a27
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5_altalt.xml
@@ -0,0 +1,87 @@
+<?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="ms1"
+ android:enabled="true"
+ android:icon="@drawable/icon1"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ android:shortcutLongLabel="@string/shortcut_text1"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+ >
+ <intent
+ android:action="action1"
+ android:data="http://a.b.c/1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms2"
+ android:enabled="true"
+ android:icon="@drawable/icon2"
+ android:shortcutShortLabel="@string/shortcut_title2"
+ android:shortcutLongLabel="@string/shortcut_text2"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+ >
+ <intent
+ android:action="action2"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms3"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ android:shortcutLongLabel="@string/shortcut_title2"
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms4"
+ android:shortcutShortLabel="@string/shortcut_title2"
+ android:shortcutLongLabel="@string/shortcut_title2"
+ >
+ <intent
+ android:action="android.intent.action.VIEW2"
+ >
+ </intent>
+ <categories />
+ <categories android:name="" />
+ <categories android:name="cat" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms5"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ >
+ <intent
+ android:action="action"
+ android:data="http://www/"
+ android:targetPackage="abc"
+ android:targetClass=".xyz"
+ android:mimeType="foo/bar"
+ >
+ <categories android:name="cat1" />
+ <categories android:name="cat2" />
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="value2" />
+ </intent>
+ </shortcut>
+</shortcuts>
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 951f226..d017976 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -15,6 +15,11 @@
*/
package com.android.server.pm;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
+
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyOrNull;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyStringOrNull;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDisabled;
@@ -71,7 +76,9 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
@@ -101,7 +108,6 @@
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
-import java.util.function.BiPredicate;
/**
* Tests for ShortcutService and ShortcutManager.
@@ -250,7 +256,7 @@
final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.icon2));
final Icon icon3 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.icon2));
+ getTestContext().getResources(), R.drawable.icon2));
final ShortcutInfo si1 = makeShortcut(
"shortcut1",
@@ -706,13 +712,13 @@
assertBitmapSize(128, 128, bmp);
Drawable dr = mLauncherApps.getShortcutIconDrawable(
- makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
+ makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
assertTrue(dr instanceof AdaptiveIconDrawable);
float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
- dr.getIntrinsicWidth());
+ dr.getIntrinsicWidth());
assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
- dr.getIntrinsicHeight());
+ dr.getIntrinsicHeight());
}
public void testCleanupDanglingBitmaps() throws Exception {
@@ -1118,8 +1124,8 @@
// Set resource icon
assertTrue(mManager.updateShortcuts(list(
new ShortcutInfo.Builder(mClientContext, "s1")
- .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
- .build()
+ .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
+ .build()
)));
assertWith(getCallerShortcuts())
@@ -1131,9 +1137,9 @@
// Set bitmap icon
assertTrue(mManager.updateShortcuts(list(
new ShortcutInfo.Builder(mClientContext, "s1")
- .setIcon(Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_64x64)))
- .build()
+ .setIcon(Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_64x64)))
+ .build()
)));
assertWith(getCallerShortcuts())
@@ -2126,7 +2132,7 @@
final ShortcutQuery q = new ShortcutQuery().setQueryFlags(
ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_PINNED
- | ShortcutQuery.FLAG_MATCH_MANIFEST);
+ | ShortcutQuery.FLAG_MATCH_MANIFEST);
// No shortcuts are visible.
assertWith(mLauncherApps.getShortcuts(q, HANDLE_USER_0)).isEmpty();
@@ -2668,10 +2674,10 @@
"Title 1",
makeComponent(ShortcutActivity.class),
/* icon =*/ null,
- new Intent[] {makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ new Intent[]{makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
"key1", "val1", "nest", makeBundle("key", 123))
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
- new Intent("act2").setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)},
+ new Intent("act2").setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)},
/* rank */ 10);
final ShortcutInfo s1_2 = makeShortcut(
@@ -2776,8 +2782,8 @@
// Not launchable.
doReturn(ActivityManager.START_CLASS_NOT_FOUND)
.when(mMockActivityManagerInternal).startActivitiesAsPackage(
- anyStringOrNull(), anyInt(),
- anyOrNull(Intent[].class), anyOrNull(Bundle.class));
+ anyStringOrNull(), anyInt(),
+ anyOrNull(Intent[].class), anyOrNull(Bundle.class));
assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0,
ActivityNotFoundException.class);
@@ -2911,7 +2917,7 @@
.revertToOriginalList()
.selectByIds("s1", "s2")
.areAllDynamic()
- ;
+ ;
});
// 3 Update the app with no manifest shortcuts. (Pinned one will survive.)
@@ -3309,13 +3315,13 @@
});
- final SparseArray<ShortcutUser> users = mService.getShortcutsForTest();
+ final SparseArray<ShortcutUser> users = mService.getShortcutsForTest();
assertEquals(2, users.size());
assertEquals(USER_0, users.keyAt(0));
assertEquals(USER_10, users.keyAt(1));
- final ShortcutUser user0 = users.get(USER_0);
- final ShortcutUser user10 = users.get(USER_10);
+ final ShortcutUser user0 = users.get(USER_0);
+ final ShortcutUser user10 = users.get(USER_10);
// Check the registered packages.
@@ -3531,7 +3537,7 @@
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3548,7 +3554,7 @@
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_1);
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3853,52 +3859,77 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
}
- protected void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
- int version, String... signatures) {
- assertEquals(expected, spi.canRestoreTo(mService, genPackage(
- "dummy", /* uid */ 0, version, signatures)));
+ protected void checkCanRestoreTo(int expected, ShortcutPackageInfo spi,
+ boolean anyVersionOk, int version, boolean nowBackupAllowed, String... signatures) {
+ final PackageInfo pi = genPackage("dummy", /* uid */ 0, version, signatures);
+ if (!nowBackupAllowed) {
+ pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP;
+ }
+ assertEquals(expected, spi.canRestoreTo(mService, pi, anyVersionOk));
}
public void testCanRestoreTo() {
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
- addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2");
+ addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2");
+ addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1");
+
+ updatePackageInfo(CALLING_PACKAGE_3,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackageForTest(
mService, CALLING_PACKAGE_1, USER_0);
final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackageForTest(
mService, CALLING_PACKAGE_2, USER_0);
+ final ShortcutPackageInfo spi3 = ShortcutPackageInfo.generateForInstalledPackageForTest(
+ mService, CALLING_PACKAGE_3, USER_0);
- checkCanRestoreTo(true, spi1, 10, "sig1");
- checkCanRestoreTo(true, spi1, 10, "x", "sig1");
- checkCanRestoreTo(true, spi1, 10, "sig1", "y");
- checkCanRestoreTo(true, spi1, 10, "x", "sig1", "y");
- checkCanRestoreTo(true, spi1, 11, "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "x", "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "x", "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 11, true, "sig1");
- checkCanRestoreTo(false, spi1, 10 /* empty */);
- checkCanRestoreTo(false, spi1, 10, "x");
- checkCanRestoreTo(false, spi1, 10, "x", "y");
- checkCanRestoreTo(false, spi1, 10, "x");
- checkCanRestoreTo(false, spi1, 9, "sig1");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true/* empty */);
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x", "y");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x");
+ checkCanRestoreTo(DISABLED_REASON_VERSION_LOWER, spi1, false, 9, true, "sig1");
- checkCanRestoreTo(true, spi2, 10, "sig1", "sig2");
- checkCanRestoreTo(true, spi2, 10, "sig2", "sig1");
- checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2");
- checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1");
- checkCanRestoreTo(true, spi2, 10, "sig1", "sig2", "y");
- checkCanRestoreTo(true, spi2, 10, "sig2", "sig1", "y");
- checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2", "y");
- checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1", "y");
- checkCanRestoreTo(true, spi2, 11, "x", "sig2", "sig1", "y");
+ // Any version okay.
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1");
- checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x");
- checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x");
- checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2");
- checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1");
- checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x", "y");
- checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x", "y");
- checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2", "y");
- checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1", "y");
- checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig1", "sig2");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig2", "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig1", "sig2");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig2", "sig1");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig1", "sig2", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig2", "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig1", "sig2", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig2", "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 11, true, "x", "sig2", "sig1", "y");
+
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "sig1", "sig2x");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "sig2", "sig1x");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "x", "sig1x", "sig2");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "x", "sig2x", "sig1");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "sig1", "sig2x", "y");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "sig2", "sig1x", "y");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "x", "sig1x", "sig2", "y");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 10, true, "x", "sig2x", "sig1", "y");
+ checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+ spi2, false, 11, true, "x", "sig2x", "sig1", "y");
+
+ checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi1, true, 10, false, "sig1");
+ checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1");
}
public void testHandlePackageDelete() {
@@ -3929,7 +3960,7 @@
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_1);
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts())
@@ -4080,7 +4111,7 @@
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDataClear(CALLING_PACKAGE_1, USER_0));
assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4099,7 +4130,7 @@
mRunningUsers.put(USER_10, true);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDataClear(CALLING_PACKAGE_2, USER_10));
assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4126,7 +4157,7 @@
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4389,7 +4420,7 @@
// Update the package.
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -4524,7 +4555,7 @@
mRunningUsers.put(USER_10, true);
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4552,11 +4583,11 @@
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
- ;
+ ;
});
// First, no changes.
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4579,7 +4610,7 @@
// Disable activity 1
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4599,7 +4630,7 @@
// Re-enable activity 1.
// Manifest shortcuts will be re-published, but dynamic ones are not.
mEnabledActivityChecker = (activity, userId) -> true;
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4617,13 +4648,13 @@
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
- ;
+ ;
});
// Disable activity 2
// Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4686,7 +4717,7 @@
setCaller(LAUNCHER_1, USER_0);
assertForLauncherCallback(mLauncherApps, () -> {
updatePackageVersion(CALLING_PACKAGE_1, 1);
- mService.mPackageMonitor.onReceive(getTestContext(),
+ mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
// Make sure the launcher gets callbacks.
@@ -4708,12 +4739,11 @@
.areAllNotDynamic()
.areAllDisabled()
.areAllPinned()
- ;
+ ;
});
}
protected void prepareForBackupTest() {
-
prepareCrossProfileDataSet();
backupAndRestore();
@@ -4749,66 +4779,45 @@
assertFileExistsWithContent("user-0/shortcut_dump/restore-4.txt");
assertFileExistsWithContent("user-0/shortcut_dump/restore-5-finish.txt");
- checkBackupAndRestore_success();
+ checkBackupAndRestore_success(/*firstRestore=*/ true);
}
public void testBackupAndRestore_backupRestoreTwice() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- dumpsysOnLogcat("Before second backup");
+ checkBackupAndRestore_success(/*firstRestore=*/ true);
+
+ // Run a backup&restore again. Note the shortcuts that weren't restored in the previous
+ // restore are disabled, so they won't be restored again.
+ dumpsysOnLogcat("Before second backup&restore");
backupAndRestore();
- dumpsysOnLogcat("After second backup");
+ dumpsysOnLogcat("After second backup&restore");
- checkBackupAndRestore_success();
- }
-
- public void testBackupAndRestore_backupRestoreMultiple() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- // This also shouldn't affect the result.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
-
- backupAndRestore();
-
- checkBackupAndRestore_success();
+ checkBackupAndRestore_success(/*firstRestore=*/ false);
}
public void testBackupAndRestore_restoreToNewVersion() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
- checkBackupAndRestore_success();
+ checkBackupAndRestore_success(/*firstRestore=*/ true);
}
public void testBackupAndRestore_restoreToSuperSetSignatures() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
// Change package signatures.
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
- checkBackupAndRestore_success();
+ checkBackupAndRestore_success(/*firstRestore=*/ true);
}
- protected void checkBackupAndRestore_success() {
+ protected void checkBackupAndRestore_success(boolean firstRestore) {
// Make sure non-system user is not restored.
final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
assertEquals(0, userP0.getAllPackagesForTest().size());
@@ -4818,12 +4827,13 @@
final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+
+ assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
PackageWithUser.of(USER_0, LAUNCHER_1)));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
PackageWithUser.of(USER_0, LAUNCHER_2)));
- assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
@@ -4835,14 +4845,16 @@
.revertToOriginalList()
.selectPinned()
- .haveIds("s1", "s2");
+ .haveIds("s1", "s2")
+ .areAllEnabled();
});
installPackage(USER_0, LAUNCHER_1);
runWithCaller(LAUNCHER_1, USER_0, () -> {
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s1");
+ .haveIds("s1")
+ .areAllEnabled();
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
.isEmpty();
@@ -4862,17 +4874,20 @@
.revertToOriginalList()
.selectPinned()
- .haveIds("s1", "s2", "s3");
+ .haveIds("s1", "s2", "s3")
+ .areAllEnabled();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s1");
+ .haveIds("s1")
+ .areAllEnabled();
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s1", "s2");
+ .haveIds("s1", "s2")
+ .areAllEnabled();
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
.isEmpty();
@@ -4910,14 +4925,28 @@
runWithCaller(LAUNCHER_2, USER_0, () -> {
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s2");
+ .haveIds("s2")
+ .areAllEnabled();
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s2", "s3");
+ .haveIds("s2", "s3")
+ .areAllEnabled();
- assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- .isEmpty();
+ if (firstRestore) {
+ assertWith(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .haveIds("s2", "s3", "s4")
+ .areAllDisabled()
+ .areAllPinned()
+ .areAllNotDynamic()
+ .areAllWithDisabledReason(
+ ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
+ } else {
+ assertWith(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .isEmpty();
+ }
assertWith(mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0))
.isEmpty();
@@ -4937,14 +4966,27 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s1");
+ .haveIds("s1")
+ .areAllEnabled();
assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
.areAllPinned()
- .haveIds("s1", "s2");
+ .haveIds("s1", "s2")
+ .areAllEnabled();
- assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- .isEmpty();
+ if (firstRestore) {
+ assertWith(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .haveIds("s1", "s2", "s3")
+ .areAllDisabled()
+ .areAllPinned()
+ .areAllNotDynamic()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
+ } else {
+ assertWith(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .isEmpty();
+ }
assertWith(mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0))
.isEmpty();
@@ -4954,45 +4996,39 @@
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertWith(getCallerVisibleShortcuts())
.areAllPinned()
- .haveIds("s1", "s2", "s3");
+ .haveIds("s1", "s2", "s3")
+ .areAllEnabled();
});
}
public void testBackupAndRestore_publisherLowerVersion() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
- checkBackupAndRestore_publisherNotRestored();
+ checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_VERSION_LOWER);
}
public void testBackupAndRestore_publisherWrongSignature() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
- checkBackupAndRestore_publisherNotRestored();
+ checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH);
}
public void testBackupAndRestore_publisherNoLongerBackupTarget() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
updatePackageInfo(CALLING_PACKAGE_1,
pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
- checkBackupAndRestore_publisherNotRestored();
+ checkBackupAndRestore_publisherNotRestored(
+ ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
}
- protected void checkBackupAndRestore_publisherNotRestored() {
+ protected void checkBackupAndRestore_publisherNotRestored(
+ int package1DisabledReason) {
installPackage(USER_0, CALLING_PACKAGE_1);
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertEquals(0, mManager.getDynamicShortcuts().size());
@@ -5014,27 +5050,31 @@
installPackage(USER_0, LAUNCHER_1);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .haveIds("s1")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(package1DisabledReason);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .haveIds("s1", "s2")
+ .areAllPinned()
+ .areAllEnabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .isEmpty();
});
installPackage(USER_0, LAUNCHER_2);
runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .haveIds("s2")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(package1DisabledReason);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .haveIds("s2", "s3")
+ .areAllPinned()
+ .areAllEnabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .isEmpty();
});
installPackage(USER_0, CALLING_PACKAGE_3);
@@ -5044,46 +5084,51 @@
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .haveIds("s1")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(package1DisabledReason);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .haveIds("s1", "s2")
+ .areAllPinned()
+ .areAllEnabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .haveIds("s1", "s2", "s3")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
});
runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .haveIds("s2")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(package1DisabledReason);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .haveIds("s2", "s3")
+ .areAllPinned()
+ .areAllEnabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .haveIds("s2", "s3", "s4")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
});
}
public void testBackupAndRestore_launcherLowerVersion() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
- checkBackupAndRestore_launcherNotRestored();
+ // Note, we restore pinned shortcuts even if the launcher is of a lower version.
+ checkBackupAndRestore_success(/*firstRestore=*/ true);
}
public void testBackupAndRestore_launcherWrongSignature() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
checkBackupAndRestore_launcherNotRestored();
@@ -5092,9 +5137,6 @@
public void testBackupAndRestore_launcherNoLongerBackupTarget() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
updatePackageInfo(LAUNCHER_1,
pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
@@ -5185,18 +5227,12 @@
assertShortcutIds(assertAllPinned(
mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
"s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
});
}
public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
prepareForBackupTest();
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
updatePackageInfo(CALLING_PACKAGE_1,
pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
@@ -5235,15 +5271,15 @@
});
installPackage(USER_0, LAUNCHER_2);
runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .areAllPinned()
+ .haveIds("s2")
+ .areAllDisabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .areAllPinned()
+ .haveIds("s2", "s3");
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ .isEmpty();
});
// Because launcher 1 wasn't restored, "s1" is no longer pinned.
@@ -5272,15 +5308,21 @@
/* empty */);
});
runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ .areAllPinned()
+ .haveIds("s2")
+ .areAllDisabled();
+ assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ .areAllPinned()
+ .haveIds("s2", "s3");
+ assertWith(
mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
+ .haveIds("s2", "s3", "s4")
+ .areAllDisabled()
+ .areAllPinned()
+ .areAllNotDynamic()
+ .areAllWithDisabledReason(
+ ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
});
}
@@ -5305,12 +5347,12 @@
final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+ assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
PackageWithUser.of(USER_0, LAUNCHER_1)));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
PackageWithUser.of(USER_0, LAUNCHER_2)));
- assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
@@ -5421,7 +5463,7 @@
.revertToOriginalList()
.selectByIds("s1", "s2")
.areAllNotDynamic()
- ;
+ ;
});
}
@@ -5555,6 +5597,287 @@
});
}
+
+ /**
+ * Restored to a lower version with no manifest shortcuts. All shortcuts are now invisible,
+ * and all calls from the publisher should ignore them.
+ */
+ public void testBackupAndRestore_disabledShortcutsAreIgnored() {
+ // Publish two manifest shortcuts.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5_altalt);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(mServiceContext,
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcutWithShortLabel("s1", "original-title"),
+ makeShortcut("s2"), makeShortcut("s3"))));
+ });
+
+ // Pin from launcher 1.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("ms1", "ms2", "ms3", "ms4", "s1", "s2"), HANDLE_USER_0);
+ });
+
+ backupAndRestore();
+
+ // Lower the version and remove the manifest shortcuts.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_0);
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
+
+ // When re-installing the app, the manifest shortcut should be re-published.
+ mService.mPackageMonitor.onReceive(mServiceContext,
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+ mService.mPackageMonitor.onReceive(mServiceContext,
+ genPackageAddIntent(LAUNCHER_1, USER_0));
+
+ // No shortcuts should be visible to the publisher.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerVisibleShortcuts())
+ .isEmpty();
+ });
+
+ final Runnable checkAllDisabledForLauncher = () -> {
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .areAllPinned()
+ .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+
+ .forShortcutWithId("s1", si -> {
+ assertEquals("original-title", si.getShortLabel());
+ })
+ .forShortcutWithId("ms1", si -> {
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title1 + "/en"
+ , si.getShortLabel());
+ })
+ .forShortcutWithId("ms2", si -> {
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title2 + "/en"
+ , si.getShortLabel());
+ })
+ .forShortcutWithId("ms3", si -> {
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title1 + "/en"
+ , si.getShortLabel());
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title2 + "/en"
+ , si.getLongLabel());
+ })
+ .forShortcutWithId("ms4", si -> {
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title2 + "/en"
+ , si.getShortLabel());
+ assertEquals("string-com.android.test.1-user:0-res:"
+ + R.string.shortcut_title2 + "/en"
+ , si.getLongLabel());
+ });
+ });
+ };
+
+ checkAllDisabledForLauncher.run();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+
+ makeCallerForeground(); // CALLING_PACKAGE_1 is now in the foreground.
+
+ // All changing API calls should be ignored.
+
+ getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+ checkAllDisabledForLauncher.run();
+
+ getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+ checkAllDisabledForLauncher.run();
+
+ getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+ checkAllDisabledForLauncher.run();
+
+ getManager().removeAllDynamicShortcuts();
+ getManager().removeDynamicShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+ checkAllDisabledForLauncher.run();
+
+ getManager().updateShortcuts(list(makeShortcutWithShortLabel("s1", "new-title")));
+ checkAllDisabledForLauncher.run();
+
+
+ // Add a shortcut -- even though ms1 was immutable, it will succeed.
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutWithShortLabel("ms1", "original-title"))));
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+
+ .selectByIds("ms1")
+ .areAllEnabled()
+ .areAllDynamic()
+ .areAllPinned()
+ .forAllShortcuts(si -> {
+ assertEquals("original-title", si.getShortLabel());
+ })
+
+ // The rest still exist and disabled.
+ .revertToOriginalList()
+ .selectByIds("ms2", "ms3", "ms4", "s1", "s2")
+ .areAllDisabled()
+ .areAllPinned()
+ ;
+ });
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcutWithShortLabel("ms2", "new-title-2"))));
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+
+ .selectByIds("ms1")
+ .areAllEnabled()
+ .areAllNotDynamic() // ms1 was not in the list, so no longer dynamic.
+ .areAllPinned()
+ .areAllMutable()
+ .forAllShortcuts(si -> {
+ assertEquals("original-title", si.getShortLabel());
+ })
+
+ .revertToOriginalList()
+ .selectByIds("ms2")
+ .areAllEnabled()
+ .areAllDynamic()
+ .areAllPinned()
+ .areAllMutable()
+ .forAllShortcuts(si -> {
+ assertEquals("new-title-2", si.getShortLabel());
+ })
+
+ // The rest still exist and disabled.
+ .revertToOriginalList()
+ .selectByIds("ms3", "ms4", "s1", "s2")
+ .areAllDisabled()
+ .areAllPinned()
+ ;
+ });
+
+ // Prepare for requestPinShortcut().
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+ mPinConfirmActivityFetcher = (packageName, userId) ->
+ new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
+
+ mManager.requestPinShortcut(
+ makeShortcutWithShortLabel("ms3", "new-title-3"),
+ /*PendingIntent=*/ null);
+
+ // Note this was pinned, so it'll be accepted right away.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .selectByIds("ms3")
+ .areAllEnabled()
+ .areAllNotDynamic()
+ .areAllPinned()
+ .areAllMutable()
+ .forAllShortcuts(si -> {
+ assertEquals("new-title-3", si.getShortLabel());
+ // The new one replaces the old manifest shortcut, so the long label
+ // should be gone now.
+ assertNull(si.getLongLabel());
+ });
+ });
+
+ // Now, change the launcher to launcher2, and request pin again.
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
+
+ reset(mServiceContext);
+
+ assertTrue(mManager.isRequestPinShortcutSupported());
+ mManager.requestPinShortcut(
+ makeShortcutWithShortLabel("ms4", "new-title-4"),
+ /*PendingIntent=*/ null);
+
+ // Initially there should be no pinned shortcuts for L2.
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .selectPinned()
+ .isEmpty();
+
+ final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+ assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT,
+ intent.getValue().getAction());
+ assertEquals(LAUNCHER_2, intent.getValue().getComponent().getPackageName());
+
+ // Check the request object.
+ final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+ assertNotNull(request);
+ assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, request.getRequestType());
+
+ assertWith(request.getShortcutInfo())
+ .haveIds("ms4")
+ .areAllOrphan()
+ .forAllShortcuts(si -> {
+ assertEquals("new-title-4", si.getShortLabel());
+ // The new one replaces the old manifest shortcut, so the long label
+ // should be gone now.
+ assertNull(si.getLongLabel());
+ });
+ assertTrue(request.accept());
+
+ assertWith(getShortcutAsLauncher(USER_0))
+ .selectPinned()
+ .haveIds("ms4")
+ .areAllEnabled();
+ });
+ });
+ }
+
+ /**
+ * Test for restoring the pre-P backup format.
+ */
+ public void testBackupAndRestore_api27format() throws Exception {
+ final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222");
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "11111");
+
+ runWithSystemUid(() -> mService.applyRestore(payload, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .areAllPinned()
+ .haveIds("s1")
+ .areAllEnabled();
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertWith(getShortcutAsLauncher(USER_0))
+ .areAllPinned()
+ .haveIds("s1")
+ .areAllEnabled();
+ });
+ // Make sure getBackupSourceVersionCode and isBackupSourceBackupAllowed
+ // are correct. We didn't have them in the old format.
+ assertEquals(8, mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0)
+ .getPackageInfo().getBackupSourceVersionCode());
+ assertTrue(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0)
+ .getPackageInfo().isBackupSourceBackupAllowed());
+
+ assertEquals(9, mService.getLauncherShortcutForTest(LAUNCHER_1, USER_0)
+ .getPackageInfo().getBackupSourceVersionCode());
+ assertTrue(mService.getLauncherShortcutForTest(LAUNCHER_1, USER_0)
+ .getPackageInfo().isBackupSourceBackupAllowed());
+
+ }
+
public void testSaveAndLoad_crossProfile() {
prepareCrossProfileDataSet();
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 3220ea9..fcdadac 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1131,7 +1131,8 @@
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
- assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
+ | ShortcutInfo.FLAG_SHADOW , si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
@@ -1198,7 +1199,8 @@
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
- assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
+ | ShortcutInfo.FLAG_SHADOW , si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
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 926a606..a4349f4 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
@@ -22,6 +22,7 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
@@ -59,9 +60,7 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
-import org.mockito.Mockito;
import org.mockito.hamcrest.MockitoHamcrest;
import java.io.BufferedReader;
@@ -898,11 +897,14 @@
public ShortcutListAsserter areAllEnabled() {
forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled()));
+ areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
return this;
}
public ShortcutListAsserter areAllDisabled() {
forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled()));
+ forAllShortcuts(s -> assertNotEquals("id=" + s.getId(),
+ ShortcutInfo.DISABLED_REASON_NOT_DISABLED, s.getDisabledReason()));
return this;
}
@@ -930,6 +932,16 @@
return this;
}
+ public ShortcutListAsserter areAllVisibleToPublisher() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isVisibleToPublisher()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllNotVisibleToPublisher() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isVisibleToPublisher()));
+ return this;
+ }
+
public ShortcutListAsserter areAllWithKeyFieldsOnly() {
forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly()));
return this;
@@ -960,6 +972,17 @@
return this;
}
+ public ShortcutListAsserter areAllWithDisabledReason(int disabledReason) {
+ forAllShortcuts(s -> assertEquals("id=" + s.getId(),
+ disabledReason, s.getDisabledReason()));
+ if (disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ areAllVisibleToPublisher();
+ } else {
+ areAllNotVisibleToPublisher();
+ }
+ return this;
+ }
+
public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
boolean found = false;
for (int i = 0; i < mList.size(); i++) {