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

Commit dec5726c authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Shortcut: Improve backup & restore" into nyc-mr1-dev

parents 393bd2e8 fc4cf2da
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -1151,6 +1151,17 @@ class ShortcutPackage extends ShortcutPackageItem {
        }
    }

    /** @return true if there's any shortcuts that are not manifest shortcuts. */
    public boolean hasNonManifestShortcuts() {
        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
            final ShortcutInfo si = mShortcuts.valueAt(i);
            if (!si.isDeclaredInManifest()) {
                return true;
            }
        }
        return false;
    }

    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
        pw.println();

+8 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ abstract class ShortcutPackageItem {

    private final ShortcutPackageInfo mPackageInfo;

    protected final ShortcutUser mShortcutUser;
    protected ShortcutUser mShortcutUser;

    protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
            int packageUserId, @NonNull String packageName,
@@ -51,6 +51,13 @@ abstract class ShortcutPackageItem {
        mPackageInfo = Preconditions.checkNotNull(packageInfo);
    }

    /**
     * Change the parent {@link ShortcutUser}.  Need it in the restore code.
     */
    public void replaceUser(ShortcutUser user) {
        mShortcutUser = user;
    }

    public ShortcutUser getUser() {
        return mShortcutUser;
    }
+14 −6
Original line number Diff line number Diff line
@@ -380,6 +380,12 @@ public class ShortcutService extends IShortcutService.Stub {
    @GuardedBy("mLock")
    private Exception mLastWtfStacktrace;

    static class InvalidFileFormatException extends Exception {
        public InvalidFileFormatException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public ShortcutService(Context context) {
        this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
    }
@@ -961,7 +967,7 @@ public class ShortcutService extends IShortcutService.Stub {
        try {
            final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
            return ret;
        } catch (IOException | XmlPullParserException e) {
        } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
            return null;
        } finally {
@@ -970,7 +976,8 @@ public class ShortcutService extends IShortcutService.Stub {
    }

    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
            boolean fromBackup) throws XmlPullParserException, IOException {
            boolean fromBackup) throws XmlPullParserException, IOException,
            InvalidFileFormatException {

        final BufferedInputStream bis = new BufferedInputStream(is);

@@ -3170,15 +3177,16 @@ public class ShortcutService extends IShortcutService.Stub {
                wtf("Can't restore: user " + userId + " is locked or not running");
                return;
            }
            final ShortcutUser user;
            // Actually do restore.
            final ShortcutUser restored;
            final ByteArrayInputStream is = new ByteArrayInputStream(payload);
            try {
                user = loadUserInternal(userId, is, /* fromBackup */ true);
            } catch (XmlPullParserException | IOException e) {
                restored = loadUserInternal(userId, is, /* fromBackup */ true);
            } catch (XmlPullParserException | IOException | InvalidFileFormatException e) {
                Slog.w(TAG, "Restoration failed.", e);
                return;
            }
            mUsers.put(userId, user);
            getUserShortcutsLocked(userId).mergeRestoredFile(restored);

            // Rescan all packages to re-publish manifest shortcuts and do other checks.
            rescanUpdatedPackagesLocked(userId,
+111 −50
Original line number Diff line number Diff line
@@ -23,12 +23,14 @@ import android.content.pm.ShortcutManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;

import libcore.util.Objects;

@@ -164,6 +166,11 @@ class ShortcutUser {
        return mPackages.containsKey(packageName);
    }

    private void addPackage(@NonNull ShortcutPackage p) {
        p.replaceUser(this);
        mPackages.put(p.getPackageName(), p);
    }

    public ShortcutPackage removePackage(@NonNull String packageName) {
        final ShortcutPackage removed = mPackages.remove(packageName);

@@ -179,7 +186,8 @@ class ShortcutUser {
        return mLaunchers;
    }

    public void addLauncher(ShortcutLauncher launcher) {
    private void addLauncher(ShortcutLauncher launcher) {
        launcher.replaceUser(this);
        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
                launcher.getPackageName()), launcher);
    }
@@ -326,6 +334,8 @@ class ShortcutUser {
            throws IOException, XmlPullParserException {
        out.startTag(null, TAG_ROOT);

        if (!forBackup) {
            // Don't have to back them up.
            ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
            ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
                    mLastAppScanTime);
@@ -333,6 +343,7 @@ class ShortcutUser {
                    mLastAppScanOsFingerprint);

            ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
        }

        // Can't use forEachPackageItem due to the checked exceptions.
        {
@@ -365,9 +376,10 @@ class ShortcutUser {
    }

    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
            boolean fromBackup) throws IOException, XmlPullParserException {
            boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
        final ShortcutUser ret = new ShortcutUser(s, userId);

        try {
            ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
                    ATTR_KNOWN_LOCALES);

@@ -414,6 +426,10 @@ class ShortcutUser {
                }
                ShortcutService.warnForInvalidTag(depth, tag);
            }
        } catch (RuntimeException e) {
            throw new ShortcutService.InvalidFileFormatException(
                    "Unable to parse file", e);
        }
        return ret;
    }

@@ -461,6 +477,51 @@ class ShortcutUser {
        }
    }

    public void mergeRestoredFile(ShortcutUser restored) {
        final ShortcutService s = mService;
        // Note, a restore happens only at the end of setup wizard.  At this point, no apps are
        // installed from Play Store yet, but it's still possible that system apps have already
        // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED.
        // When such a system app has allowbackup=true, then we go ahead and replace all existing
        // shortcuts with the restored shortcuts.  (Then we'll re-publish manifest shortcuts later
        // in the call site.)
        // When such a system app has allowbackup=false, then we'll keep the shortcuts that have
        // already been published.  So we selectively add restored ShortcutPackages here.
        //
        // The same logic applies to launchers, but since launchers shouldn't pin shortcuts
        // without users interaction it's really not a big deal, so we just clear existing
        // ShortcutLauncher instances in mLaunchers and add all the restored ones here.

        mLaunchers.clear();
        restored.forAllLaunchers(sl -> {
            // If the app is already installed and allowbackup = false, then ignore the restored
            // data.
            if (s.isPackageInstalled(sl.getPackageName(), getUserId())
                    && !s.shouldBackupApp(sl.getPackageName(), getUserId())) {
                return;
            }
            addLauncher(sl);
        });
        restored.forAllPackages(sp -> {
            // If the app is already installed and allowbackup = false, then ignore the restored
            // data.
            if (s.isPackageInstalled(sp.getPackageName(), getUserId())
                    && !s.shouldBackupApp(sp.getPackageName(), getUserId())) {
                return;
            }

            final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName());
            if (previous != null && previous.hasNonManifestShortcuts()) {
                Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
                        + " Existing non-manifeset shortcuts will be overwritten.");
            }
            addPackage(sp);
        });
        // Empty the launchers and packages in restored to avoid accidentally using them.
        restored.mLaunchers.clear();
        restored.mPackages.clear();
    }

    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
        pw.print(prefix);
        pw.print("User: ");
+30 −0
Original line number Diff line number Diff line
@@ -5359,6 +5359,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
    /**
     * 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.
     *
     * Also, when a restore target app is already installed, and
     * - if it has allowBackup=true, we'll restore normally, so all existing shortcuts will be
     * replaced. (but manifest shortcuts will be re-published anyway.)  We log a warning on
     * logcat.
     * - if it has allowBackup=false, we don't touch any of the existing shortcuts.
     */
    public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
        // Pre-backup.  Same as testBackupAndRestore_manifestRePublished().
@@ -5390,6 +5396,19 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
        mService.mPackageMonitor.onReceive(mServiceContext,
                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));

        // Set up shortcuts for package 3, which won't be backed up / restored.
        addManifestShortcutResource(
                new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
                R.xml.shortcut_1);
        updatePackageVersion(CALLING_PACKAGE_3, 1);
        mService.mPackageMonitor.onReceive(mServiceContext,
                genPackageAddIntent(CALLING_PACKAGE_3, USER_0));

        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertTrue(getManager().setDynamicShortcuts(list(
                    makeShortcut("s1"))));
        });

        // Make sure the manifest shortcuts have been published.
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertWith(getCallerShortcuts())
@@ -5415,6 +5434,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
                    .areAllDisabled();
        });

        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertWith(getCallerShortcuts())
                    .haveIds("s1", "ms1");
        });

        // Backup and *without restarting the service, just call applyRestore()*.
        {
            int prevUid = mInjectedCallingUid;
@@ -5454,6 +5478,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
                    .areAllNotDynamic()
            ;
        });

        // Package 3 still has the same shortcuts.
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertWith(getCallerShortcuts())
                    .haveIds("s1", "ms1");
        });
    }

    public void testSaveAndLoad_crossProfile() {