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

Commit 31f453f3 authored by Oli Lan's avatar Oli Lan
Browse files

Change commit of pending backup and restore to work per-Rollback.

This changes the pendign backup and restore mechanism in AppDataRollbackHelper
to work on a per-Rollback basis.

This corrects a potential issue where, in the case where two rollbacks
exist for the same package, a restore for one could be cancelled on the
basis of a restore of the other.

This change will also allow per-rollback synchronisation to be added in
future CLs.

Bug: 136241838
Test: atest AppDataRollbackHelperTest
Test: atest RollbackTest
Change-Id: I226fc2aba66b96479f63284d4ba054de64f7fd1c
parent 4b747a32
Loading
Loading
Loading
Loading
+45 −125
Original line number Diff line number Diff line
@@ -27,13 +27,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -156,103 +150,41 @@ public class AppDataRollbackHelper {
    }

    /**
     * Computes the list of pending backups for {@code userId} given lists of rollbacks.
     * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
     * with their corresponding {@code PackageRollbackInfo}.
     * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
     * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
     * of the CE user data snapshot.
     *
     * @return the list of rollbacks that have pending backups. Note that some of the
     *         backups won't be performed, because they might be counteracted by pending restores.
     * @return true if any backups or restores were found for the userId
     */
    private static List<Rollback> computePendingBackups(int userId,
            Map<String, PackageRollbackInfo> pendingBackupPackages,
            List<Rollback> rollbacks) {
        List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();

        for (Rollback rollback : rollbacks) {
    boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
        boolean foundBackupOrRestore = false;
        for (PackageRollbackInfo info : rollback.info.getPackages()) {
            boolean hasPendingBackup = false;
            boolean hasPendingRestore = false;
            final IntArray pendingBackupUsers = info.getPendingBackups();
            if (pendingBackupUsers != null) {
                    final int idx = pendingBackupUsers.indexOf(userId);
                    if (idx != -1) {
                        pendingBackupPackages.put(info.getPackageName(), info);
                        if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
                            rollbacksWithPendingBackups.add(rollback);
                        }
                    }
                }
                if (pendingBackupUsers.indexOf(userId) != -1) {
                    hasPendingBackup = true;
                    foundBackupOrRestore = true;
                }
            }
        return rollbacksWithPendingBackups;
    }

    /**
     * Computes the list of pending restores for {@code userId} given lists of rollbacks.
     * Packages pending restore are added to {@code pendingRestores} along with their corresponding
     * {@code PackageRollbackInfo}.
     *
     * @return the list of rollbacks that have pending restores. Note that some of the
     *         restores won't be performed, because they might be counteracted by pending backups.
     */
    private static List<Rollback> computePendingRestores(int userId,
            Map<String, PackageRollbackInfo> pendingRestorePackages,
            List<Rollback> rollbacks) {
        List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();

        for (Rollback rollback : rollbacks) {
            for (PackageRollbackInfo info : rollback.info.getPackages()) {
                final RestoreInfo ri = info.getRestoreInfo(userId);
            RestoreInfo ri = info.getRestoreInfo(userId);
            if (ri != null) {
                    pendingRestorePackages.put(info.getPackageName(), info);
                    if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
                        rollbacksWithPendingRestores.add(rollback);
                    }
                }
                hasPendingRestore = true;
                foundBackupOrRestore = true;
            }
        }

        return rollbacksWithPendingRestores;
    }

    /**
     * Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
     * with pending backups, updates the {@code Rollback} instance with a mapping from
     * {@code userId} to inode of the CE user data snapshot.
     *
     * @return the set of rollbacks with changes that should be stored on disk.
     */
    public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
            List<Rollback> rollbacks) {

        final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
        final List<Rollback> pendingBackups = computePendingBackups(userId,
                pendingBackupPackages, rollbacks);

        final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
        final List<Rollback> pendingRestores = computePendingRestores(userId,
                pendingRestorePackages, rollbacks);

        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
            if (hasPendingBackup && hasPendingRestore) {
                // Remove unnecessary backup, i.e. when user did not unlock their phone between the
                // request to backup data and the request to restore it.
        Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
                pendingBackupPackages.entrySet().iterator();
        while (iter.hasNext()) {
            PackageRollbackInfo backupPackage = iter.next().getValue();
            PackageRollbackInfo restorePackage =
                    pendingRestorePackages.get(backupPackage.getPackageName());
            if (restorePackage != null) {
                backupPackage.removePendingBackup(userId);
                backupPackage.removePendingRestoreInfo(userId);
                iter.remove();
                pendingRestorePackages.remove(backupPackage.getPackageName());
            }
                info.removePendingBackup(userId);
                info.removePendingRestoreInfo(userId);
                continue;
            }

        if (!pendingBackupPackages.isEmpty()) {
            for (Rollback rollback : pendingBackups) {
                for (PackageRollbackInfo info : rollback.info.getPackages()) {
                    final IntArray pendingBackupUsers = info.getPendingBackups();
                    final int idx = pendingBackupUsers.indexOf(userId);
                    if (idx != -1) {
            if (hasPendingBackup) {
                int idx = pendingBackupUsers.indexOf(userId);
                try {
                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
                            userId, rollback.info.getRollbackId(),
@@ -265,15 +197,8 @@ public class AppDataRollbackHelper {
                                    + info.getPackageName() + ", userId: " + userId, ie);
                }
            }
                }
            }
        }

        if (!pendingRestorePackages.isEmpty()) {
            for (Rollback rollback : pendingRestores) {
                for (PackageRollbackInfo info : rollback.info.getPackages()) {
                    final RestoreInfo ri = info.getRestoreInfo(userId);
                    if (ri != null) {
            if (hasPendingRestore) {
                try {
                    mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
                            ri.seInfo, userId, rollback.info.getRollbackId(),
@@ -285,12 +210,7 @@ public class AppDataRollbackHelper {
                }
            }
        }
            }
        }

        final Set<Rollback> changed = new HashSet<>(pendingBackups);
        changed.addAll(pendingRestores);
        return changed;
        return foundBackupOrRestore;
    }

    /**
+6 −5
Original line number Diff line number Diff line
@@ -578,12 +578,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                rollbacks = new ArrayList<>(mRollbacks);
            }

            final Set<Rollback> changed =
                    mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);

            for (Rollback rollback : changed) {
            for (int i = 0; i < rollbacks.size(); i++) {
                Rollback rollback = rollbacks.get(i);
                if (mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollback)) {
                    saveRollback(rollback);
                }
            }

            latch.countDown();
        });

+3 −11
Original line number Diff line number Diff line
@@ -45,8 +45,6 @@ import org.mockito.Mockito;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;

@RunWith(JUnit4.class)
public class AppDataRollbackHelperTest {
@@ -250,28 +248,22 @@ public class AppDataRollbackHelperTest {
        dataForRestore.info.getPackages().add(pendingRestore);
        dataForRestore.info.getPackages().add(wasRecentlyRestored);

        Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
                Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
                    dataForRestore));
        InOrder inOrder = Mockito.inOrder(installer);

        // Check that pending backup and restore for the same package mutually destroyed each other.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
        assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
        assertNull(wasRecentlyRestored.getRestoreInfo(37));

        // Check that backup was performed.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
        inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
                eq(Installer.FLAG_STORAGE_CE));
        assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
        assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));

        // Check that changed returns correct Rollback.
        assertEquals(3, changed.size());
        assertTrue(changed.contains(dataWithPendingBackup));
        assertTrue(changed.contains(dataWithRecentRestore));
        assertTrue(changed.contains(dataForRestore));

        // Check that restore was performed.
        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
        inOrder.verify(installer).restoreAppDataSnapshot(
                eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
                eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));