Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96a30ab8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Improve shortcut backup & restore."

parents 27531d11 a4f89b12
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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";
  }
+8 −0
Original line number Diff line number Diff line
@@ -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";
  }
+9 −0
Original line number Diff line number Diff line
@@ -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";
  }
+169 −5
Original line number Diff line number Diff line
@@ -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 = {
@@ -157,6 +165,91 @@ public final class ShortcutInfo implements Parcelable {
    @Retention(RetentionPolicy.SOURCE)
    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.
     */
@@ -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,14 +714,24 @@ 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");

        if (isVisibleToPublisher()) {
            // Don't do this check for restore-blocked shortcuts.
            Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
        }
    }

    /**
     * Copy non-null/zero fields from another {@link ShortcutInfo}.  Only "public" information
@@ -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.
@@ -1490,6 +1627,18 @@ public final class ShortcutInfo implements Parcelable {
        clearFlags(FLAG_ICON_FILE_PENDING_SAVE);
    }

    /**
     * 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.
@@ -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;
    }
}
+37 −16
Original line number Diff line number Diff line
@@ -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);
Loading