From 377b7970ad70e87f8137ea7d9e7a6b00de1826ae Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 9 Aug 2016 14:43:55 -0700 Subject: [PATCH] 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 --- .../android/server/pm/ShortcutService.java | 40 +++-- .../server/pm/ShortcutManagerTest1.java | 152 ++++++++++++++++-- 2 files changed, 167 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 44df9264d748e..d5767b4cad8a2 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 @@ public class ShortcutService extends IShortcutService.Stub { /* 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } 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 4bd0d6fb9b383..75a34272d82b4 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.any; 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.net.Uri; 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 class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .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() {