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

Commit e7b5752c authored by Makoto Onuki's avatar Makoto Onuki Committed by android-build-merger
Browse files

Merge "ShortcutManager: implement backup & restore" into nyc-dev

am: b1a684c6

* commit 'b1a684c6':
  ShortcutManager: implement backup & restore

Change-Id: Id18fb8e6f4cae11086eb0aaee810d5a20ea0e678
parents 5e61e92f b1a684c6
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -13,25 +13,58 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.pm;
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 ShortcutBackupAgent extends BlobBackupHelper {
public class ShortcutBackupHelper extends BlobBackupHelper {
    private static final String TAG = "ShortcutBackupAgent";
    private static final int BLOB_VERSION = 1;

    public ShortcutBackupAgent(int currentBlobVersion, String... keys) {
        super(currentBlobVersion, keys);
    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) {
        throw new RuntimeException("not implemented yet"); // todo
        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) {
        throw new RuntimeException("not implemented yet"); // todo
        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);
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -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 @@ public class SystemBackupAgent extends BackupAgentHelper {
    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 @@ public class SystemBackupAgent extends BackupAgentHelper {
        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 @@ public class SystemBackupAgent extends BackupAgentHelper {
        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);
+70 −22
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ import android.annotation.UserIdInt;
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 org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
@@ -43,15 +48,16 @@ class ShortcutLauncher extends ShortcutPackageItem {
    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 @@ class ShortcutLauncher extends ShortcutPackageItem {

    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 @@ class ShortcutLauncher extends ShortcutPackageItem {
        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 @@ class ShortcutLauncher extends ShortcutPackageItem {
                    newSet.add(id);
                }
            }
            mPinnedShortcuts.put(packageName, newSet);
            mPinnedShortcuts.put(pu, newSet);
        }
        packageShortcuts.refreshPinnedFlags(s);
    }
@@ -101,12 +129,13 @@ class ShortcutLauncher extends ShortcutPackageItem {
    /**
     * 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 @@ class ShortcutLauncher extends ShortcutPackageItem {
        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 @@ class ShortcutLauncher extends ShortcutPackageItem {
        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 @@ class ShortcutLauncher extends ShortcutPackageItem {
            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 @@ class ShortcutLauncher extends ShortcutPackageItem {
            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 @@ class ShortcutLauncher extends ShortcutPackageItem {
        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 @@ class ShortcutLauncher extends ShortcutPackageItem {
        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 @@ class ShortcutLauncher extends ShortcutPackageItem {
            }
        }
    }

    @VisibleForTesting
    ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
    }
}
+42 −14
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.util.ArrayMap;
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.File;
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 @@ class ShortcutPackage extends ShortcutPackageItem {
     */
    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 @@ class ShortcutPackage extends ShortcutPackageItem {
        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 @@ class ShortcutPackage extends ShortcutPackageItem {
                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 @@ class ShortcutPackage extends ShortcutPackageItem {
    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 @@ class ShortcutPackage extends ShortcutPackageItem {
                    || ((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 @@ class ShortcutPackage extends ShortcutPackageItem {
                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 @@ class ShortcutPackage extends ShortcutPackageItem {
            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 @@ class ShortcutPackage extends ShortcutPackageItem {
            }
            ShortcutService.warnForInvalidTag(depth, tag);
        }
        if (spi != null) {
            ret.replacePackageInfo(spi);
        }
        return ret;
    }

@@ -567,4 +590,9 @@ class ShortcutPackage extends ShortcutPackageItem {
                intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
                iconRes, bitmapPath);
    }

    @VisibleForTesting
    List<ShortcutInfo> getAllShortcutsForTest() {
        return new ArrayList<>(mShortcuts.values());
    }
}
+28 −11
Original line number Diff line number Diff line
@@ -67,10 +67,6 @@ class ShortcutPackageInfo {
        return mIsShadow;
    }

    public boolean isInstalled() {
        return !mIsShadow;
    }

    public void setShadow(boolean shadow) {
        mIsShadow = shadow;
    }
@@ -79,14 +75,24 @@ class ShortcutPackageInfo {
        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 @@ class ShortcutPackageInfo {
    }

    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,14 +143,16 @@ class ShortcutPackageInfo {
        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);

        final ArrayList<byte[]> hashes = new ArrayList<>();
        // 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;
@@ -163,7 +176,11 @@ class ShortcutPackageInfo {
            }
            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) {
Loading