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

Commit a4f89b12 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Improve shortcut backup & restore.

From http://go/p-shortcut-restore :

Currently on pre-P builds, there are 6 cases where shortcuts won't be restored.

Launcher issue:
A: Launcher is downgraded
B: Launcher has allowBackup="false"
C: Launcher's signature changed.
A-C would result in not restoring any shortcuts from any apps.

App issue:
D: App is downgraded
E: App has allowBackup="false"
F: App's signature changed.
D-F would result in not restoring any shortcuts from individual apps.

In P, we'll restore all pinned shortcuts anyway except in case B and C.

The new behavior would be:
A: We'll restore all pinned shortcuts anyway even if the launcher is downgraded.
B: If the launcher doesn't support backup&restore, we still don't restore shortcuts.
C: If the launcher has a different signature, we still don't restore shortcuts.

D, E, F: All pinned shortcuts will be restored, but are disabled.

In case of D, E, F, shortcuts would be:
1. ShortcutInfo.isEnabled() will return false. They are not launchable.
2. If it's case D (downgrade), shortcuts will be re-enabled once a publisher app
is updated to the original version or higher.
3. getDisabledMessage() will return a special, canned message explaining why
they're disabled.
4. A new API getDisabledReason() will return one of the following values:
 - DISABLED_REASON_NOT_DISABLED (for non-disabled shortcuts)
 - DISABLED_REASON_BY_APP (for shortcuts disabled by apps)
 - DISABLED_REASON_APP_CHANGED (for shortcuts disabled when a manifest shortcut is gone.)
 - DISABLED_REASON_VERSION_LOWER (case D)
 - DISABLED_REASON_BACKUP_NOT_SUPPORTED (case E)
 - DISABLED_REASON_SIGNATURE_MISMATCH (case F)
 - (DISABLE_REASON_OTHER_RESTORE_ISSUE for future use)
Launcher can opt to use this API to show a custom error message, rather than
using the canned system message from getDisabledMessage().

5. This spec change is completely transparent to publisher apps.
In case D,E,F, the disabled shortcuts will only be visible to the launcher,
and from the publisher app's point of view, they still don't exist.
APIs such as getPinnedShortcuts() will not return them, and updateShortcuts()
will not affect them. The enableShortcut() will not enable them either.

Bug 62451035
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest1 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest2 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest3 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest4 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest5 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest6 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest7 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest8 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest9 -w com.android.frameworks.servicestests
Test: adb shell am instrument -w -e class com.android.server.pm.ShortcutManagerTest10 -w com.android.frameworks.servicestests
Test: cts-tradefed run cts-dev --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -l INFO -m CtsShortcutManagerTestCases
Test: cts-tradefed run cts-dev --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -l INFO -m CtsShortcutHostTestCases

Change-Id: Ib4d73aa44a0395f5e37c8ad0c9cd041a9038fb66
parent d5ad2ff5
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -10999,6 +10999,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();
@@ -11017,6 +11018,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
@@ -11722,6 +11722,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();
@@ -11740,6 +11741,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
@@ -11082,6 +11082,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();
@@ -11098,8 +11099,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