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

Commit 377b7970 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

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
parent 04029362
Loading
Loading
Loading
Loading
+24 −16
Original line number Original line Diff line number Diff line
@@ -2601,22 +2601,32 @@ public class ShortcutService extends IShortcutService.Stub {
                                /* appStillExists = */ false);
                                /* appStillExists = */ false);
                    }
                    }
                }
                }

                rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime(),
                        /* forceRescan=*/ false);
            }
        } finally {
            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
        }
        verifyStates();
    }

    private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime,
            boolean forceRescan) {
        final ShortcutUser user = getUserShortcutsLocked(userId);

        final long now = injectCurrentTimeMillis();
        final long now = injectCurrentTimeMillis();


        // Then for each installed app, publish manifest shortcuts when needed.
        // Then for each installed app, publish manifest shortcuts when needed.
                forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
        forUpdatedPackages(userId, lastScanTime, ai -> {
                    user.rescanPackageIfNeeded(ai.packageName, /* forceRescan=*/ false);
            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
        // 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.
        // been updated, and we want to catch them in the next time.
        user.setLastAppScanTime(now);
        user.setLastAppScanTime(now);
                scheduleSaveUser(ownerUserId);
        scheduleSaveUser(userId);
            }
        } finally {
            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
        }
        verifyStates();
    }
    }


    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
@@ -3119,12 +3129,10 @@ public class ShortcutService extends IShortcutService.Stub {
            }
            }
            mUsers.put(userId, user);
            mUsers.put(userId, user);


            // Then purge all the save images.
            // Rescan all packages to re-publish manifest shortcuts and do other checks.
            final File bitmapPath = getUserBitmapFilePath(userId);
            rescanUpdatedPackagesLocked(userId,
            final boolean success = FileUtils.deleteContents(bitmapPath);
                    0, // lastScanTime = 0; rescan all packages.
            if (!success) {
                    /* forceRescan= */ true);
                Slog.w(TAG, "Failed to delete " + bitmapPath);
            }


            saveUserLocked(userId);
            saveUserLocked(userId);
        }
        }
+143 −9
Original line number Original line Diff line number Diff line
@@ -57,13 +57,11 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


import android.Manifest.permission;
import android.Manifest.permission;
import android.app.ActivityManager;
import android.app.ActivityManager;
@@ -82,6 +80,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
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.
        // Publish two manifest shortcuts.
        addManifestShortcutResource(
        addManifestShortcutResource(
                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -5162,9 +5161,15 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
        mService.mPackageMonitor.onReceive(mServiceContext,
        mService.mPackageMonitor.onReceive(mServiceContext,
                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
                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.
        // Pin from launcher 1.
        runWithCaller(LAUNCHER_1, USER_0, () -> {
        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.
        // Update and now ms2 is gone -> disabled.
@@ -5178,9 +5183,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
        // Make sure the manifest shortcuts have been published.
        // Make sure the manifest shortcuts have been published.
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertWith(getCallerShortcuts())
            assertWith(getCallerShortcuts())
                    .areAllPinned()
                    .selectManifest()
                    .haveIds("ms1", "ms2")
                    .haveIds("ms1")

                    .revertToOriginalList()
                    .selectDynamic()
                    .haveIds("s1", "s2", "s3")

                    .revertToOriginalList()
                    .selectPinned()
                    .haveIds("ms1", "ms2", "s1", "s2")


                    .revertToOriginalList()
                    .selectByIds("ms1")
                    .selectByIds("ms1")
                    .areAllManifest()
                    .areAllManifest()
                    .areAllEnabled()
                    .areAllEnabled()
@@ -5191,10 +5205,130 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
                    .areAllDisabled();
                    .areAllDisabled();
        });
        });


        // Now do the regular backup & restore test.
        // The existence of the manifest shortcuts shouldn't affect the result.
        prepareCrossProfileDataSet();
        backupAndRestore();
        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() {
    public void testSaveAndLoad_crossProfile() {