Merge "ShortcutManager: implement backup & restore" into nyc-dev
diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java
new file mode 100644
index 0000000..0b3f2ae
--- /dev/null
+++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.content.Context;
+import android.content.pm.IShortcutService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class ShortcutBackupHelper extends BlobBackupHelper {
+    private static final String TAG = "ShortcutBackupAgent";
+    private static final int BLOB_VERSION = 1;
+
+    private static final String KEY_USER_FILE = "shortcutuser.xml";
+
+    public ShortcutBackupHelper() {
+        super(BLOB_VERSION, KEY_USER_FILE);
+    }
+
+    private IShortcutService getShortcutService() {
+        return IShortcutService.Stub.asInterface(
+                ServiceManager.getService(Context.SHORTCUT_SERVICE));
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        switch (key) {
+            case KEY_USER_FILE:
+                try {
+                    return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Backup failed", e);
+                }
+                break;
+            default:
+                Slog.w(TAG, "Unknown key: " + key);
+        }
+        return null;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        switch (key) {
+            case KEY_USER_FILE:
+                try {
+                    getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Restore failed", e);
+                }
+                break;
+            default:
+                Slog.w(TAG, "Unknown key: " + key);
+        }
+    }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 181ed51..2d12fcd 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,9 +17,9 @@
 package com.android.server.backup;
 
 import android.app.IWallpaperManager;
+import android.app.backup.BackupAgentHelper;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupAgentHelper;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
@@ -48,6 +48,7 @@
     private static final String NOTIFICATION_HELPER = "notifications";
     private static final String PERMISSION_HELPER = "permissions";
     private static final String USAGE_STATS_HELPER = "usage_stats";
+    private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -100,6 +101,7 @@
         addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
         addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
         addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+        addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         super.onBackup(oldState, data, newState);
     }
 
@@ -138,6 +140,7 @@
         addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
         addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
         addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+        addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
 
         try {
             super.onRestore(data, appVersionCode, newState);
diff --git a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
deleted file mode 100644
index a0fbc37..0000000
--- a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.app.backup.BlobBackupHelper;
-
-public class ShortcutBackupAgent extends BlobBackupHelper {
-    private static final String TAG = "ShortcutBackupAgent";
-    private static final int BLOB_VERSION = 1;
-
-    public ShortcutBackupAgent(int currentBlobVersion, String... keys) {
-        super(currentBlobVersion, keys);
-    }
-
-    @Override
-    protected byte[] getBackupPayload(String key) {
-        throw new RuntimeException("not implemented yet"); // todo
-    }
-
-    @Override
-    protected void applyRestoredPayload(String key, byte[] payload) {
-        throw new RuntimeException("not implemented yet"); // todo
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 7699f30..c6d66fe 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,10 @@
 import android.content.pm.ShortcutInfo;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +31,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -43,15 +48,16 @@
     private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_PACKAGE_NAME = "package-name";
+    private static final String ATTR_PACKAGE_USER_ID = "package-user";
 
     private final int mOwnerUserId;
 
     /**
      * Package name -> IDs.
      */
-    final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+    final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
 
-    public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+    private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
             @UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
         super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
         mOwnerUserId = ownerUserId;
@@ -59,7 +65,7 @@
 
     public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
             @UserIdInt int launcherUserId) {
-        this(launcherUserId, packageName, launcherUserId, null);
+        this(ownerUserId, packageName, launcherUserId, null);
     }
 
     @Override
@@ -67,16 +73,38 @@
         return mOwnerUserId;
     }
 
+    /**
+     * Called when the new package can't receive the backup, due to signature or version mismatch.
+     */
+    @Override
+    protected void onRestoreBlocked(ShortcutService s) {
+        final ArrayList<PackageWithUser> pinnedPackages =
+                new ArrayList<>(mPinnedShortcuts.keySet());
+        mPinnedShortcuts.clear();
+        for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
+            final PackageWithUser pu = pinnedPackages.get(i);
+            s.getPackageShortcutsLocked(pu.packageName, pu.userId)
+                    .refreshPinnedFlags(s);
+        }
+    }
+
+    @Override
+    protected void onRestored(ShortcutService s) {
+        // Nothing to do.
+    }
+
     public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
             @NonNull String packageName, @NonNull List<String> ids) {
         final ShortcutPackage packageShortcuts =
                 s.getPackageShortcutsLocked(packageName, packageUserId);
 
+        final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+
         final int idSize = ids.size();
         if (idSize == 0) {
-            mPinnedShortcuts.remove(packageName);
+            mPinnedShortcuts.remove(pu);
         } else {
-            final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
+            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.
@@ -93,7 +121,7 @@
                     newSet.add(id);
                 }
             }
-            mPinnedShortcuts.put(packageName, newSet);
+            mPinnedShortcuts.put(pu, newSet);
         }
         packageShortcuts.refreshPinnedFlags(s);
     }
@@ -101,12 +129,13 @@
     /**
      * Return the pinned shortcut IDs for the publisher package.
      */
-    public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
-        return mPinnedShortcuts.get(packageName);
+    public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
+            @UserIdInt int packageUserId) {
+        return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
     }
 
-    boolean cleanUpPackage(String packageName) {
-        return mPinnedShortcuts.remove(packageName) != null;
+    boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
+        return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
     }
 
     /**
@@ -126,9 +155,15 @@
         getPackageInfo().saveToXml(out);
 
         for (int i = 0; i < size; i++) {
+            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
+            if (forBackup && (pu.userId != getOwnerUserId())) {
+                continue; // Target package on a different user, skip. (i.e. work profile)
+            }
+
             out.startTag(null, TAG_PACKAGE);
-            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
-                    mPinnedShortcuts.keyAt(i));
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -157,8 +192,6 @@
         final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
                 launcherUserId);
 
-        ShortcutPackageInfo spi = null;
-
         ArraySet<String> ids = null;
         final int outerDepth = parser.getDepth();
         int type;
@@ -172,13 +205,17 @@
             if (depth == outerDepth + 1) {
                 switch (tag) {
                     case ShortcutPackageInfo.TAG_ROOT:
-                        spi = ShortcutPackageInfo.loadFromXml(parser);
+                        ret.getPackageInfo().loadFromXml(parser, fromBackup);
                         continue;
                     case TAG_PACKAGE: {
                         final String packageName = ShortcutService.parseStringAttribute(parser,
                                 ATTR_PACKAGE_NAME);
+                        final int packageUserId = fromBackup ? ownerUserId
+                                : ShortcutService.parseIntAttribute(parser,
+                                ATTR_PACKAGE_USER_ID, ownerUserId);
                         ids = new ArraySet<>();
-                        ret.mPinnedShortcuts.put(packageName, ids);
+                        ret.mPinnedShortcuts.put(
+                                PackageWithUser.of(packageUserId, packageName), ids);
                         continue;
                     }
                 }
@@ -186,17 +223,17 @@
             if (depth == outerDepth + 2) {
                 switch (tag) {
                     case TAG_PIN: {
-                        ids.add(ShortcutService.parseStringAttribute(parser,
-                                ATTR_VALUE));
+                        if (ids == null) {
+                            Slog.w(TAG, TAG_PIN + " in invalid place");
+                        } else {
+                            ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
+                        }
                         continue;
                     }
                 }
             }
             ShortcutService.warnForInvalidTag(depth, tag);
         }
-        if (spi != null) {
-            ret.replacePackageInfo(spi);
-        }
         return ret;
     }
 
@@ -208,6 +245,8 @@
         pw.print(getPackageName());
         pw.print("  Package user: ");
         pw.print(getPackageUserId());
+        pw.print("  Owner user: ");
+        pw.print(getOwnerUserId());
         pw.println();
 
         getPackageInfo().dump(s, pw, prefix + "  ");
@@ -217,10 +256,14 @@
         for (int i = 0; i < size; i++) {
             pw.println();
 
+            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
             pw.print(prefix);
             pw.print("  ");
             pw.print("Package: ");
-            pw.println(mPinnedShortcuts.keyAt(i));
+            pw.print(pu.packageName);
+            pw.print("  User: ");
+            pw.println(pu.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -233,4 +276,9 @@
             }
         }
     }
+
+    @VisibleForTesting
+    ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
+        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 5916202..1076a7a 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -26,6 +26,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -34,6 +36,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -83,7 +86,7 @@
      */
     private long mLastResetTime;
 
-    public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
+    private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
         super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
     }
 
@@ -97,6 +100,19 @@
         return getPackageUserId();
     }
 
+    @Override
+    protected void onRestoreBlocked(ShortcutService s) {
+        // Can't restore due to version/signature mismatch.  Remove all shortcuts.
+        mShortcuts.clear();
+    }
+
+    @Override
+    protected void onRestored(ShortcutService s) {
+        // Because some launchers may not have been restored (e.g. allowBackup=false),
+        // we need to re-calculate the pinned shortcuts.
+        refreshPinnedFlags(s);
+    }
+
     /**
      * Note this does *not* provide a correct view to the calling launcher.
      */
@@ -229,20 +245,26 @@
                 s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
 
         for (int l = launchers.size() - 1; l >= 0; l--) {
+            // Note even if a launcher that hasn't been installed can still pin shortcuts.
+
             final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
             final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
-                    getPackageName());
+                    getPackageName(), getPackageUserId());
 
             if (pinned == null || pinned.size() == 0) {
                 continue;
             }
             for (int i = pinned.size() - 1; i >= 0; i--) {
-                final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
+                final String id = pinned.valueAt(i);
+                final ShortcutInfo si = mShortcuts.get(id);
                 if (si == null) {
-                    s.wtf("Shortcut not found");
-                } else {
-                    si.addFlags(ShortcutInfo.FLAG_PINNED);
+                    // This happens if a launcher pinned shortcuts from this package, then backup&
+                    // restored, but this package doesn't allow backing up.
+                    // In that case the launcher ends up having a dangling pinned shortcuts.
+                    // That's fine, when the launcher is restored, we'll fix it.
+                    continue;
                 }
+                si.addFlags(ShortcutInfo.FLAG_PINNED);
             }
         }
 
@@ -312,11 +334,15 @@
     public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
             @Nullable String callingLauncher, int launcherUserId) {
+        if (getPackageInfo().isShadow()) {
+            // Restored and the app not installed yet, so don't return any.
+            return;
+        }
 
         // Set of pinned shortcuts by the calling launcher.
         final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
-                : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
-                    .getPinnedShortcutIds(getPackageName());
+                : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
+                    .getPinnedShortcutIds(getPackageName(), getPackageUserId());
 
         for (int i = 0; i < mShortcuts.size(); i++) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -328,7 +354,8 @@
                     || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
             if (!si.isDynamic()) {
                 if (!si.isPinned()) {
-                    s.wtf("Shortcut not pinned here");
+                    s.wtf("Shortcut not pinned: package " + getPackageName()
+                            + ", user=" + getPackageUserId() + ", id=" + si.getId());
                     continue;
                 }
                 if (!isPinnedByCaller) {
@@ -479,7 +506,6 @@
                 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
         ret.mLastResetTime =
                 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
-        ShortcutPackageInfo spi = null;
 
         final int outerDepth = parser.getDepth();
         int type;
@@ -493,7 +519,7 @@
             if (depth == outerDepth + 1) {
                 switch (tag) {
                     case ShortcutPackageInfo.TAG_ROOT:
-                        spi = ShortcutPackageInfo.loadFromXml(parser);
+                        ret.getPackageInfo().loadFromXml(parser, fromBackup);
                         continue;
                     case TAG_SHORTCUT:
                         final ShortcutInfo si = parseShortcut(parser, packageName);
@@ -505,9 +531,6 @@
             }
             ShortcutService.warnForInvalidTag(depth, tag);
         }
-        if (spi != null) {
-            ret.replacePackageInfo(spi);
-        }
         return ret;
     }
 
@@ -567,4 +590,9 @@
                 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
                 iconRes, bitmapPath);
     }
+
+    @VisibleForTesting
+    List<ShortcutInfo> getAllShortcutsForTest() {
+        return new ArrayList<>(mShortcuts.values());
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 5f706b8..2c45890 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -67,10 +67,6 @@
         return mIsShadow;
     }
 
-    public boolean isInstalled() {
-        return !mIsShadow;
-    }
-
     public void setShadow(boolean shadow) {
         mIsShadow = shadow;
     }
@@ -79,14 +75,24 @@
         return mVersionCode;
     }
 
-    public boolean canRestoreTo(PackageInfo target) {
+    public boolean hasSignatures() {
+        return mSigHashes.size() > 0;
+    }
+
+    public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
+        if (!s.shouldBackupApp(target)) {
+            // "allowBackup" was true when backed up, but now false.
+            Slog.w(TAG, "Can't restore: package no longer allows backup");
+            return false;
+        }
         if (target.versionCode < mVersionCode) {
-            Slog.w(TAG, String.format("Package current version %d < backed up version %d",
+            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, "Package signature mismtach");
+            Slog.w(TAG, "Can't restore: Package signature mismatch");
             return false;
         }
         return true;
@@ -106,6 +112,11 @@
     }
 
     public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+        if (mIsShadow) {
+            s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
+                    + ", user=" + pkg.getOwnerUserId());
+            return;
+        }
         // Note use mUserId here, rather than userId.
         final PackageInfo pi = s.getPackageInfoWithSignatures(
                 pkg.getPackageName(), pkg.getPackageUserId());
@@ -132,15 +143,17 @@
         out.endTag(null, TAG_ROOT);
     }
 
-    public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
+    public void loadFromXml(XmlPullParser parser, boolean fromBackup)
             throws IOException, XmlPullParserException {
 
         final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
-        final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
+
+        // When restoring from backup, it's always shadow.
+        final boolean shadow =
+                fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
 
         final ArrayList<byte[]> hashes = new ArrayList<>();
 
-
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -163,7 +176,11 @@
             }
             ShortcutService.warnForInvalidTag(depth, tag);
         }
-        return new ShortcutPackageInfo(versionCode, hashes, shadow);
+
+        // Successfully loaded; replace the feilds.
+        mVersionCode = versionCode;
+        mIsShadow = shadow;
+        mSigHashes = hashes;
     }
 
     public void dump(ShortcutService s, PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index de2709d..f31dd17 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,6 +16,8 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
 
@@ -25,10 +27,12 @@
 import java.io.IOException;
 
 abstract class ShortcutPackageItem {
+    private static final String TAG = ShortcutService.TAG;
+
     private final int mPackageUserId;
     private final String mPackageName;
 
-    private ShortcutPackageInfo mPackageInfo;
+    private final ShortcutPackageInfo mPackageInfo;
 
     protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
             @NonNull ShortcutPackageInfo packageInfo) {
@@ -61,25 +65,62 @@
         return mPackageInfo;
     }
 
-    /**
-     * Should be only used when loading from a file.o
-     */
-    protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
-        mPackageInfo = Preconditions.checkNotNull(packageInfo);
-    }
-
     public void refreshPackageInfoAndSave(ShortcutService s) {
+        if (mPackageInfo.isShadow()) {
+            return; // Don't refresh for shadow user.
+        }
         mPackageInfo.refresh(s, this);
         s.scheduleSaveUser(getOwnerUserId());
     }
 
-    public void ensureNotShadowAndSave(ShortcutService s) {
-        if (mPackageInfo.isShadow()) {
-            mPackageInfo.setShadow(false);
-            s.scheduleSaveUser(getOwnerUserId());
+    public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
+        if (!mPackageInfo.isShadow()) {
+            return; // Already installed, nothing to do.
         }
+        if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
+            if (ShortcutService.DEBUG) {
+                Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+                        mPackageName, mPackageUserId));
+            }
+            return; // Not installed, no need to restore yet.
+        }
+        if (!mPackageInfo.hasSignatures()) {
+            s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+                    + " but signatures not found in the restore data.");
+            onRestoreBlocked(s);
+            return;
+        }
+
+        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.
+            onRestoreBlocked(s);
+            return;
+        }
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
+                    mPackageUserId, getOwnerUserId()));
+        }
+
+        onRestored(s);
+
+        // Now the package is not 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(ShortcutService s);
+
+    /**
+     * Called when the new package is successfully restored.
+     */
+    protected abstract void onRestored(ShortcutService s);
+
     public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException;
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 76a2dfa..7aefcb8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -46,6 +46,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
@@ -101,6 +102,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -112,17 +114,16 @@
  *
  * - Scan and remove orphan bitmaps (just in case).
  *
- * - Backup & restore
- *
  * - Detect when already registered instances are passed to APIs again, which might break
  *   internal bitmap handling.
+ *
+ * - Add more call stats.
  */
 public class ShortcutService extends IShortcutService.Stub {
     static final String TAG = "ShortcutService";
 
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
-    static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
 
     @VisibleForTesting
     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -262,6 +263,26 @@
             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
             | PackageManager.MATCH_UNINSTALLED_PACKAGES;
 
+    // Stats
+    @VisibleForTesting
+    interface Stats {
+        int GET_DEFAULT_HOME = 0;
+        int GET_PACKAGE_INFO = 1;
+        int GET_PACKAGE_INFO_WITH_SIG = 2;
+        int GET_APPLICATION_INFO = 3;
+        int LAUNCHER_PERMISSION_CHECK = 4;
+
+        int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
+    }
+
+    final Object mStatLock = new Object();
+
+    @GuardedBy("mStatLock")
+    private final int[] mCountStats = new int[Stats.COUNT];
+
+    @GuardedBy("mStatLock")
+    private final long[] mDurationStats = new long[Stats.COUNT];
+
     public ShortcutService(Context context) {
         this(context, BackgroundThread.get().getLooper());
     }
@@ -278,6 +299,13 @@
         mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
     }
 
+    void logDurationStat(int statId, long start) {
+        synchronized (mStatLock) {
+            mCountStats[statId]++;
+            mDurationStats[statId] += (System.currentTimeMillis() - start);
+        }
+    }
+
     /**
      * System service lifecycle.
      */
@@ -822,7 +850,7 @@
 
     @GuardedBy("mLock")
     @NonNull
-    boolean isUserLoadedLocked(@UserIdInt int userId) {
+    private boolean isUserLoadedLocked(@UserIdInt int userId) {
         return mUsers.get(userId) != null;
     }
 
@@ -841,19 +869,27 @@
         return userPackages;
     }
 
+    void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
+        for (int i = mUsers.size() - 1; i >= 0; i--) {
+            c.accept(mUsers.valueAt(i));
+        }
+    }
+
     /** Return the per-user per-package state. */
     @GuardedBy("mLock")
     @NonNull
     ShortcutPackage getPackageShortcutsLocked(
             @NonNull String packageName, @UserIdInt int userId) {
-        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
+        return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
     }
 
     @GuardedBy("mLock")
     @NonNull
-    ShortcutLauncher getLauncherShortcuts(
-            @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
-        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
+    ShortcutLauncher getLauncherShortcutsLocked(
+            @NonNull String packageName, @UserIdInt int ownerUserId,
+            @UserIdInt int launcherUserId) {
+        return getUserShortcutsLocked(ownerUserId)
+                .getLauncherShortcuts(this, packageName, launcherUserId);
     }
 
     // === Caller validation ===
@@ -1212,8 +1248,6 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
-            ps.ensureNotShadowAndSave(this);
-
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1249,8 +1283,6 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
-            ps.ensureNotShadowAndSave(this);
-
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1288,8 +1320,6 @@
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
-            ps.ensureNotShadowAndSave(this);
-
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1422,15 +1452,17 @@
     @VisibleForTesting
     boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
         synchronized (mLock) {
-            long start = System.currentTimeMillis();
+            final long start = System.currentTimeMillis();
 
             final ShortcutUser user = getUserShortcutsLocked(userId);
 
             final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
 
             // Default launcher from package manager.
+            final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
             final ComponentName defaultLauncher = injectPackageManagerInternal()
                     .getHomeActivitiesAsUser(allHomeCandidates, userId);
+            logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
 
             ComponentName detected;
             if (defaultLauncher != null) {
@@ -1473,10 +1505,8 @@
                     lastPriority = ri.priority;
                 }
             }
-            final long end = System.currentTimeMillis();
-            if (DEBUG) {
-                Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
-            }
+            logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
+
             if (detected != null) {
                 if (DEBUG) {
                     Slog.v(TAG, "Detected launcher: " + detected);
@@ -1492,10 +1522,17 @@
 
     // === House keeping ===
 
+    /**
+     * Remove all the information associated with a package.  This will really remove all the
+     * information, including the restore information (i.e. it'll remove packages even if they're
+     * shadow).
+     */
     @VisibleForTesting
     void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
-
-        // TODO Don't remove shadow packages' information.
+        if (isPackageInstalled(packageName, packageUserId)) {
+            wtf("Package " + packageName + " is still installed for user " + packageUserId);
+            return;
+        }
 
         final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
 
@@ -1504,7 +1541,7 @@
 
         // First, remove the package from the package list (if the package is a publisher).
         if (packageUserId == owningUserId) {
-            if (mUser.getPackages().remove(packageName) != null) {
+            if (mUser.removePackage(packageName) != null) {
                 doNotify = true;
             }
         }
@@ -1515,12 +1552,12 @@
         // Then remove pinned shortcuts from all launchers.
         final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
         for (int i = launchers.size() - 1; i >= 0; i--) {
-            launchers.valueAt(i).cleanUpPackage(packageName);
+            launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
         }
         // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
         // step.  Remove them too.
-        for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
-            mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
+        for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) {
+            mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this);
         }
 
         scheduleSaveUser(owningUserId);
@@ -1539,6 +1576,7 @@
      * Entry point from {@link LauncherApps}.
      */
     private class LocalService extends ShortcutServiceInternal {
+
         @Override
         public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
@@ -1551,13 +1589,16 @@
                             : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
 
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
                             callingPackage, packageName, changedSince,
                             componentName, queryFlags, userId, ret, cloneFlag);
                 } else {
                     final ArrayMap<String, ShortcutPackage> packages =
-                            getUserShortcutsLocked(userId).getPackages();
+                            getUserShortcutsLocked(userId).getAllPackages();
                     for (int i = packages.size() - 1; i >= 0; i--) {
                         getShortcutsInnerLocked(launcherUserId,
                                 callingPackage, packages.keyAt(i), changedSince,
@@ -1601,6 +1642,9 @@
             final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
             final ArraySet<String> idSet = new ArraySet<>(ids);
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 getPackageShortcutsLocked(packageName, userId).findAll(
                         ShortcutService.this, ret,
                         (ShortcutInfo si) -> idSet.contains(si.getId()),
@@ -1616,13 +1660,16 @@
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 final ShortcutInfo si = getShortcutInfoLocked(
                         launcherUserId, callingPackage, packageName, shortcutId, userId);
                 return si != null && si.isPinned();
             }
         }
 
-        public ShortcutInfo getShortcutInfoLocked(
+        private ShortcutInfo getShortcutInfoLocked(
                 int launcherUserId, @NonNull String callingPackage,
                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
             Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1646,9 +1693,8 @@
 
             synchronized (mLock) {
                 final ShortcutLauncher launcher =
-                        getLauncherShortcuts(callingPackage, userId, launcherUserId);
-
-                launcher.ensureNotShadowAndSave(ShortcutService.this);
+                        getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
+                launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
 
                 launcher.pinShortcuts(
                         ShortcutService.this, userId, packageName, shortcutIds);
@@ -1665,6 +1711,9 @@
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
 
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 // Make sure the shortcut is actually visible to the launcher.
                 final ShortcutInfo si = getShortcutInfoLocked(
                         launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1690,6 +1739,9 @@
             Preconditions.checkNotNull(shortcut, "shortcut");
 
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
                         shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
                 return (shortcutInfo != null && shortcutInfo.hasIconResource())
@@ -1704,6 +1756,9 @@
             Preconditions.checkNotNull(shortcutIn, "shortcut");
 
             synchronized (mLock) {
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
                         shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
                 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
@@ -1757,28 +1812,28 @@
      * perform cleanup.
      */
     @VisibleForTesting
-    void cleanupGonePackages(@UserIdInt int userId) {
+    void cleanupGonePackages(@UserIdInt int ownerUserId) {
         if (DEBUG) {
-            Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
+            Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
         }
         final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
 
         synchronized (mLock) {
-            final ShortcutUser user = getUserShortcutsLocked(userId);
+            final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
 
             user.forAllPackageItems(spi -> {
                 if (spi.getPackageInfo().isShadow()) {
                     return; // Don't delete shadow information.
                 }
                 if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
-                    return;
+                    return; // Package not gone.
                 }
                 gonePackages.add(PackageWithUser.of(spi));
             });
             if (gonePackages.size() > 0) {
                 for (int i = gonePackages.size() - 1; i >= 0; i--) {
                     final PackageWithUser pu = gonePackages.get(i);
-                    cleanUpPackageLocked(pu.packageName, userId, pu.userId);
+                    cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
                 }
             }
         }
@@ -1789,7 +1844,8 @@
             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
         }
         synchronized (mLock) {
-            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+            forEachLoadedUserLocked(user ->
+                    user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
         }
     }
 
@@ -1798,18 +1854,20 @@
             Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
                     packageName, userId));
         }
-
         synchronized (mLock) {
-            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+            forEachLoadedUserLocked(user ->
+                    user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
         }
     }
 
-    private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
+    private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
         if (DEBUG) {
-            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
+            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
+                    packageUserId));
         }
         synchronized (mLock) {
-            cleanUpPackageLocked(packageName, userId, userId);
+            forEachLoadedUserLocked(user ->
+                cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
         }
     }
 
@@ -1836,6 +1894,7 @@
     @VisibleForTesting
     PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
+        final long start = System.currentTimeMillis();
         final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -1847,11 +1906,16 @@
             return null;
         } finally {
             injectRestoreCallingIdentity(token);
+
+            logDurationStat(
+                    (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
+                    start);
         }
     }
 
     @VisibleForTesting
     ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+        final long start = System.currentTimeMillis();
         final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -1861,6 +1925,8 @@
             return null;
         } finally {
             injectRestoreCallingIdentity(token);
+
+            logDurationStat(Stats.GET_APPLICATION_INFO, start);
         }
     }
 
@@ -1869,7 +1935,7 @@
         return (ai != null) && ((ai.flags & flags) == flags);
     }
 
-    private boolean isPackageInstalled(String packageName, int userId) {
+    boolean isPackageInstalled(String packageName, int userId) {
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
     }
 
@@ -1879,8 +1945,12 @@
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
     }
 
+    boolean shouldBackupApp(PackageInfo pi) {
+        return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+    }
+
     @Override
-    public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
+    public byte[] getBackupPayload(@UserIdInt int userId) {
         enforceSystem();
         if (DEBUG) {
             Slog.d(TAG, "Backing up user " + userId);
@@ -1908,7 +1978,7 @@
     }
 
     @Override
-    public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
+    public void applyRestore(byte[] payload, @UserIdInt int userId) {
         enforceSystem();
         if (DEBUG) {
             Slog.d(TAG, "Restoring user " + userId);
@@ -1923,6 +1993,15 @@
         }
         synchronized (mLock) {
             mUsers.put(userId, user);
+
+            // Then purge all the save images.
+            final File bitmapPath = getUserBitmapFilePath(userId);
+            final boolean success = FileUtils.deleteContents(bitmapPath);
+            if (!success) {
+                Slog.w(TAG, "Failed to delete " + bitmapPath);
+            }
+
+            saveUserLocked(userId);
         }
     }
 
@@ -1974,9 +2053,19 @@
             pw.print("  Icon format: ");
             pw.print(mIconPersistFormat);
             pw.print("  Icon quality: ");
-            pw.print(mIconPersistQuality);
+            pw.println(mIconPersistQuality);
             pw.println();
 
+            pw.println("  Stats:");
+            synchronized (mStatLock) {
+                final String p = "     ";
+                dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
+                dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
+
+                dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
+                dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
+                dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
+            }
 
             for (int i = 0; i < mUsers.size(); i++) {
                 pw.println();
@@ -1991,6 +2080,15 @@
         return tobj.format("%Y-%m-%d %H:%M:%S");
     }
 
+    private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
+        pw.print(prefix);
+        final int count = mCountStats[statId];
+        final long dur = mDurationStats[statId];
+        pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
+                label, count, dur,
+                (count == 0 ? 0 : ((double) dur) / count)));
+    }
+
     // === Shell support ===
 
     @Override
@@ -2202,9 +2300,10 @@
     }
 
     final void wtf(String message) {
-        Slog.wtf(TAG, message, /* exception= */ null);
+        wtf( message, /* exception= */ null);
     }
 
+    // Injection point.
     void wtf(String message, Exception e) {
         Slog.wtf(TAG, message, e);
     }
@@ -2275,7 +2374,7 @@
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
 
-            final ShortcutPackage pkg = user.getPackages().get(packageName);
+            final ShortcutPackage pkg = user.getAllPackages().get(packageName);
             if (pkg == null) return null;
 
             return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 487558f..593f607 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -53,8 +53,8 @@
             this.packageName = Preconditions.checkNotNull(packageName);
         }
 
-        public static PackageWithUser of(int launcherUserId, String packageName) {
-            return new PackageWithUser(launcherUserId, packageName);
+        public static PackageWithUser of(int userId, String packageName) {
+            return new PackageWithUser(userId, packageName);
         }
 
         public static PackageWithUser of(ShortcutPackageItem spi) {
@@ -78,12 +78,12 @@
 
         @Override
         public String toString() {
-            return String.format("{Launcher: %d, %s}", userId, packageName);
+            return String.format("{Package: %d, %s}", userId, packageName);
         }
     }
 
     @UserIdInt
-    final int mUserId;
+    private final int mUserId;
 
     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
 
@@ -95,10 +95,18 @@
         mUserId = userId;
     }
 
-    public ArrayMap<String, ShortcutPackage> getPackages() {
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ArrayMap<String, ShortcutPackage> getAllPackages() {
         return mPackages;
     }
 
+    public ShortcutPackage removePackage(@NonNull String packageName) {
+        return mPackages.remove(packageName);
+    }
+
     public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
         return mLaunchers;
     }
@@ -113,22 +121,26 @@
         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
     }
 
-    public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+    public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
         ShortcutPackage ret = mPackages.get(packageName);
         if (ret == null) {
             ret = new ShortcutPackage(mUserId, packageName);
             mPackages.put(packageName, ret);
+        } else {
+            ret.attemptToRestoreIfNeededAndSave(s);
         }
         return ret;
     }
 
-    public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
+    public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
             @UserIdInt int launcherUserId) {
         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
         ShortcutLauncher ret = mLaunchers.get(key);
         if (ret == null) {
             ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
             mLaunchers.put(key, ret);
+        } else {
+            ret.attemptToRestoreIfNeededAndSave(s);
         }
         return ret;
     }
@@ -148,14 +160,6 @@
         }
     }
 
-    public void unshadowPackage(ShortcutService s, @NonNull String packageName,
-            @UserIdInt int packageUserId) {
-        forPackageItem(packageName, packageUserId, spi -> {
-            Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
-            spi.ensureNotShadowAndSave(s);
-        });
-    }
-
     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
             Consumer<ShortcutPackageItem> callback) {
         forAllPackageItems(spi -> {
@@ -166,6 +170,13 @@
         });
     }
 
+    public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
+            @UserIdInt int packageUserId) {
+        forPackageItem(packageName, packageUserId, spi -> {
+            spi.attemptToRestoreIfNeededAndSave(s);
+        });
+    }
+
     public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException {
         out.startTag(null, TAG_ROOT);
@@ -229,7 +240,7 @@
                                 s, parser, userId, fromBackup);
 
                         // Don't use addShortcut(), we don't need to save the icon.
-                        ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
+                        ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
                         continue;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
deleted file mode 100644
index c44ffa4..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
+++ /dev/null
@@ -1,271 +0,0 @@
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.PersistableBundle;
-import android.test.AndroidTestCase;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.testutis.TestUtils;
-
-/**
- * Tests for {@link ShortcutInfo}.
-
- m FrameworksServicesTests &&
- adb install \
-   -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
- adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
-   -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
-
- */
-public class ShortcutInfoTest extends AndroidTestCase {
-
-    public void testMissingMandatoryFields() {
-        TestUtils.assertExpectException(
-                IllegalArgumentException.class,
-                "ID must be provided",
-                () -> new ShortcutInfo.Builder(mContext).build());
-        TestUtils.assertExpectException(
-                IllegalArgumentException.class,
-                "title must be provided",
-                () -> new ShortcutInfo.Builder(mContext).setId("id").build()
-                        .enforceMandatoryFields());
-        TestUtils.assertExpectException(
-                NullPointerException.class,
-                "Intent must be provided",
-                () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
-                        .enforceMandatoryFields());
-    }
-
-    private ShortcutInfo parceled(ShortcutInfo si) {
-        Parcel p = Parcel.obtain();
-        p.writeParcelable(si, 0);
-        p.setDataPosition(0);
-        ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
-        p.recycle();
-        return si2;
-    }
-
-    private Intent makeIntent(String action, Object... bundleKeysAndValues) {
-        final Intent intent = new Intent(action);
-        intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
-        return intent;
-    }
-
-    public void testParcel() {
-        ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
-                .setId("id")
-                .setTitle("title")
-                .setIntent(makeIntent("action"))
-                .build());
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals("title", si.getTitle());
-        assertEquals("action", si.getIntent().getAction());
-
-        PersistableBundle pb = new PersistableBundle();
-        pb.putInt("k", 1);
-
-        si = new ShortcutInfo.Builder(getContext())
-                .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
-                .setTitle("title")
-                .setText("text")
-                .setIntent(makeIntent("action", "key", "val"))
-                .setWeight(123)
-                .setExtras(pb)
-                .build();
-        si.addFlags(ShortcutInfo.FLAG_PINNED);
-        si.setBitmapPath("abc");
-        si.setIconResourceId(456);
-
-        si = parceled(si);
-
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals("content://a.b.c/", si.getIcon().getUriString());
-        assertEquals("title", si.getTitle());
-        assertEquals("text", si.getText());
-        assertEquals("action", si.getIntent().getAction());
-        assertEquals("val", si.getIntent().getStringExtra("key"));
-        assertEquals(123, si.getWeight());
-        assertEquals(1, si.getExtras().getInt("k"));
-
-        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
-        assertEquals("abc", si.getBitmapPath());
-        assertEquals(456, si.getIconResourceId());
-    }
-
-    public void testClone() {
-        PersistableBundle pb = new PersistableBundle();
-        pb.putInt("k", 1);
-        ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
-                .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
-                .setTitle("title")
-                .setText("text")
-                .setIntent(makeIntent("action", "key", "val"))
-                .setWeight(123)
-                .setExtras(pb)
-                .build();
-        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
-        sorig.setBitmapPath("abc");
-        sorig.setIconResourceId(456);
-
-        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
-
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals("content://a.b.c/", si.getIcon().getUriString());
-        assertEquals("title", si.getTitle());
-        assertEquals("text", si.getText());
-        assertEquals("action", si.getIntent().getAction());
-        assertEquals("val", si.getIntent().getStringExtra("key"));
-        assertEquals(123, si.getWeight());
-        assertEquals(1, si.getExtras().getInt("k"));
-
-        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
-        assertEquals("abc", si.getBitmapPath());
-        assertEquals(456, si.getIconResourceId());
-
-        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
-
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals(null, si.getIcon());
-        assertEquals("title", si.getTitle());
-        assertEquals("text", si.getText());
-        assertEquals("action", si.getIntent().getAction());
-        assertEquals("val", si.getIntent().getStringExtra("key"));
-        assertEquals(123, si.getWeight());
-        assertEquals(1, si.getExtras().getInt("k"));
-
-        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
-        assertEquals(null, si.getBitmapPath());
-        assertEquals(0, si.getIconResourceId());
-
-        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
-        assertEquals(null, si.getIcon());
-        assertEquals("title", si.getTitle());
-        assertEquals("text", si.getText());
-        assertEquals(null, si.getIntent());
-        assertEquals(123, si.getWeight());
-        assertEquals(1, si.getExtras().getInt("k"));
-
-        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
-        assertEquals(null, si.getBitmapPath());
-        assertEquals(0, si.getIconResourceId());
-
-        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
-
-        assertEquals(getContext().getPackageName(), si.getPackageName());
-        assertEquals("id", si.getId());
-        assertEquals(null, si.getActivityComponent());
-        assertEquals(null, si.getIcon());
-        assertEquals(null, si.getTitle());
-        assertEquals(null, si.getText());
-        assertEquals(null, si.getIntent());
-        assertEquals(0, si.getWeight());
-        assertEquals(null, si.getExtras());
-
-        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
-        assertEquals(null, si.getBitmapPath());
-        assertEquals(0, si.getIconResourceId());
-    }
-
-
-    public void testCopyNonNullFieldsFrom() {
-        PersistableBundle pb = new PersistableBundle();
-        pb.putInt("k", 1);
-        ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
-                .setId("id")
-                .setActivityComponent(new ComponentName("a", "b"))
-                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
-                .setTitle("title")
-                .setText("text")
-                .setIntent(makeIntent("action", "key", "val"))
-                .setWeight(123)
-                .setExtras(pb)
-                .build();
-        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
-        sorig.setBitmapPath("abc");
-        sorig.setIconResourceId(456);
-
-        ShortcutInfo si;
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setActivityComponent(new ComponentName("x", "y")).build());
-        assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
-        assertEquals("content://x.y.z/", si.getIcon().getUriString());
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setTitle("xyz").build());
-        assertEquals("xyz", si.getTitle());
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setText("xxx").build());
-        assertEquals("xxx", si.getText());
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setIntent(makeIntent("action2")).build());
-        assertEquals("action2", si.getIntent().getAction());
-        assertEquals(null, si.getIntent().getStringExtra("key"));
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setIntent(makeIntent("action3", "key", "x")).build());
-        assertEquals("action3", si.getIntent().getAction());
-        assertEquals("x", si.getIntent().getStringExtra("key"));
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setWeight(999).build());
-        assertEquals(999, si.getWeight());
-
-
-        PersistableBundle pb2 = new PersistableBundle();
-        pb2.putInt("x", 99);
-
-        si = sorig.clone(/* flags=*/ 0);
-        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
-                .setExtras(pb2).build());
-        assertEquals(99, si.getExtras().getInt("x"));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 5d29242..0e2a80c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -57,7 +57,9 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -76,6 +78,7 @@
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
+import com.android.server.testutis.TestUtils;
 
 import libcore.io.IoUtils;
 
@@ -96,6 +99,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Tests for ShortcutService and ShortcutManager.
@@ -107,10 +111,8 @@
  -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
 
  * TODO: Add checks with assertAllNotHaveIcon()
- *
- * TODO: separate, detailed tests for ShortcutInfo (CTS?) *
- *
- * TODO: Cross-user test (do in CTS?)
+ * TODO: Detailed test for hasShortcutPermissionInner().
+ * TODO: Add tests for the command line functions too.
  */
 @SmallTest
 public class ShortcutManagerTest extends InstrumentationTestCase {
@@ -122,6 +124,8 @@
      */
     private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
 
+    private static final boolean DUMP_ON_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
     // public for mockito
     public class BaseContext extends MockContext {
         @Override
@@ -261,7 +265,8 @@
         @Override
         boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
             // Sort of hack; do a simpler check.
-            return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage);
+            return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
+                    || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
         }
 
         @Override
@@ -394,7 +399,7 @@
 
     private Map<String, PackageInfo> mInjectedPackages;
 
-    private ArrayList<PackageWithUser> mUninstalledPackages;
+    private Set<PackageWithUser> mUninstalledPackages;
 
     private PackageManager mMockPackageManager;
     private PackageManagerInternal mMockPackageManagerInternal;
@@ -409,12 +414,21 @@
     private static final String CALLING_PACKAGE_3 = "com.android.test.3";
     private static final int CALLING_UID_3 = 10003;
 
+    private static final String CALLING_PACKAGE_4 = "com.android.test.4";
+    private static final int CALLING_UID_4 = 10004;
+
     private static final String LAUNCHER_1 = "com.android.launcher.1";
     private static final int LAUNCHER_UID_1 = 10011;
 
     private static final String LAUNCHER_2 = "com.android.launcher.2";
     private static final int LAUNCHER_UID_2 = 10012;
 
+    private static final String LAUNCHER_3 = "com.android.launcher.3";
+    private static final int LAUNCHER_UID_3 = 10013;
+
+    private static final String LAUNCHER_4 = "com.android.launcher.4";
+    private static final int LAUNCHER_UID_4 = 10014;
+
     private static final int USER_0 = UserHandle.USER_SYSTEM;
     private static final int USER_10 = 10;
     private static final int USER_11 = 11;
@@ -438,6 +452,13 @@
 
     private static final int MAX_ICON_DIMENSION_LOWRAM = 32;
 
+    private static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
+
+    static {
+        QUERY_ALL.setQueryFlags(
+                ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -457,10 +478,19 @@
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
         addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
         addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
+        addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10);
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
         addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
+        addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6);
+        addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10);
 
-        mUninstalledPackages = new ArrayList<>();
+        // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target.
+        updatePackageInfo(CALLING_PACKAGE_3,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+        updatePackageInfo(LAUNCHER_3,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        mUninstalledPackages = new HashSet<>();
 
         mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
 
@@ -475,6 +505,13 @@
         setCaller(CALLING_PACKAGE_1);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (DUMP_ON_TEARDOWN) dumpsysOnLogcat("Teardown");
+
+        super.tearDown();
+    }
+
     private Context getTestContext() {
         return getInstrumentation().getContext();
     }
@@ -504,6 +541,10 @@
         return Arrays.asList(array);
     }
 
+    private <T> Set<T> set(Set<T> in) {
+        return new ArraySet<T>(in);
+    }
+
     private Signature[] genSignatures(String... signatures) {
         final Signature[] sigs = new Signature[signatures.length];
         for (int i = 0; i < signatures.length; i++){
@@ -529,10 +570,24 @@
         mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
     }
 
+    private void updatePackageInfo(String packageName, Consumer<PackageInfo> c) {
+        c.accept(mInjectedPackages.get(packageName));
+    }
+
     private void uninstallPackage(int userId, String packageName) {
+        if (ENABLE_DUMP) {
+            Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
+        }
         mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
     }
 
+    private void installPackage(int userId, String packageName) {
+        if (ENABLE_DUMP) {
+            Log.i(TAG, "Install package " + packageName + " / " + userId);
+        }
+        mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+    }
+
     PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
         final PackageInfo pi = mInjectedPackages.get(packageName);
@@ -591,14 +646,22 @@
 
     /** For debugging */
     private void dumpsysOnLogcat() {
-        if (!ENABLE_DUMP) return;
+        dumpsysOnLogcat("");
+    }
+
+    private void dumpsysOnLogcat(String message) {
+        dumpsysOnLogcat(message, false);
+    }
+
+    private void dumpsysOnLogcat(String message, boolean force) {
+        if (force || !ENABLE_DUMP) return;
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         final PrintWriter pw = new PrintWriter(out);
         mService.dumpInner(pw);
         pw.close();
 
-        Log.e(TAG, "Dumping ShortcutService:");
+        Log.e(TAG, "Dumping ShortcutService: " + message);
         for (String line : out.toString().split("\n")) {
             Log.e(TAG, line);
         }
@@ -608,9 +671,13 @@
      * For debugging, dump arbitrary file on logcat.
      */
     private void dumpFileOnLogcat(String path) {
+        dumpFileOnLogcat(path, "");
+    }
+
+    private void dumpFileOnLogcat(String path, String message) {
         if (!ENABLE_DUMP) return;
 
-        Log.i(TAG, "Dumping file: " + path);
+        Log.i(TAG, "Dumping file: " + path + " " + message);
         final StringBuilder sb = new StringBuilder();
         try (BufferedReader br = new BufferedReader(new FileReader(path))) {
             String line;
@@ -636,10 +703,14 @@
      * For debugging, dump per-user state file on logcat.
      */
     private void dumpUserFile(int userId) {
+        dumpUserFile(userId, "");
+    }
+
+    private void dumpUserFile(int userId, String message) {
         mService.saveDirtyInfo();
         dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
                 + "/user-" + userId
-                + "/" + ShortcutService.FILENAME_USER_PACKAGES);
+                + "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
     }
 
     private void waitOnMainThread() throws Throwable {
@@ -1055,11 +1126,13 @@
         return i;
     }
 
-    /**
-     * Wrap a set in an ArraySet just to get a better toString.
-     */
-    private <T> Set<T> set(Set<T> in) {
-        return new ArraySet<T>(in);
+    private ShortcutInfo parceled(ShortcutInfo si) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(si, 0);
+        p.setDataPosition(0);
+        ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
+        p.recycle();
+        return si2;
     }
 
     /**
@@ -1320,8 +1393,6 @@
 
         // Still 2 calls left.
         assertEquals(2, mManager.getRemainingCallCount());
-
-        // TODO Make sure pinned shortcuts won't be deleted.
     }
 
     public void testDeleteAllDynamicShortcuts() {
@@ -1351,8 +1422,6 @@
 
         // Still 1 call left
         assertEquals(1, mManager.getRemainingCallCount());
-
-        // TODO Make sure pinned shortcuts won't be deleted.
     }
 
     public void testThrottling() {
@@ -1877,9 +1946,6 @@
         });
     }
 
-    // TODO: updateShortcuts()
-    // TODO: getPinnedShortcuts()
-
     // === Test for launcher side APIs ===
 
     private static ShortcutQuery buildQuery(long changedSince,
@@ -1893,6 +1959,20 @@
         return q;
     }
 
+    private static ShortcutQuery buildAllQuery(String packageName) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setPackage(packageName);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+        return q;
+    }
+
+    private static ShortcutQuery buildPinnedQuery(String packageName) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setPackage(packageName);
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED);
+        return q;
+    }
+
     public void testGetShortcuts() {
 
         // Set up shortcuts.
@@ -3052,6 +3132,7 @@
 
         // Remove CALLING_PACKAGE_2
         reset(c0);
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
         mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
 
         // Should get a callback with an empty list.
@@ -3299,9 +3380,9 @@
         // Check the registered packages.
         dumpsysOnLogcat();
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3326,13 +3407,14 @@
         mService.saveDirtyInfo();
 
         // Nonexistent package.
+        uninstallPackage(USER_0, "abc");
         mService.cleanUpPackageLocked("abc", USER_0, USER_0);
 
         // No changes.
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3357,12 +3439,13 @@
         mService.saveDirtyInfo();
 
         // Remove a package.
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
         mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3387,12 +3470,13 @@
         mService.saveDirtyInfo();
 
         // Remove a launcher.
+        uninstallPackage(USER_10, LAUNCHER_1);
         mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3414,12 +3498,13 @@
         mService.saveDirtyInfo();
 
         // Remove a package.
+        uninstallPackage(USER_10, CALLING_PACKAGE_2);
         mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3441,12 +3526,13 @@
         mService.saveDirtyInfo();
 
         // Remove the other launcher from user 10 too.
+        uninstallPackage(USER_10, LAUNCHER_2);
         mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3468,12 +3554,13 @@
         mService.saveDirtyInfo();
 
         // More remove.
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
         mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
-                set(user0.getPackages().keySet()));
+                set(user0.getAllPackages().keySet()));
         assertEquals(makeSet(),
-                set(user10.getPackages().keySet()));
+                set(user10.getAllPackages().keySet()));
         assertEquals(
                 makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
                         PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3494,97 +3581,6 @@
         mService.saveDirtyInfo();
     }
 
-
-    public void testSaveAndLoadUser_forBackup() {
-        // Create some shortcuts.
-        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
-        });
-        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
-        });
-        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
-        });
-        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
-        });
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
-        // Pin some.
-
-        runWithCaller(LAUNCHER_1, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    list("s1"), HANDLE_USER_0);
-
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    list("s2"), UserHandle.of(USER_P0));
-
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                    list("s3"), HANDLE_USER_0);
-        });
-
-        runWithCaller(LAUNCHER_1, USER_P0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    list("s2"), HANDLE_USER_0);
-
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    list("s3"), UserHandle.of(USER_P0));
-
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                    list("s1"), HANDLE_USER_0);
-        });
-
-        runWithCaller(LAUNCHER_1, USER_10, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    list("s3"), HANDLE_USER_10);
-        });
-
-        // Check the state.
-
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
-        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
-        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
-        // Make sure all the information is persisted.
-        mService.saveDirtyInfo();
-        initService();
-        mService.handleUnlockUser(USER_0);
-        mService.handleUnlockUser(USER_P0);
-        mService.handleUnlockUser(USER_10);
-    }
-
     public void testHandleGonePackage_crossProfile() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3841,13 +3837,9 @@
         assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
     }
 
-    // TODO Detailed test for hasShortcutPermissionInner().
-
-    // TODO Add tests for the command line functions too.
-
     private void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
             int version, String... signatures) {
-        assertEquals(expected, spi.canRestoreTo(genPackage(
+        assertEquals(expected, spi.canRestoreTo(mService, genPackage(
                 "dummy", /* uid */ 0, version, signatures)));
     }
 
@@ -3919,6 +3911,7 @@
         assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
         assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
+        uninstallPackage(USER_0, CALLING_PACKAGE_1);
         mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
 
@@ -3929,6 +3922,7 @@
         assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
         assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
+        uninstallPackage(USER_10, CALLING_PACKAGE_2);
         mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
 
@@ -3961,7 +3955,1163 @@
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
     }
 
-    public void testHandlePackageUpdate() {
-        // TODO: Make sure unshadow is called.
+    private void backupAndRestore() {
+        int prevUid = mInjectedCallingUid;
+
+        mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it.
+
+        dumpsysOnLogcat("Before backup");
+
+        final byte[] payload =  mService.getBackupPayload(USER_0);
+        if (ENABLE_DUMP) {
+            final String xml = new String(payload);
+            Log.i(TAG, "Backup payload:");
+            for (String line : xml.split("\n")) {
+                Log.i(TAG, line);
+            }
+        }
+
+        // Before doing anything else, uninstall all packages.
+        for (int userId : list(USER_0, USER_P0)) {
+            for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+                    LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) {
+                uninstallPackage(userId, pkg);
+            }
+        }
+
+        initService();
+        mService.applyRestore(payload, USER_0);
+
+        // handleUnlockUser will perform the gone package check, but it shouldn't remove
+        // shadow information.
+        mService.handleUnlockUser(USER_0);
+
+        dumpsysOnLogcat("After restore");
+
+        mInjectedCallingUid = prevUid;
+    }
+
+    private void prepareCrossProfileDataSet() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list()));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"),
+                    makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_3, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_4, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0);
+        });
+
+        // Launcher on a managed profile is referring ot user 0!
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"),
+                    HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0);
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"),
+                    HANDLE_USER_10);
+        });
+    }
+
+    private void prepareForBackupTest() {
+
+        prepareCrossProfileDataSet();
+
+        backupAndRestore();
+    }
+
+    private void assertExistsAndShadow(ShortcutPackageItem spi) {
+        assertNotNull(spi);
+        assertTrue(spi.getPackageInfo().isShadow());
+    }
+
+    /**
+     * Make sure the backup data doesn't have the following information:
+     * - Launchers on other users.
+     * - Non-backup app information.
+     *
+     * But restores all other infomation.
+     *
+     * It also omits the following pieces of information, but that's tested in
+     * {@link #testShortcutInfoSaveAndLoad_forBackup}.
+     * - Unpinned dynamic shortcuts
+     * - Bitmaps
+     */
+    public void testBackupAndRestore() {
+        prepareForBackupTest();
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_backupRestoreTwice() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        dumpsysOnLogcat("Before second backup");
+
+        backupAndRestore();
+
+        dumpsysOnLogcat("After second backup");
+
+        checkBackupAndRestore_success();
+    }
+
+    public void testBackupAndRestore_backupRestoreMultiple() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        // This also shouldn't affect the result.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+
+        backupAndRestore();
+
+        checkBackupAndRestore_success();
+    }
+
+    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();
+    }
+
+    public void testBackupAndRestore_restoreToSuperSetSignatures() {
+        prepareForBackupTest();
+
+        // Note doing a backup & restore again here shouldn't affect the result.
+        backupAndRestore();
+
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
+
+        checkBackupAndRestore_success();
+    }
+
+    private void checkBackupAndRestore_success() {
+        // Make sure non-system user is not restored.
+        final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
+        assertEquals(0, userP0.getAllPackages().size());
+        assertEquals(0, userP0.getAllLaunchers().size());
+
+        // Make sure only "allowBackup" apps are restored, and are shadow.
+        final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
+        assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1));
+        assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2));
+        assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
+        assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
+
+        assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3));
+        assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    /* empty, not restored */ );
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty, not restored */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            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, not restored */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        // 3 shouldn't be backed up, so no pinned shortcuts.
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        // Launcher on a different profile shouldn't be restored.
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+                    .size());
+            assertEquals(0,
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+                            .size());
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+        });
+
+        // Package on a different profile, no restore.
+        installPackage(USER_P0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        // Restore launcher 2 on user 0.
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+
+        // Restoration of launcher2 shouldn't affect other packages; so do the same checks and
+        // make sure they still have the same result.
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_1);
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+                    "s1", "s2");
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* wasn't restored, so still empty */ );
+
+            assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+    }
+
+    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();
+    }
+
+    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();
+    }
+
+    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();
+    }
+
+    private void checkBackupAndRestore_publisherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        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 */);
+        });
+        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 */);
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        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 */);
+        });
+        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 */);
+        });
+    }
+
+    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();
+    }
+
+    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();
+    }
+
+    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);
+
+        checkBackupAndRestore_launcherNotRestored();
+    }
+
+    private void checkBackupAndRestore_launcherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+
+            // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here.
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2");
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        // Now we try to restore launcher 1.  Then we realize it's not restorable, so L1 has no pinned
+        // shortcuts.
+        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))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+
+            // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned.
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2");
+        });
+
+        installPackage(USER_0, LAUNCHER_2);
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            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 */);
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        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))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+                    "s2");
+            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);
+
+        updatePackageInfo(LAUNCHER_1,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+        checkBackupAndRestore_publisherAndLauncherNotRestored();
+    }
+
+    private void checkBackupAndRestore_publisherAndLauncherNotRestored() {
+        installPackage(USER_0, CALLING_PACKAGE_1);
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_2);
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3");
+        });
+
+        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))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+        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 */);
+        });
+
+        // Because launcher 1 wasn't restored, "s1" is no longer pinned.
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertShortcutIds(assertAllPinned(
+                    mManager.getPinnedShortcuts()),
+                    "s2", "s3");
+        });
+
+        installPackage(USER_0, CALLING_PACKAGE_3);
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertEquals(0, mManager.getDynamicShortcuts().size());
+            assertEquals(0, mManager.getPinnedShortcuts().size());
+        });
+
+        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))
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* 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(
+                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    /* empty */);
+        });
+    }
+
+    public void testSaveAndLoad_crossProfile() {
+        prepareCrossProfileDataSet();
+
+        dumpsysOnLogcat("Before save & load");
+
+        mService.saveDirtyInfo();
+        initService();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5");
+        });
+        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+                    /* empty */);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+                    /* empty */);
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+                    /* empty */);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+                    "x1", "x2", "x3", "x4", "x5", "x6");
+            assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+                    "x4", "x5");
+        });
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s1");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s1", "s2");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s1", "s2", "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s1", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+            TestUtils.assertExpectException(
+                    SecurityException.class, "", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+                    });
+        });
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s2");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s2", "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s2", "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s2", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_3, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s3");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s3", "s4", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s3", "s6");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_4, USER_0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+                    /* empty */);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+                    "s3", "s4");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+                    "s3", "s4", "s5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+                    "s3", "s4", "s5", "s6");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+                    "s1", "s4");
+            TestUtils.assertExpectException(
+                    SecurityException.class, "you need to be SYSTEM", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+                    });
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10),
+                    "x4", "x5");
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10)
+                    /* empty */);
+            assertShortcutIds(
+                    mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10)
+                    /* empty */);
+            TestUtils.assertExpectException(
+                    SecurityException.class, "you need to be SYSTEM", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0);
+                    });
+            TestUtils.assertExpectException(
+                    SecurityException.class, "you need to be SYSTEM", () -> {
+                        mLauncherApps.getShortcuts(
+                                buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
+                    });
+        });
+    }
+
+    // ShortcutInfo tests
+
+    public void testShortcutInfoMissingMandatoryFields() {
+        TestUtils.assertExpectException(
+                IllegalArgumentException.class,
+                "ID must be provided",
+                () -> new ShortcutInfo.Builder(getTestContext()).build());
+        TestUtils.assertExpectException(
+                IllegalArgumentException.class,
+                "title must be provided",
+                () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
+                        .enforceMandatoryFields());
+        TestUtils.assertExpectException(
+                NullPointerException.class,
+                "Intent must be provided",
+                () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build()
+                        .enforceMandatoryFields());
+    }
+
+    public void testShortcutInfoParcel() {
+        ShortcutInfo si = parceled(new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setTitle("title")
+                .setIntent(makeIntent("action", ShortcutActivity.class))
+                .build());
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals("action", si.getIntent().getAction());
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+
+        si = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        si.addFlags(ShortcutInfo.FLAG_PINNED);
+        si.setBitmapPath("abc");
+        si.setIconResourceId(456);
+
+        si = parceled(si);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+    }
+
+    public void testShortcutInfoClone() {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals(null, si.getIntent());
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+        assertEquals(getTestContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(null, si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals(null, si.getTitle());
+        assertEquals(null, si.getText());
+        assertEquals(null, si.getIntent());
+        assertEquals(0, si.getWeight());
+        assertEquals(null, si.getExtras());
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+    }
+
+    public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        ShortcutInfo si;
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setActivityComponent(new ComponentName("x", "y")).build());
+        assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+        assertEquals("content://x.y.z/", si.getIcon().getUriString());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitle("xyz").build());
+        assertEquals("xyz", si.getTitle());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setText("xxx").build());
+        assertEquals("xxx", si.getText());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
+        assertEquals("action2", si.getIntent().getAction());
+        assertEquals(null, si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
+        assertEquals("action3", si.getIntent().getAction());
+        assertEquals("x", si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setWeight(999).build());
+        assertEquals(999, si.getWeight());
+
+
+        PersistableBundle pb2 = new PersistableBundle();
+        pb2.putInt("x", 99);
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setExtras(pb2).build());
+        assertEquals(99, si.getExtras().getInt("x"));
+
+        final long timestamp = si.getLastChangedTimestamp();
+        Thread.sleep(2);
+
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+                .setTitle("xyz").build());
+
+        assertTrue(si.getLastChangedTimestamp() > timestamp);
+    }
+
+    public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(bmp32x32)
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+
+        mManager.addDynamicShortcut(sorig);
+
+        Thread.sleep(2);
+        final long now = System.currentTimeMillis();
+
+        // Save and load.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags());
+        assertNotNull(si.getBitmapPath()); // Something should be set.
+        assertEquals(0, si.getIconResourceId());
+        assertTrue(si.getLastChangedTimestamp() < now);
+    }
+
+    public void testShortcutInfoSaveAndLoad_forBackup() {
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+                .setId("id")
+                .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+                .setIcon(bmp32x32)
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+
+        mManager.addDynamicShortcut(sorig);
+
+        // Dynamic shortcuts won't be backed up, so we need to pin it.
+        setCaller(LAUNCHER_1, USER_0);
+        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
+
+        // Do backup & restore.
+        backupAndRestore();
+
+        ShortcutInfo si;
+        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+        assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertNull(si.getBitmapPath()); // No icon.
+        assertEquals(0, si.getIconResourceId());
+    }
+
+    public void testDumpsys_crossProfile() {
+        prepareCrossProfileDataSet();
+        dumpsysOnLogcat("test1", /* force= */ true);
+    }
+
+    public void testDumpsys_withIcons() {
+        testIcons();
+        // Dump after having some icons.
+        dumpsysOnLogcat("test1", /* force= */ true);
     }
 }