Rescan apps after restore and re-publish manifest shortcuts

Originally when I wrote backup & restore for ShortcutManager,
there was no manifest shortcuts, so there was no need to
handle preintalled apps specially.

However, now we have manifest shortcuts, which are published
when the user is unlocked, by the time restore happens preinstalled
apps may already have manifest shortcuts, which will be overwritten
by the restored shortcuts.

So we need to re-publish manifest shortcuts after restore.

Bug 30746028

Change-Id: I6afbae7790c9ed38483637f33c381ecb6f854677
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 44df926..d5767b4 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2601,17 +2601,9 @@
                                 /* appStillExists = */ false);
                     }
                 }
-                final long now = injectCurrentTimeMillis();
 
-                // Then for each installed app, publish manifest shortcuts when needed.
-                forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
-                    user.rescanPackageIfNeeded(ai.packageName, /* forceRescan=*/ false);
-                });
-
-                // Write the time just before the scan, because there may be apps that have just
-                // been updated, and we want to catch them in the next time.
-                user.setLastAppScanTime(now);
-                scheduleSaveUser(ownerUserId);
+                rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime(),
+                        /* forceRescan=*/ false);
             }
         } finally {
             logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
@@ -2619,6 +2611,24 @@
         verifyStates();
     }
 
+    private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime,
+            boolean forceRescan) {
+        final ShortcutUser user = getUserShortcutsLocked(userId);
+
+        final long now = injectCurrentTimeMillis();
+
+        // Then for each installed app, publish manifest shortcuts when needed.
+        forUpdatedPackages(userId, lastScanTime, ai -> {
+            user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId);
+            user.rescanPackageIfNeeded(ai.packageName, forceRescan);
+        });
+
+        // Write the time just before the scan, because there may be apps that have just
+        // been updated, and we want to catch them in the next time.
+        user.setLastAppScanTime(now);
+        scheduleSaveUser(userId);
+    }
+
     private void handlePackageAdded(String packageName, @UserIdInt int userId) {
         if (DEBUG) {
             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
@@ -3119,12 +3129,10 @@
             }
             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);
-            }
+            // Rescan all packages to re-publish manifest shortcuts and do other checks.
+            rescanUpdatedPackagesLocked(userId,
+                    0, // lastScanTime = 0; rescan all packages.
+                    /* forceRescan= */ true);
 
             saveUserLocked(userId);
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4bd0d6fb..75a3427 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -57,13 +57,11 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
 import android.app.ActivityManager;
@@ -82,6 +80,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -5153,7 +5152,7 @@
     }
 
 
-    public void testBackupAndRestore_manifestNotRestored() {
+    public void testBackupAndRestore_manifestRePublished() {
         // Publish two manifest shortcuts.
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -5162,9 +5161,15 @@
         mService.mPackageMonitor.onReceive(mServiceContext,
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
         // Pin from launcher 1.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1", "ms2"), HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("ms1", "ms2", "s1", "s2"), HANDLE_USER_0);
         });
 
         // Update and now ms2 is gone -> disabled.
@@ -5178,9 +5183,18 @@
         // Make sure the manifest shortcuts have been published.
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertWith(getCallerShortcuts())
-                    .areAllPinned()
-                    .haveIds("ms1", "ms2")
+                    .selectManifest()
+                    .haveIds("ms1")
 
+                    .revertToOriginalList()
+                    .selectDynamic()
+                    .haveIds("s1", "s2", "s3")
+
+                    .revertToOriginalList()
+                    .selectPinned()
+                    .haveIds("ms1", "ms2", "s1", "s2")
+
+                    .revertToOriginalList()
                     .selectByIds("ms1")
                     .areAllManifest()
                     .areAllEnabled()
@@ -5191,10 +5205,130 @@
                     .areAllDisabled();
         });
 
-        // Now do the regular backup & restore test.
-        // The existence of the manifest shortcuts shouldn't affect the result.
-        prepareCrossProfileDataSet();
         backupAndRestore();
+
+        // When re-installing the app, the manifest shortcut should be re-published.
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerVisibleShortcuts())
+                    .selectPinned()
+                    // ms2 was disabled, so not restored.
+                    .haveIds("ms1", "s1", "s2")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms1")
+                    .areAllManifest()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "s2")
+                    .areAllNotDynamic()
+                    ;
+        });
+    }
+
+    /**
+     * It's the case with preintalled apps -- when applyRestore() is called, the system
+     * apps are already installed, so manifest shortcuts need to be re-published.
+     */
+    public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
+        // Pre-backup.  Same as testBackupAndRestore_manifestRePublished().
+
+        // Publish two manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        // Pin from launcher 1.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("ms1", "ms2", "s1", "s2"), HANDLE_USER_0);
+        });
+
+        // Update and now ms2 is gone -> disabled.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_1);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        // Make sure the manifest shortcuts have been published.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .selectManifest()
+                    .haveIds("ms1")
+
+                    .revertToOriginalList()
+                    .selectDynamic()
+                    .haveIds("s1", "s2", "s3")
+
+                    .revertToOriginalList()
+                    .selectPinned()
+                    .haveIds("ms1", "ms2", "s1", "s2")
+
+                    .revertToOriginalList()
+                    .selectByIds("ms1")
+                    .areAllManifest()
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms2")
+                    .areAllNotManifest()
+                    .areAllDisabled();
+        });
+
+        // Backup and *without restarting the service, just call applyRestore()*.
+        {
+            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.v(TAG, "Backup payload:");
+                for (String line : xml.split("\n")) {
+                    Log.v(TAG, line);
+                }
+            }
+            mService.applyRestore(payload, USER_0);
+
+            dumpsysOnLogcat("After restore");
+
+            mInjectedCallingUid = prevUid;
+        }
+
+        // The check is also the same as testBackupAndRestore_manifestRePublished().
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerVisibleShortcuts())
+                    .selectPinned()
+                    // ms2 was disabled, so not restored.
+                    .haveIds("ms1", "s1", "s2")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms1")
+                    .areAllManifest()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "s2")
+                    .areAllNotDynamic()
+            ;
+        });
     }
 
     public void testSaveAndLoad_crossProfile() {