diff options
17 files changed, 1314 insertions, 382 deletions
diff --git a/api/current.txt b/api/current.txt index ae6587d4e4e8..a2e242d68351 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11000,6 +11000,7 @@ package android.content.pm { 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 @@ package android.content.pm { 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 2bf045f2bc74..1e2ce4fa988e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -11723,6 +11723,7 @@ package android.content.pm { 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 @@ package android.content.pm { 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 25da76312b8b..3c6144830f4f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -11081,6 +11081,7 @@ package android.content.pm { 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 @@ package android.content.pm { 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 6b9c7537d710..2f78161b1729 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -18,6 +18,7 @@ package android.content.pm; 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 @@ public final class ShortcutInfo implements Parcelable { /** @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 final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { /** * @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 @@ public final class ShortcutInfo implements Parcelable { * @hide */ public void copyNonNullFieldsFrom(ShortcutInfo source) { - ensureUpdatableWith(source); + ensureUpdatableWith(source, /*isUpdating=*/ true); if (source.mActivity != null) { mActivity = source.mActivity; @@ -1169,6 +1278,19 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { } /** + * 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 @@ public final class ShortcutInfo implements Parcelable { mFlags = source.readInt(); mIconResId = source.readInt(); mLastChangedTimestamp = source.readLong(); + mDisabledReason = source.readInt(); if (source.readInt() == 0) { return; // key information only. @@ -1711,6 +1861,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeInt(mFlags); dest.writeInt(mIconResId); dest.writeLong(mLastChangedTimestamp); + dest.writeInt(mDisabledReason); if (hasKeyFieldsOnly()) { dest.writeInt(0); @@ -1808,6 +1959,11 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { addIndentOrComma(sb, indent); + sb.append("disabledReason="); + sb.append(getDisabledReasonLabel(mDisabledReason)); + + addIndentOrComma(sb, indent); + sb.append("categories="); sb.append(mCategories); @@ -1953,7 +2116,7 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 f922ad195ac2..cedf47633c78 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { } @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 @@ class ShortcutLauncher extends ShortcutPackageItem { } 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { } /** - * 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 @@ class ShortcutLauncher extends ShortcutPackageItem { } /** - * 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 @@ class ShortcutLauncher extends ShortcutPackageItem { } 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 @@ class ShortcutLauncher extends ShortcutPackageItem { @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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 6fc1e738408e..a4bec1d562bf 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } /** - * 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 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); } - addShortcutInner(newShortcut); + forceReplaceShortcutInner(newShortcut); } /** @@ -273,7 +307,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (removeList != null) { for (int i = removeList.size() - 1; i >= 0; i--) { - deleteShortcutInner(removeList.get(i)); + forceDeleteShortcutInner(removeList.get(i)); } } } @@ -281,13 +315,13 @@ class ShortcutPackage extends ShortcutPackageItem { /** * 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 @@ class ShortcutPackage extends ShortcutPackageItem { * @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 @@ class ShortcutPackage extends ShortcutPackageItem { * @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 @@ class ShortcutPackage extends ShortcutPackageItem { * 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 @@ class ShortcutPackage extends ShortcutPackageItem { @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { return oldShortcut; } else { - deleteShortcutInner(shortcutId); + forceDeleteShortcutInner(shortcutId); return null; } } @@ -381,11 +427,25 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { // 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { service.wtf("Found manifest shortcuts in excess list."); continue; } - deleteDynamicWithId(shortcut.getId()); + deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true); } } @@ -1075,7 +1157,7 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { String disabledMessage; int disabledMessageResId; String disabledMessageResName; + int disabledReason; Intent intentLegacy; PersistableBundle intentPersistableExtrasLegacy = null; ArrayList<Intent> intents = new ArrayList<>(); @@ -1408,6 +1516,7 @@ class ShortcutPackage extends ShortcutPackageItem { int iconResId; String iconResName; String bitmapPath; + int backupVersionCode; ArraySet<String> categories = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); @@ -1424,6 +1533,7 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 e5a2f5acbf89..3a9bbc8926ed 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 @@ package com.android.server.pm; 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { return mSigHashes.size() > 0; } - public boolean canRestoreTo(ShortcutService s, PackageInfo target) { - if (!s.shouldBackupApp(target)) { + //@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 (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) { // "allowBackup" was true when backed up, but now false. - Slog.w(TAG, "Can't restore: package no longer allows backup"); - return false; + Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup"); + return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED; } - if (target.versionCode < mVersionCode) { + 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; - } - if (!BackupUtils.signaturesMatch(mSigHashes, target)) { - Slog.w(TAG, "Can't restore: Package signature mismatch"); - return false; + currentPackage.versionCode, mBackupSourceVersionCode)); + return ShortcutInfo.DISABLED_REASON_VERSION_LOWER; } - return true; + return ShortcutInfo.DISABLED_REASON_NOT_DISABLED; } @VisibleForTesting @@ -132,6 +165,8 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { pw.print(prefix); pw.print(" IsShadow: "); pw.print(mIsShadow); + pw.print(mIsShadow ? " (not installed)" : " (installed)"); pw.println(); pw.print(prefix); @@ -230,6 +305,25 @@ class ShortcutPackageInfo { 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 e59d69f4c563..97b7b590514a 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 @@ package com.android.server.pm; 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 @@ abstract class ShortcutPackageItem { 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; + int restoreBlockReason; + int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; + if (!mPackageInfo.hasSignatures()) { - s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId + s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId + " but signatures not found in the restore data."); - blockRestore = true; - } - if (!blockRestore) { + restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH; + } else { 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; - } + currentVersionCode = pi.versionCode; + restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion()); } - if (blockRestore) { - onRestoreBlocked(); - } else { - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName, - mPackageUserId, getOwnerUserId())); - } - onRestored(); + 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 3cf4200b52c9..866c46c643ee 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 @@ public class ShortcutParser { (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 @@ public class ShortcutParser { 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 8a8128db9d16..3e44de989c7c 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 @@ class ShortcutRequestPinProcessor { 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 @@ class ShortcutRequestPinProcessor { // manifest shortcut.) Preconditions.checkArgument(shortcutInfo.isEnabled(), "Shortcut ID=" + shortcutInfo + " already exists but disabled."); - } private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId, @@ -463,7 +464,7 @@ class ShortcutRequestPinProcessor { 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 @@ class ShortcutRequestPinProcessor { if (original.getActivity() == null) { original.setActivity(mService.getDummyMainActivity(appPackageName)); } - ps.addOrUpdateDynamicShortcut(original); + ps.addOrReplaceDynamicShortcut(original); } // Pin the shortcut. @@ -505,13 +506,14 @@ class ShortcutRequestPinProcessor { 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 27560c5f13ce..9d8679639559 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 class ShortcutService extends IShortcutService.Stub { public Lifecycle(Context context) { super(context); + if (DEBUG) { + Binder.LOG_RUNTIME_EXCEPTION = true; + } mService = new ShortcutService(context); } @@ -738,6 +741,10 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); fillInDefaultActivity(newShortcuts); @@ -1709,12 +1718,12 @@ public class ShortcutService extends IShortcutService.Stub { } // 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } @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 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); fillInDefaultActivity(newShortcuts); @@ -1845,7 +1856,7 @@ public class ShortcutService extends IShortcutService.Stub { newShortcut.setRankChanged(); // Add it. - ps.addOrUpdateDynamicShortcut(newShortcut); + ps.addOrReplaceDynamicShortcut(newShortcut); } // Lastly, adjust the ranks. @@ -1901,6 +1912,22 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { throwIfUserLockedL(userId); final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.deleteAllDynamicShortcuts(); + ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); } packageShortcutsChanged(packageName, userId); @@ -2013,7 +2055,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isDynamic); + ShortcutInfo::isDynamicVisible); } } @@ -2027,7 +2069,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isManifestShortcut); + ShortcutInfo::isManifestVisible); } } @@ -2041,7 +2083,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isPinned); + ShortcutInfo::isPinnedVisible); } } @@ -2513,7 +2555,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { // 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 55e6d28a166e..48eccd02db64 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 @@ class ShortcutUser { 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 000000000000..266ab99cdf20 --- /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 000000000000..1476a2739bd9 --- /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 951f226676b0..d01797644560 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.ComponentName; 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.io.IOException; 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .revertToOriginalList() .selectByIds("s1", "s2") .areAllDynamic() - ; + ; }); // 3 Update the app with no manifest shortcuts. (Pinned one will survive.) @@ -3309,13 +3315,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); - 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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); - - 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(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(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"); - - 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"); + final ShortcutPackageInfo spi3 = ShortcutPackageInfo.generateForInstalledPackageForTest( + mService, CALLING_PACKAGE_3, USER_0); + + 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(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"); + + // Any version okay. + checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1"); + checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1"); + + 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .areAllNotDynamic() .areAllDisabled() .areAllPinned() - ; + ; }); } protected void prepareForBackupTest() { - prepareCrossProfileDataSet(); backupAndRestore(); @@ -4749,66 +4779,45 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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); - backupAndRestore(); - - dumpsysOnLogcat("After second backup"); - - checkBackupAndRestore_success(); - } - - public void testBackupAndRestore_backupRestoreMultiple() { - prepareForBackupTest(); + // 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"); - // 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(); + dumpsysOnLogcat("After second backup&restore"); - 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); 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 class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .revertToOriginalList() .selectByIds("s1", "s2") .areAllNotDynamic() - ; + ; }); } @@ -5555,6 +5597,287 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } + + /** + * 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 3220ea960f5d..fcdadaccd2ac 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 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { 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 926a606df294..a4349f45a897 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.assertNull; 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.hamcrest.Matcher; 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 class ShortcutManagerTestUtils { 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 @@ public class ShortcutManagerTestUtils { 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 @@ public class ShortcutManagerTestUtils { 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++) { |