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

Commit 856391f5 authored by Oli Lan's avatar Oli Lan
Browse files

Move enable/snapshot/restore/delete methods to the Rollback class.

This moves functionality around snapshotting, restoring, and deleting app data,
and enabling rollbacks, into the Rollback class.

Note that the new methods still rely on the AppDataRollbackHelper class - that
functionality may be combined into Rollback in future but those changes are not
in scope for this CL.

This is part of a series of changes to move rollback-specific functionality
into the Rollback class. Once these are completed, synchronisation can be
moved to be internal to Rollback, and the internal state of the Rollback class
can be cleaned up.

Bug: 136241838
Test: atest RollbackTest
Test: atest StagedRollbackTest
Test: atest CtsRollbackManagerHostTestCases
Test: atest RollbackUnitTest (new unit tests added)
Change-Id: I0d3996f1718893046d82e7a0644ea757cc76a01e
parent 7363a621
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -78,7 +78,6 @@ public class AppDataRollbackHelper {
                        + packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
            }
        }
        packageRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
    }

    /**
+144 −20
Original line number Diff line number Diff line
@@ -34,9 +34,12 @@ import android.content.rollback.RollbackManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.UserManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;

import java.io.File;
import java.io.IOException;
@@ -64,6 +67,7 @@ class Rollback {
            ROLLBACK_STATE_ENABLING,
            ROLLBACK_STATE_AVAILABLE,
            ROLLBACK_STATE_COMMITTED,
            ROLLBACK_STATE_DELETED
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface RollbackState {}
@@ -84,6 +88,11 @@ class Rollback {
     */
    static final int ROLLBACK_STATE_COMMITTED = 3;

    /**
     * The rollback has been deleted.
     */
    static final int ROLLBACK_STATE_DELETED = 4;

    /**
     * The session ID for the staged session if this rollback data represents a staged session,
     * {@code -1} otherwise.
@@ -252,15 +261,77 @@ class Rollback {
        return mState == ROLLBACK_STATE_COMMITTED;
    }

    /**
     * Returns true if the rollback is in the DELETED state.
     */
    @GuardedBy("getLock")
    boolean isDeleted() {
        return mState == ROLLBACK_STATE_DELETED;
    }

    /**
     * Enables this rollback for the provided package.
     *
     * @return boolean True if the rollback was enabled successfully for the specified package.
     */
    @GuardedBy("getLock")
    boolean enableForPackage(String packageName, long newVersion, long installedVersion,
            boolean isApex, String sourceDir, String[] splitSourceDirs) {
        try {
            RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
            if (!ArrayUtils.isEmpty(splitSourceDirs)) {
                for (String dir : splitSourceDirs) {
                    RollbackStore.backupPackageCodePath(this, packageName, dir);
                }
            }
        } catch (IOException e) {
            Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
            return false;
        }

        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
                new VersionedPackage(packageName, newVersion),
                new VersionedPackage(packageName, installedVersion),
                new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
                isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);

        info.getPackages().add(packageRollbackInfo);

        return true;
    }

    /**
     * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
     * not in the ENABLING state.
     */
    @GuardedBy("getLock")
    void snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper) {
        if (!isEnabling()) {
            return;
        }

        for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
            if (pkgRollbackInfo.getPackageName().equals(packageName)) {
                dataHelper.snapshotAppData(info.getRollbackId(), pkgRollbackInfo, userIds);

                RollbackStore.saveRollback(this);
                pkgRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
                break;
            }
        }
    }

    /**
     * Changes the state of the rollback to AVAILABLE. This also changes the timestamp to the
     * current time and saves the rollback.
     * current time and saves the rollback. Does nothing if this rollback is already in the
     * DELETED state.
     */
    @GuardedBy("getLock")
    void makeAvailable() {
        // TODO: What if the rollback has since been expired, for example due
        // to a new package being installed. Won't this revive an expired
        // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
        if (isDeleted()) {
            Slog.w(TAG, "Cannot make deleted rollback available.");
            return;
        }
        mState = ROLLBACK_STATE_AVAILABLE;
        mTimestamp = Instant.now();
        RollbackStore.saveRollback(this);
@@ -305,32 +376,32 @@ class Rollback {
            PackageInstaller.Session parentSession = packageInstaller.openSession(
                    parentSessionId);

            for (PackageRollbackInfo info : info.getPackages()) {
            for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                // TODO: We can't get the installerPackageName for apex
                // (b/123920130). Is it okay to ignore the installer package
                // for apex?
                if (!info.isApex()) {
                if (!pkgRollbackInfo.isApex()) {
                    String installerPackageName =
                            pm.getInstallerPackageName(info.getPackageName());
                            pm.getInstallerPackageName(pkgRollbackInfo.getPackageName());
                    if (installerPackageName != null) {
                        params.setInstallerPackageName(installerPackageName);
                    }
                }
                params.setRequestDowngrade(true);
                params.setRequiredInstalledVersionCode(
                        info.getVersionRolledBackFrom().getLongVersionCode());
                        pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
                if (isStaged()) {
                    params.setStaged();
                }
                if (info.isApex()) {
                if (pkgRollbackInfo.isApex()) {
                    params.setInstallAsApex();
                }
                int sessionId = packageInstaller.createSession(params);
                PackageInstaller.Session session = packageInstaller.openSession(sessionId);
                File[] packageCodePaths = RollbackStore.getPackageCodePaths(
                        this, info.getPackageName());
                        this, pkgRollbackInfo.getPackageName());
                if (packageCodePaths == null) {
                    sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                            "Backup copy of package inaccessible");
@@ -420,6 +491,58 @@ class Rollback {
        }
    }

    /**
     * Restores user data for the specified package if this rollback is currently marked as
     * having a restore in progress.
     *
     * @return boolean True if this rollback has a restore in progress and contains the specified
     * package.
     */
    @GuardedBy("getLock")
    boolean restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId,
            String seInfo, AppDataRollbackHelper dataHelper) {
        if (!isRestoreUserDataInProgress()) {
            return false;
        }

        boolean foundPackage = false;
        for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
            if (pkgRollbackInfo.getPackageName().equals(packageName)) {
                foundPackage = true;
                boolean changedRollback = false;
                for (int userId : userIds) {
                    changedRollback |= dataHelper.restoreAppData(
                            info.getRollbackId(), pkgRollbackInfo, userId, appId, seInfo);
                }
                // We've updated metadata about this rollback, so save it to flash.
                if (changedRollback) {
                    RollbackStore.saveRollback(this);
                }
                break;
            }
        }
        return foundPackage;
    }

    /**
     * Deletes app data snapshots associated with this rollback, and moves to the DELETED state.
     */
    @GuardedBy("getLock")
    void delete(AppDataRollbackHelper dataHelper) {
        for (PackageRollbackInfo pkgInfo : info.getPackages()) {
            IntArray snapshottedUsers = pkgInfo.getSnapshottedUsers();
            for (int i = 0; i < snapshottedUsers.size(); i++) {
                // Destroy app data snapshot.
                int userId = snapshottedUsers.get(i);

                dataHelper.destroyAppDataSnapshot(info.getRollbackId(), pkgInfo, userId);
            }
        }

        RollbackStore.deleteRollback(this);
        mState = ROLLBACK_STATE_DELETED;
    }

    /**
     * Returns the id of the post-reboot apk session for a staged install, if any.
     */
@@ -459,8 +582,8 @@ class Rollback {
     */
    @GuardedBy("getLock")
    boolean includesPackage(String packageName) {
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.getPackageName().equals(packageName)) {
        for (PackageRollbackInfo packageRollbackInfo : info.getPackages()) {
            if (packageRollbackInfo.getPackageName().equals(packageName)) {
                return true;
            }
        }
@@ -473,9 +596,10 @@ class Rollback {
     */
    @GuardedBy("getLock")
    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.getPackageName().equals(packageName)
                    && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
        for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
            if (pkgRollbackInfo.getPackageName().equals(packageName)
                    && pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode()
                    != versionCode) {
                return true;
            }
        }
@@ -488,8 +612,8 @@ class Rollback {
    @GuardedBy("getLock")
    List<String> getPackageNames() {
        List<String> result = new ArrayList<>();
        for (PackageRollbackInfo info : info.getPackages()) {
            result.add(info.getPackageName());
        for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
            result.add(pkgRollbackInfo.getPackageName());
        }
        return result;
    }
@@ -500,9 +624,9 @@ class Rollback {
    @GuardedBy("getLock")
    List<String> getApexPackageNames() {
        List<String> result = new ArrayList<>();
        for (PackageRollbackInfo info : info.getPackages()) {
            if (info.isApex()) {
                result.add(info.getPackageName());
        for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
            if (pkgRollbackInfo.isApex()) {
                result.add(pkgRollbackInfo.getPackageName());
            }
        }
        return result;
+27 −126
Original line number Diff line number Diff line
@@ -52,10 +52,8 @@ import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
@@ -63,7 +61,6 @@ import com.android.server.pm.Installer;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.SecureRandom;
import java.time.Instant;
@@ -410,7 +407,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                synchronized (rollback.getLock()) {
                    if (rollback.includesPackage(packageName)) {
                        iter.remove();
                        deleteRollback(rollback);
                        rollback.delete(mAppDataRollbackHelper);
                    }
                }
            }
@@ -516,7 +513,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                        // mRollbacks, or is it okay to leave as
                        // unavailable until the next reboot when it will go
                        // away on its own?
                        deleteRollback(rollback);
                        rollback.delete(mAppDataRollbackHelper);
                    } else if (session.isStagedSessionApplied()) {
                        makeRollbackAvailable(rollback);
                    }
@@ -574,7 +571,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                            && rollback.includesPackageWithDifferentVersion(packageName,
                            installedVersion)) {
                        iter.remove();
                        deleteRollback(rollback);
                        rollback.delete(mAppDataRollbackHelper);
                    }
                }
            }
@@ -626,7 +623,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                            rollback.getTimestamp()
                                    .plusMillis(mRollbackLifetimeDurationInMillis))) {
                        iter.remove();
                        deleteRollback(rollback);
                        rollback.delete(mAppDataRollbackHelper);
                    } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
                        oldest = rollback.getTimestamp();
                    }
@@ -799,7 +796,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        }

        // Get information about the package to be installed.
        PackageParser.PackageLite newPackage = null;
        PackageParser.PackageLite newPackage;
        try {
            newPackage = PackageParser.parsePackageLite(new File(session.resolvedBaseCodePath), 0);
        } catch (PackageParser.PackageParserException e) {
@@ -818,11 +815,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            return false;
        }

        VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
        final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);

        // Get information about the currently installed package.
        PackageManager pm = mContext.getPackageManager();
        final PackageInfo pkgInfo;
        try {
            pkgInfo = getPackageInfo(packageName);
@@ -833,32 +828,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            return false;
        }

        VersionedPackage installedVersion = new VersionedPackage(packageName,
                pkgInfo.getLongVersionCode());

        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
                newVersion, installedVersion,
                new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
                isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);


        try {
        ApplicationInfo appInfo = pkgInfo.applicationInfo;
            RollbackStore.backupPackageCodePath(rollback, packageName, appInfo.sourceDir);
            if (!ArrayUtils.isEmpty(appInfo.splitSourceDirs)) {
                for (String sourceDir : appInfo.splitSourceDirs) {
                    RollbackStore.backupPackageCodePath(rollback, packageName, sourceDir);
                }
            }
        } catch (IOException e) {
            Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
            return false;
        }

        synchronized (rollback.getLock()) {
            rollback.info.getPackages().add(packageRollbackInfo);
            return rollback.enableForPackage(packageName, newPackage.versionCode,
                    pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
                    appInfo.splitSourceDirs);
        }
        return true;
    }

    @Override
@@ -871,7 +846,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {

        getHandler().post(() -> {
            snapshotUserDataInternal(packageName, userIds);
            restoreUserDataInternal(packageName, userIds, appId, ceDataInode, seInfo, token);
            restoreUserDataInternal(packageName, userIds, appId, seInfo);
            final PackageManagerInternal pmi = LocalServices.getService(
                    PackageManagerInternal.class);
            pmi.finishPackageInstall(token, false);
@@ -884,66 +859,29 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            for (int i = 0; i < mRollbacks.size(); i++) {
                Rollback rollback = mRollbacks.get(i);
                synchronized (rollback.getLock()) {
                    if (!rollback.isEnabling()) {
                        continue;
                    }

                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
                        if (info.getPackageName().equals(packageName)) {
                            mAppDataRollbackHelper.snapshotAppData(
                                    rollback.info.getRollbackId(), info, userIds);
                            RollbackStore.saveRollback(rollback);
                            break;
                        }
                    }
                    rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
                }
            }
            // non-staged installs
            PackageRollbackInfo info;
            for (NewRollback rollback : mNewRollbacks) {
                synchronized (rollback.rollback.getLock()) {
                    info = getPackageRollbackInfo(rollback.rollback, packageName);
                    if (info != null) {
                        mAppDataRollbackHelper.snapshotAppData(
                                rollback.rollback.info.getRollbackId(), info, userIds);
                        RollbackStore.saveRollback(rollback.rollback);
                    }
                    rollback.rollback.snapshotUserData(
                            packageName, userIds, mAppDataRollbackHelper);
                }
            }
        }
    }

    private void restoreUserDataInternal(String packageName, int[] userIds, int appId,
            long ceDataInode, String seInfo, int token) {
        PackageRollbackInfo info = null;
        Rollback rollback = null;
    private void restoreUserDataInternal(
            String packageName, int[] userIds, int appId, String seInfo) {
        synchronized (mLock) {
            for (int i = 0; i < mRollbacks.size(); ++i) {
                Rollback candidate = mRollbacks.get(i);
                synchronized (candidate.getLock()) {
                    if (candidate.isRestoreUserDataInProgress()) {
                        info = getPackageRollbackInfo(candidate, packageName);
                        if (info != null) {
                            rollback = candidate;
                            break;
                        }
                    }
                }
            }
        }

        if (rollback == null) {
                Rollback rollback = mRollbacks.get(i);
                synchronized (rollback.getLock()) {
                    if (rollback.restoreUserDataForPackageIfInProgress(
                            packageName, userIds, appId, seInfo, mAppDataRollbackHelper)) {
                        return;
                    }

        for (int userId : userIds) {
            synchronized (rollback.getLock()) {
                final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
                        rollback.info.getRollbackId(), info, userId, appId, seInfo);

                // We've updated metadata about this rollback, so save it to flash.
                if (changedRollback) {
                    RollbackStore.saveRollback(rollback);
                }
            }
        }
@@ -1083,8 +1021,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
     * Returns -1 if the package is not currently installed.
     */
    private long getInstalledPackageVersion(String packageName) {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pkgInfo = null;
        PackageInfo pkgInfo;
        try {
            pkgInfo = getPackageInfo(packageName);
        } catch (PackageManager.NameNotFoundException e) {
@@ -1095,9 +1032,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
    }

    /**
     * Gets PackageInfo for the given package.
     * Matches any user and apex. Returns null if no such package is
     * installed.
     * Gets PackageInfo for the given package. Matches any user and apex.
     *
     * @throws PackageManager.NameNotFoundException if no such package is installed.
     */
    private PackageInfo getPackageInfo(String packageName)
            throws PackageManager.NameNotFoundException {
@@ -1113,13 +1050,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        }
    }


    private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
        return a != null && b != null
            && a.getPackageName().equals(b.getPackageName())
            && a.getLongVersionCode() == b.getLongVersionCode();
    }

    private class SessionCallback extends PackageInstaller.SessionCallback {

        @Override
@@ -1171,19 +1101,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        synchronized (rollback.getLock()) {
            if (!success) {
                // The install session was aborted, clean up the pending install.
                deleteRollback(rollback);
                rollback.delete(mAppDataRollbackHelper);
                return null;
            }
            if (newRollback.isCancelled) {
                Slog.e(TAG, "Rollback has been cancelled by PackageManager");
                deleteRollback(rollback);
                rollback.delete(mAppDataRollbackHelper);
                return null;
            }


            if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
                Slog.e(TAG, "Failed to enable rollback for all packages in session.");
                deleteRollback(rollback);
                rollback.delete(mAppDataRollbackHelper);
                return null;
            }

@@ -1236,22 +1166,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        return null;
    }

    /**
     * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
     * a specified {@code Rollback}.
     */
    @GuardedBy("rollback.getLock")
    private static PackageRollbackInfo getPackageRollbackInfo(Rollback rollback,
            String packageName) {
        for (PackageRollbackInfo info : rollback.info.getPackages()) {
            if (info.getPackageName().equals(packageName)) {
                return info;
            }
        }

        return null;
    }

    @GuardedBy("mLock")
    private int allocateRollbackIdLocked() {
        int n = 0;
@@ -1267,19 +1181,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        throw new IllegalStateException("Failed to allocate rollback ID");
    }

    @GuardedBy("rollback.getLock")
    private void deleteRollback(Rollback rollback) {
        for (PackageRollbackInfo info : rollback.info.getPackages()) {
            IntArray snapshottedUsers = info.getSnapshottedUsers();
            for (int i = 0; i < snapshottedUsers.size(); i++) {
                int userId = snapshottedUsers.get(i);
                mAppDataRollbackHelper.destroyAppDataSnapshot(rollback.info.getRollbackId(),
                        info, userId);
            }
        }
        mRollbackStore.deleteRollback(rollback);
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+1 −1
Original line number Diff line number Diff line
@@ -274,7 +274,7 @@ class RollbackStore {
    /**
     * Removes all persistent storage associated with the given rollback.
     */
    void deleteRollback(Rollback rollback) {
    static void deleteRollback(Rollback rollback) {
        removeFile(rollback.getBackupDir());
    }

+0 −12
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.server.rollback;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -271,15 +270,4 @@ public class AppDataRollbackHelperTest {

        inOrder.verifyNoMoreInteractions();
    }

    @Test
    public void snapshotAddDataSavesSnapshottedUsersToInfo() {
        Installer installer = mock(Installer.class);
        AppDataRollbackHelper helper = new AppDataRollbackHelper(installer);

        PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
        helper.snapshotAppData(5, info, new int[]{10, 11});

        assertArrayEquals(info.getSnapshottedUsers().toArray(), new int[]{10, 11});
    }
}
Loading