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

Commit 1addc53a authored by Narayan Kamath's avatar Narayan Kamath Committed by Android (Google) Code Review
Browse files

Merge "RollbackManager: Support userdata snapshot / restore."

parents 9c6e668b 869f706d
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -793,6 +793,12 @@ public abstract class PackageManagerInternal {
    public static final String EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS =
            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";

    /**
     * Extra field name for the set of installed users for a given rollback package.
     */
    public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";

    /**
     * Used as the {@code enableRollbackCode} argument for
     * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
@@ -827,4 +833,10 @@ public abstract class PackageManagerInternal {
     * Ask the package manager to compile layouts in the given package.
     */
    public abstract boolean compileLayouts(String packageName);

    /*
     * Inform the package manager that the pending package install identified by
     * {@code token} can be completed.
     */
    public abstract void finishPackageInstall(int token, boolean didLaunch);
}
+6 −0
Original line number Diff line number Diff line
@@ -33,6 +33,12 @@ interface IRollbackManager {
    void executeRollback(in RollbackInfo rollback, String callerPackageName,
            in IntentSender statusReceiver);

    // Exposed for use from the system server only. Callback from the package
    // manager during the install flow when user data can be restored for a given
    // package.
    void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
            String seInfo, int token);

    // Exposed for test purposes only.
    void reloadPersistedData();

+25 −0
Original line number Diff line number Diff line
@@ -611,6 +611,31 @@ public class Installer extends SystemService {
        }
    }

    public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
            throws InstallerException {
        if (!checkBeforeRemote()) return false;

        try {
            mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
            return true;
        } catch (Exception e) {
            throw InstallerException.from(e);
        }
    }

    public boolean restoreAppDataSnapshot(String pkg, @AppIdInt  int appId, long ceDataInode,
            String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
        if (!checkBeforeRemote()) return false;

        try {
            mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
                    storageFlags);
            return true;
        } catch (Exception e) {
            throw InstallerException.from(e);
        }
    }

    private static void assertValidInstructionSet(String instructionSet)
            throws InstallerException {
        for (String abi : Build.SUPPORTED_ABIS) {
+42 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRAD
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@ import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
import android.content.rollback.IRollbackManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -13872,6 +13874,38 @@ public class PackageManagerService extends IPackageManager.Stub
            }
        }
        // If this is an update to a package that might be potentially downgraded, then we
        // need to check with the rollback manager whether there's any userdata that might
        // need to be restored for the package.
        //
        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
            IRollbackManager rm = IRollbackManager.Stub.asInterface(
                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
            final String packageName = res.pkg.applicationInfo.packageName;
            final String seInfo = res.pkg.applicationInfo.seInfo;
            final PackageSetting ps;
            int appId = -1;
            long ceDataInode = -1;
            synchronized (mSettings) {
                ps = mSettings.getPackageLPr(packageName);
                if (ps != null) {
                    appId = ps.appId;
                    ceDataInode = ps.getCeDataInode(userId);
                }
            }
            if (ps != null) {
                try {
                    rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
                } catch (RemoteException re) {
                    // Cannot happen, the RollbackManager is hosted in the same process.
                }
                doRestore = true;
            }
        }
        if (!doRestore) {
            // No restore possible, or the Backup Manager was mysteriously not
            // available -- just fire the post-install work request directly.
@@ -14569,6 +14603,9 @@ public class PackageManagerService extends IPackageManager.Stub
                    enableRollbackIntent.putExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
                            installFlags);
                    enableRollbackIntent.putExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
                            resolveUserIds(args.user.getIdentifier()));
                    enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
                            PACKAGE_MIME_TYPE);
                    enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -23791,6 +23828,11 @@ public class PackageManagerService extends IPackageManager.Stub
            }
            return mArtManagerService.compileLayouts(pkg);
        }
        @Override
        public void finishPackageInstall(int token, boolean didLaunch) {
            PackageManagerService.this.finishPackageInstall(token, didLaunch);
        }
    }
    @GuardedBy("mPackages")
+106 −42
Original line number Diff line number Diff line
@@ -43,10 +43,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageManagerServiceUtils;

import java.io.File;
@@ -56,12 +60,11 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Implementation of service that manages APK level rollbacks.
@@ -103,9 +106,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {

    private final Context mContext;
    private final HandlerThread mHandlerThread;
    private final Installer mInstaller;

    RollbackManagerServiceImpl(Context context) {
        mContext = context;
        // Note that we're calling onStart here because this object is only constructed on
        // SystemService#onStart.
        mInstaller = new Installer(mContext);
        mInstaller.onStart();
        mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
        mHandlerThread.start();

@@ -120,8 +128,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        // expiration.
        getHandler().post(() -> ensureRollbackDataLoaded());

        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
        installer.registerSessionCallback(new SessionCallback(), getHandler());
        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
        packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -158,10 +166,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
                    int installFlags = intent.getIntExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
                    int[] installedUsers = intent.getIntArrayExtra(
                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
                    File newPackageCodePath = new File(intent.getData().getPath());

                    getHandler().post(() -> {
                        boolean success = enableRollback(installFlags, newPackageCodePath);
                        boolean success = enableRollback(installFlags, newPackageCodePath,
                                installedUsers);
                        int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
                        if (!success) {
                            ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -356,10 +367,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                parentSession.addChildSessionId(sessionId);
            }

            final LocalIntentReceiver receiver = new LocalIntentReceiver();
            parentSession.commit(receiver.getIntentSender());

            Intent result = receiver.getResult();
            final LocalIntentReceiver receiver = new LocalIntentReceiver(
                    (Intent result) -> {
                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                PackageInstaller.STATUS_FAILURE);
                        if (status != PackageInstaller.STATUS_SUCCESS) {
@@ -378,6 +387,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                        // TODO: This call emits the warning "Calling a method in the
                        // system process without a qualified user". Fix that.
                        mContext.sendBroadcast(broadcast);
                    }
            );

            parentSession.commit(receiver.getIntentSender());
        } catch (IOException e) {
            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
            sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -620,12 +633,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
     * staged for install with rollback enabled. Called before the package has
     * been installed.
     *
     * @param id the id of the enable rollback request
     * @param installFlags information about what is being installed.
     * @param newPackageCodePath path to the package about to be installed.
     * @param installedUsers the set of users for which a given package is installed.
     * @return true if enabling the rollback succeeds, false otherwise.
     */
    private boolean enableRollback(int installFlags, File newPackageCodePath) {
    private boolean enableRollback(int installFlags, File newPackageCodePath,
            int[] installedUsers) {
        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
            Log.e(TAG, "Rollbacks not supported for instant app install");
            return false;
@@ -690,6 +704,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        PackageRollbackInfo.PackageVersion installedVersion =
                new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());

        for (int user : installedUsers) {
            final int storageFlags;
            if (StorageManager.isFileEncryptedNativeOrEmulated()
                    && !StorageManager.isUserKeyUnlocked(user)) {
                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
                // across app user data until the user unlocks their device.
                Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
                storageFlags = Installer.FLAG_STORAGE_DE;
            } else {
                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
            }

            try {
                mInstaller.snapshotAppData(packageName, user, storageFlags);
            } catch (InstallerException ie) {
                Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
            }
        }

        PackageRollbackInfo info = new PackageRollbackInfo(
                packageName, newVersion, installedVersion);

@@ -722,33 +755,64 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        return true;
    }

    // TODO: Don't copy this from PackageManagerShellCommand like this?
    private static class LocalIntentReceiver {
        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
    @Override
    public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
            String seInfo, int token) {
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("restoureUserData may only be called by the system.");
        }

        getHandler().post(() -> {
            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
            // TODO(narayan): Should we make sure we're in the middle of a session commit for a
            // a package with this package name ? Otherwise it's possible we may roll back data
            // for some other downgrade.
            if (getRollbackForPackage(packageName) == null) {
                pmi.finishPackageInstall(token, false);
                return;
            }

            final int storageFlags;
            if (StorageManager.isFileEncryptedNativeOrEmulated()
                    && !StorageManager.isUserKeyUnlocked(userId)) {
                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
                // across app user data until the user unlocks their device.
                Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");

                storageFlags = Installer.FLAG_STORAGE_DE;
            } else {
                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
            }

            try {
                mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
                        seInfo, userId, storageFlags);
            } catch (InstallerException ie) {
                Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
            }

            pmi.finishPackageInstall(token, false);
        });
    }

    private class LocalIntentReceiver {
        final Consumer<Intent> mConsumer;

        LocalIntentReceiver(Consumer<Intent> consumer) {
            mConsumer = consumer;
        }

        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
            @Override
            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
                try {
                    mResult.offer(intent, 5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                getHandler().post(() -> mConsumer.accept(intent));
            }
        };

        public IntentSender getIntentSender() {
            return new IntentSender((IIntentSender) mLocalSender);
        }

        public Intent getResult() {
            try {
                return mResult.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
Loading