diff options
6 files changed, 318 insertions, 246 deletions
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 1d0ab5ad2679..7e5532c8e9bb 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -108,6 +108,11 @@ public final class PackageRollbackInfo implements Parcelable { } /** @hide */ + public void addPendingBackup(int userId) { + mPendingBackups.add(userId); + } + + /** @hide */ public IntArray getPendingBackups() { return mPendingBackups; } @@ -154,6 +159,19 @@ public final class PackageRollbackInfo implements Parcelable { } /** @hide */ + public void removePendingBackup(int userId) { + int idx = mPendingBackups.indexOf(userId); + if (idx != -1) { + mPendingBackups.remove(idx); + } + } + + /** @hide */ + public void removePendingRestoreInfo(int userId) { + removeRestoreInfo(getRestoreInfo(userId)); + } + + /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index c2a75aba28b9..bab612d3c092 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -616,6 +616,7 @@ public class Installer extends SystemService { * * @param pkg name of the package to snapshot user data for. * @param userId id of the user whose data to snapshot. + * @param snapshotId id of this snapshot. * @param storageFlags flags controlling which data (CE or DE) to snapshot. * * @return inode of the snapshot of users CE package data, or {@code 0} if a remote calls @@ -623,12 +624,12 @@ public class Installer extends SystemService { * * @throws InstallerException if failed to snapshot user data. */ - public long snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) + public long snapshotAppData(String pkg, @UserIdInt int userId, int snapshotId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return 0; try { - return mInstalld.snapshotAppData(null, pkg, userId, storageFlags); + return mInstalld.snapshotAppData(null, pkg, userId, snapshotId, storageFlags); } catch (Exception e) { throw InstallerException.from(e); } @@ -639,8 +640,8 @@ public class Installer extends SystemService { * * @param pkg name of the package to restore user data for. * @param appId id of the package to restore user data for. - * @param ceDataInode inode of CE user data folder of this app. * @param userId id of the user whose data to restore. + * @param snapshotId id of the snapshot to restore. * @param storageFlags flags controlling which data (CE or DE) to restore. * * @return {@code true} if user data restore was successful, or {@code false} if a remote call @@ -648,12 +649,12 @@ public class Installer extends SystemService { * * @throws InstallerException if failed to restore user data. */ - public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, - String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { + public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, String seInfo, + @UserIdInt int userId, int snapshotId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; try { - mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId, + mInstalld.restoreAppDataSnapshot(null, pkg, appId, seInfo, userId, snapshotId, storageFlags); return true; } catch (Exception e) { @@ -667,6 +668,7 @@ public class Installer extends SystemService { * @param pkg name of the package to delete user data snapshot for. * @param userId id of the user whose user data snapshot to delete. * @param ceSnapshotInode inode of CE user data snapshot. + * @param snapshotId id of the snapshot to delete. * @param storageFlags flags controlling which user data snapshot (CE or DE) to delete. * * @return {@code true} if user data snapshot was successfully deleted, or {@code false} if a @@ -675,11 +677,12 @@ public class Installer extends SystemService { * @throws InstallerException if failed to delete user data snapshot. */ public boolean destroyAppDataSnapshot(String pkg, @UserIdInt int userId, long ceSnapshotInode, - int storageFlags) throws InstallerException { + int snapshotId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; try { - mInstalld.destroyAppDataSnapshot(null, pkg, userId, ceSnapshotInode, storageFlags); + mInstalld.destroyAppDataSnapshot(null, pkg, userId, ceSnapshotInode, snapshotId, + storageFlags); return true; } catch (Exception e) { throw InstallerException.from(e); diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index f3b838560ebd..e9ccea54fe99 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -29,6 +29,8 @@ import com.android.server.pm.Installer; import com.android.server.pm.Installer.InstallerException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -49,16 +51,11 @@ public class AppDataRollbackHelper { } /** - * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, - * a specified set of users for whom the package is installed. - * - * @return a {@link SnapshotAppDataResult}/ - * @see SnapshotAppDataResult + * Creates an app data snapshot for a specified {@code packageRollbackInfo}. Updates said {@code + * packageRollbackInfo} with the inodes of the CE user data snapshot folders. */ - public SnapshotAppDataResult snapshotAppData(String packageName, int[] installedUsers) { - final IntArray pendingBackups = new IntArray(); - final SparseLongArray ceSnapshotInodes = new SparseLongArray(); - + public void snapshotAppData(int snapshotId, PackageRollbackInfo packageRollbackInfo) { + final int[] installedUsers = packageRollbackInfo.getInstalledUsers().toArray(); for (int user : installedUsers) { final int storageFlags; if (isUserCredentialLocked(user)) { @@ -66,51 +63,38 @@ public class AppDataRollbackHelper { // across app user data until the user unlocks their device. Log.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup."); storageFlags = Installer.FLAG_STORAGE_DE; - pendingBackups.add(user); + packageRollbackInfo.addPendingBackup(user); } else { storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; } try { - long ceSnapshotInode = mInstaller.snapshotAppData(packageName, user, storageFlags); + long ceSnapshotInode = mInstaller.snapshotAppData( + packageRollbackInfo.getPackageName(), user, snapshotId, storageFlags); if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) { - ceSnapshotInodes.put(user, ceSnapshotInode); + packageRollbackInfo.putCeSnapshotInode(user, ceSnapshotInode); } } catch (InstallerException ie) { - Log.e(TAG, "Unable to create app data snapshot for: " + packageName - + ", userId: " + user, ie); + Log.e(TAG, "Unable to create app data snapshot for: " + + packageRollbackInfo.getPackageName() + ", userId: " + user, ie); } } - - return new SnapshotAppDataResult(pendingBackups, ceSnapshotInodes); } /** - * Restores an app data snapshot for a specified package ({@code packageName}, - * {@code rollbackData}) for a specified {@code userId}. + * Restores an app data snapshot for a specified {@code packageRollbackInfo}, for a specified + * {@code userId}. * - * @return {@code true} iff. a change to the {@code rollbackData} has been made. Changes to - * {@code rollbackData} are restricted to the removal or addition of {@code userId} to - * the list of pending backups or restores. + * @return {@code true} iff. a change to the {@code packageRollbackInfo} has been made. Changes + * to {@code packageRollbackInfo} are restricted to the removal or addition of {@code + * userId} to the list of pending backups or restores. */ - public boolean restoreAppData(String packageName, RollbackData rollbackData, - int userId, int appId, long ceDataInode, String seInfo) { - if (rollbackData == null) { - return false; - } - - if (!rollbackData.inProgress) { - Log.e(TAG, "Request to restore userData for: " + packageName - + ", but no rollback in progress."); - return false; - } - - PackageRollbackInfo packageInfo = RollbackManagerServiceImpl.getPackageRollbackInfo( - rollbackData, packageName); + public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo, + int userId, int appId, String seInfo) { int storageFlags = Installer.FLAG_STORAGE_DE; - final IntArray pendingBackups = packageInfo.getPendingBackups(); - final List<RestoreInfo> pendingRestores = packageInfo.getPendingRestores(); + final IntArray pendingBackups = packageRollbackInfo.getPendingBackups(); + final List<RestoreInfo> pendingRestores = packageRollbackInfo.getPendingRestores(); boolean changedRollbackData = false; // If we still have a userdata backup pending for this user, it implies that the user @@ -134,53 +118,60 @@ public class AppDataRollbackHelper { } try { - mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode, - seInfo, userId, storageFlags); + mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), appId, seInfo, + userId, rollbackId, storageFlags); } catch (InstallerException ie) { - Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie); + Log.e(TAG, "Unable to restore app data snapshot: " + + packageRollbackInfo.getPackageName(), ie); } return changedRollbackData; } /** - * Deletes an app data data snapshot for a specified package {@code packageName} for a - * given {@code user}. + * Deletes an app data snapshot with a given {@code rollbackId} for a specified package + * {@code packageName} for a given {@code user}. */ - public void destroyAppDataSnapshot(String packageName, int user, long ceSnapshotInode) { + public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo, + int user) { int storageFlags = Installer.FLAG_STORAGE_DE; + final SparseLongArray ceSnapshotInodes = packageRollbackInfo.getCeSnapshotInodes(); + long ceSnapshotInode = ceSnapshotInodes.get(user); if (ceSnapshotInode > 0) { storageFlags |= Installer.FLAG_STORAGE_CE; } try { - mInstaller.destroyAppDataSnapshot(packageName, user, ceSnapshotInode, storageFlags); + mInstaller.destroyAppDataSnapshot(packageRollbackInfo.getPackageName(), user, + ceSnapshotInode, rollbackId, storageFlags); + if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) { + ceSnapshotInodes.delete(user); + } } catch (InstallerException ie) { - Log.e(TAG, "Unable to delete app data snapshot for " + packageName, ie); + Log.e(TAG, "Unable to delete app data snapshot for " + + packageRollbackInfo.getPackageName(), ie); } } /** - * Computes the list of pending backups and restores for {@code userId} given lists of - * available and recent rollbacks. Packages pending backup for the given user are added - * to {@code pendingBackups} and packages pending restore are added to {@code pendingRestores} - * along with their corresponding {@code RestoreInfo}. + * Computes the list of pending backups for {@code userId} given lists of available rollbacks. + * Packages pending backup for the given user are added to {@code pendingBackupPackages} along + * with their corresponding {@code PackageRollbackInfo}. * - * @return the list of {@code RollbackData} that have been modified during this computation. + * @return the list of {@code RollbackData} that has pending backups. Note that some of the + * backups won't be performed, because they might be counteracted by pending restores. */ - public List<RollbackData> computePendingBackupsAndRestores(int userId, - ArrayList<String> pendingBackupPackages, Map<String, RestoreInfo> pendingRestores, - List<RollbackData> availableRollbacks, List<RollbackInfo> recentRollbacks) { + private static List<RollbackData> computePendingBackups(int userId, + Map<String, PackageRollbackInfo> pendingBackupPackages, + List<RollbackData> availableRollbacks) { List<RollbackData> rd = new ArrayList<>(); - // First check with the list of available rollbacks to see whether there are any - // pending backup operations that we've not managed to execute. + for (RollbackData data : availableRollbacks) { for (PackageRollbackInfo info : data.packages) { final IntArray pendingBackupUsers = info.getPendingBackups(); if (pendingBackupUsers != null) { final int idx = pendingBackupUsers.indexOf(userId); if (idx != -1) { - pendingBackupPackages.add(info.getPackageName()); - pendingBackupUsers.remove(idx); + pendingBackupPackages.put(info.getPackageName(), info); if (rd.indexOf(data) == -1) { rd.add(data); } @@ -188,23 +179,30 @@ public class AppDataRollbackHelper { } } } + return rd; + } + + /** + * Computes the list of pending restores for {@code userId} given lists of recent rollbacks. + * Packages pending restore are added to {@code pendingRestores} along with their corresponding + * {@code PackageRollbackInfo}. + * + * @return the list of {@code RollbackInfo} that has pending restores. Note that some of the + * restores won't be performed, because they might be counteracted by pending backups. + */ + private static List<RollbackInfo> computePendingRestores(int userId, + Map<String, PackageRollbackInfo> pendingRestorePackages, + List<RollbackInfo> recentRollbacks) { + List<RollbackInfo> rd = new ArrayList<>(); - // Then check with the list of recently executed rollbacks to see whether there are - // any rollback operations for (RollbackInfo data : recentRollbacks) { for (PackageRollbackInfo info : data.getPackages()) { final RestoreInfo ri = info.getRestoreInfo(userId); if (ri != null) { - if (pendingBackupPackages.contains(info.getPackageName())) { - // This implies that the user hasn't unlocked their device between - // the request to backup data for this user and the request to restore - // it, so we do nothing here. - pendingBackupPackages.remove(info.getPackageName()); - } else { - pendingRestores.put(info.getPackageName(), ri); + pendingRestorePackages.put(info.getPackageName(), info); + if (rd.indexOf(data) == -1) { + rd.add(data); } - - info.removeRestoreInfo(ri); } } } @@ -216,47 +214,77 @@ public class AppDataRollbackHelper { * Commits the list of pending backups and restores for a given {@code userId}. For the pending * backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId} * to a inode of theirs CE user data snapshot. + * + * @return a list {@code RollbackData} that have been changed and should be stored on disk. */ - public void commitPendingBackupAndRestoreForUser(int userId, - ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores, - List<RollbackData> changedRollbackData) { - if (!pendingBackups.isEmpty()) { - for (String packageName : pendingBackups) { - try { - long ceSnapshotInode = mInstaller.snapshotAppData(packageName, userId, - Installer.FLAG_STORAGE_CE); - for (RollbackData data : changedRollbackData) { - for (PackageRollbackInfo info : data.packages) { - if (info.getPackageName().equals(packageName)) { - info.putCeSnapshotInode(userId, ceSnapshotInode); - } + public List<RollbackData> commitPendingBackupAndRestoreForUser(int userId, + List<RollbackData> availableRollbacks, List<RollbackInfo> recentlyExecutedRollbacks) { + + final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>(); + final List<RollbackData> pendingBackups = computePendingBackups(userId, + pendingBackupPackages, availableRollbacks); + + final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>(); + final List<RollbackInfo> pendingRestores = computePendingRestores(userId, + pendingRestorePackages, recentlyExecutedRollbacks); + + // First remove unnecessary backups, 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()); + } + } + + if (!pendingBackupPackages.isEmpty()) { + for (RollbackData data : pendingBackups) { + for (PackageRollbackInfo info : data.packages) { + final IntArray pendingBackupUsers = info.getPendingBackups(); + final int idx = pendingBackupUsers.indexOf(userId); + if (idx != -1) { + try { + long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(), + userId, data.rollbackId, Installer.FLAG_STORAGE_CE); + info.putCeSnapshotInode(userId, ceSnapshotInode); + pendingBackupUsers.remove(idx); + } catch (InstallerException ie) { + Log.e(TAG, + "Unable to create app data snapshot for: " + + info.getPackageName() + ", userId: " + userId, ie); } } - } catch (InstallerException ie) { - Log.e(TAG, "Unable to create app data snapshot for: " + packageName - + ", userId: " + userId, ie); } } } - // TODO(narayan): Should we perform the restore before the backup for packages that have - // both backups and restores pending ? We could get into this case if we have a pending - // restore from a rollback + a snapshot request from a new restore. - if (!pendingRestores.isEmpty()) { - for (String packageName : pendingRestores.keySet()) { - try { - final RestoreInfo ri = pendingRestores.get(packageName); - - // TODO(narayan): Verify that the user of "0" for ceDataInode is accurate - // here. We know that the user has unlocked (and that their CE data is - // available) so we shouldn't need to resort to the fallback path. - mInstaller.restoreAppDataSnapshot(packageName, ri.appId, - 0 /* ceDataInode */, ri.seInfo, userId, Installer.FLAG_STORAGE_CE); - } catch (InstallerException ie) { - Log.e(TAG, "Unable to restore app data snapshot for: " + packageName, ie); + if (!pendingRestorePackages.isEmpty()) { + for (RollbackInfo data : pendingRestores) { + for (PackageRollbackInfo info : data.getPackages()) { + final RestoreInfo ri = info.getRestoreInfo(userId); + if (ri != null) { + try { + mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId, + ri.seInfo, userId, data.getRollbackId(), + Installer.FLAG_STORAGE_CE); + info.removeRestoreInfo(ri); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to restore app data snapshot for: " + + info.getPackageName(), ie); + } + } } } } + + return pendingBackups; } /** @@ -267,26 +295,4 @@ public class AppDataRollbackHelper { return StorageManager.isFileEncryptedNativeOrEmulated() && !StorageManager.isUserKeyUnlocked(userId); } - - /** - * Encapsulates a result of {@link #snapshotAppData} method. - */ - public static final class SnapshotAppDataResult { - - /** - * A list of users for which the snapshot is pending, usually because data for one or more - * users is still credential locked. - */ - public final IntArray pendingBackups; - - /** - * A mapping between user and an inode of theirs CE data snapshot. - */ - public final SparseLongArray ceSnapshotInodes; - - public SnapshotAppDataResult(IntArray pendingBackups, SparseLongArray ceSnapshotInodes) { - this.pendingBackups = pendingBackups; - this.ceSnapshotInodes = ceSnapshotInodes; - } - } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 05d3c17e5c25..c14f1261cc76 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -32,7 +32,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.VersionedPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.PackageRollbackInfo; -import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.Binder; @@ -466,18 +465,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { void onUnlockUser(int userId) { getHandler().post(() -> { - final ArrayList<String> pendingBackupPackages = new ArrayList<>(); - final Map<String, RestoreInfo> pendingRestorePackages = new HashMap<>(); - final List<RollbackData> changed; + final List<RollbackData> availableRollbacks; + final List<RollbackInfo> recentlyExecutedRollbacks; synchronized (mLock) { ensureRollbackDataLoadedLocked(); - changed = mAppDataRollbackHelper.computePendingBackupsAndRestores(userId, - pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, - mRecentlyExecutedRollbacks); + availableRollbacks = new ArrayList<>(mAvailableRollbacks); + recentlyExecutedRollbacks = new ArrayList<>(mRecentlyExecutedRollbacks); } - mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, - pendingBackupPackages, pendingRestorePackages, changed); + final List<RollbackData> changed = + mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, + availableRollbacks, recentlyExecutedRollbacks); for (RollbackData rd : changed) { try { @@ -844,13 +842,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : rd.packages) { if (info.getPackageName().equals(packageName)) { info.getInstalledUsers().addAll(IntArray.wrap(installedUsers)); - AppDataRollbackHelper.SnapshotAppDataResult rs = - mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); - info.getPendingBackups().addAll(rs.pendingBackups); - for (int i = 0; i < rs.ceSnapshotInodes.size(); i++) { - info.putCeSnapshotInode(rs.ceSnapshotInodes.keyAt(i), - rs.ceSnapshotInodes.valueAt(i)); - } + mAppDataRollbackHelper.snapshotAppData(rd.rollbackId, info); try { mRollbackStore.saveAvailableRollback(rd); } catch (IOException ioe) { @@ -919,18 +911,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { VersionedPackage installedVersion = new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); - final AppDataRollbackHelper.SnapshotAppDataResult result; - if (snapshotUserData && !isApex) { - result = mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); - } else { - result = new AppDataRollbackHelper.SnapshotAppDataResult(IntArray.wrap(new int[0]), - new SparseLongArray()); - } - PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, - result.pendingBackups, new ArrayList<>(), isApex, IntArray.wrap(installedUsers), - result.ceSnapshotInodes); - + new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, + isApex, IntArray.wrap(installedUsers), + new SparseLongArray() /* ceSnapshotInodes */); RollbackData data; try { int childSessionId = session.getSessionId(); @@ -948,7 +932,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { int rollbackId = allocateRollbackIdLocked(); if (session.isStaged()) { data = mRollbackStore.createPendingStagedRollback(rollbackId, - parentSessionId); + parentSessionId); } else { data = mRollbackStore.createAvailableRollback(rollbackId); } @@ -961,6 +945,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } + if (snapshotUserData && !isApex) { + mAppDataRollbackHelper.snapshotAppData(data.rollbackId, info); + } + try { RollbackStore.backupPackageCode(data, packageName, pkgInfo.applicationInfo.sourceDir); } catch (IOException e) { @@ -980,8 +968,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { final RollbackData rollbackData = getRollbackForPackage(packageName); for (int userId : userIds) { + if (rollbackData == null || !rollbackData.inProgress) { + Log.e(TAG, "Request to restore userData for: " + packageName + + ", but no rollback in progress."); + continue; + } + final PackageRollbackInfo info = getPackageRollbackInfo(rollbackData, packageName); final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData( - packageName, rollbackData, userId, appId, ceDataInode, seInfo); + rollbackData.rollbackId, info, userId, appId, seInfo); + // We've updated metadata about this rollback, so save it to flash. if (changedRollbackData) { try { @@ -1252,7 +1247,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from * a specified {@code RollbackData}. */ - static PackageRollbackInfo getPackageRollbackInfo(RollbackData data, + private static PackageRollbackInfo getPackageRollbackInfo(RollbackData data, String packageName) { for (PackageRollbackInfo info : data.packages) { if (info.getPackageName().equals(packageName)) { @@ -1281,11 +1276,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private void deleteRollback(RollbackData rollbackData) { for (PackageRollbackInfo info : rollbackData.packages) { IntArray installedUsers = info.getInstalledUsers(); - SparseLongArray ceSnapshotInodes = info.getCeSnapshotInodes(); for (int i = 0; i < installedUsers.size(); i++) { int userId = installedUsers.get(i); - mAppDataRollbackHelper.destroyAppDataSnapshot(info.getPackageName(), userId, - ceSnapshotInodes.get(userId, 0)); + mAppDataRollbackHelper.destroyAppDataSnapshot(rollbackData.rollbackId, info, + userId); } } mRollbackStore.deleteAvailableRollback(rollbackData); diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index 50dbaf570e9e..d848b2dc75fe 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -16,19 +16,22 @@ package com.android.server.rollback; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.PackageRollbackInfo.RestoreInfo; +import android.content.rollback.RollbackInfo; import android.util.IntArray; import android.util.SparseLongArray; @@ -42,7 +45,9 @@ import org.mockito.Mockito; import java.io.File; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @RunWith(JUnit4.class) public class AppDataRollbackHelperTest { @@ -55,72 +60,54 @@ public class AppDataRollbackHelperTest { // All users are unlocked so we should snapshot data for them. doReturn(true).when(helper).isUserCredentialLocked(eq(10)); doReturn(true).when(helper).isUserCredentialLocked(eq(11)); - AppDataRollbackHelper.SnapshotAppDataResult result = helper.snapshotAppData("com.foo.bar", - new int[]{10, 11}); + PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar", new int[]{10, 11}); + helper.snapshotAppData(5, info); - assertEquals(2, result.pendingBackups.size()); - assertEquals(10, result.pendingBackups.get(0)); - assertEquals(11, result.pendingBackups.get(1)); + assertEquals(2, info.getPendingBackups().size()); + assertEquals(10, info.getPendingBackups().get(0)); + assertEquals(11, info.getPendingBackups().get(1)); - assertEquals(0, result.ceSnapshotInodes.size()); + assertEquals(0, info.getCeSnapshotInodes().size()); InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).snapshotAppData( - eq("com.foo.bar"), eq(10), eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo.bar"), eq(10), eq(5), eq(Installer.FLAG_STORAGE_DE)); inOrder.verify(installer).snapshotAppData( - eq("com.foo.bar"), eq(11), eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo.bar"), eq(11), eq(5), eq(Installer.FLAG_STORAGE_DE)); inOrder.verifyNoMoreInteractions(); // One of the users is unlocked but the other isn't doReturn(false).when(helper).isUserCredentialLocked(eq(10)); doReturn(true).when(helper).isUserCredentialLocked(eq(11)); - when(installer.snapshotAppData(anyString(), anyInt(), anyInt())).thenReturn(239L); + when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(239L); - result = helper.snapshotAppData("com.foo.bar", new int[]{10, 11}); - assertEquals(1, result.pendingBackups.size()); - assertEquals(11, result.pendingBackups.get(0)); + PackageRollbackInfo info2 = createPackageRollbackInfo("com.foo.bar", new int[]{10, 11}); + helper.snapshotAppData(7, info2); + assertEquals(1, info2.getPendingBackups().size()); + assertEquals(11, info2.getPendingBackups().get(0)); - assertEquals(1, result.ceSnapshotInodes.size()); - assertEquals(239L, result.ceSnapshotInodes.get(10)); + assertEquals(1, info2.getCeSnapshotInodes().size()); + assertEquals(239L, info2.getCeSnapshotInodes().get(10)); inOrder = Mockito.inOrder(installer); inOrder.verify(installer).snapshotAppData( - eq("com.foo.bar"), eq(10), + eq("com.foo.bar"), eq(10), eq(7), eq(Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE)); inOrder.verify(installer).snapshotAppData( - eq("com.foo.bar"), eq(11), eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo.bar"), eq(11), eq(7), eq(Installer.FLAG_STORAGE_DE)); inOrder.verifyNoMoreInteractions(); } - private static RollbackData createInProgressRollbackData(String packageName) { - RollbackData data = new RollbackData(1, new File("/does/not/exist"), -1, true); - data.packages.add(new PackageRollbackInfo( - new VersionedPackage(packageName, 1), new VersionedPackage(packageName, 1), - new IntArray(), new ArrayList<>(), false, new IntArray(), new SparseLongArray())); - data.inProgress = true; - - return data; + private static PackageRollbackInfo createPackageRollbackInfo(String packageName, + final int[] installedUsers) { + return new PackageRollbackInfo( + new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1), + new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers), + new SparseLongArray()); } - @Test - public void testRestoreAppDataSnapshot_noRollbackData() throws Exception { - Installer installer = mock(Installer.class); - AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer)); - - assertFalse(helper.restoreAppData("com.foo", null, 0, 0, 0, "seinfo")); - verifyZeroInteractions(installer); - } - - @Test - public void testRestoreAppDataSnapshot_noRollbackInProgress() throws Exception { - Installer installer = mock(Installer.class); - AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer)); - - RollbackData rd = createInProgressRollbackData("com.foo"); - // Override the in progress flag. - rd.inProgress = false; - assertFalse(helper.restoreAppData("com.foo", rd, 0, 0, 0, "seinfo")); - verifyZeroInteractions(installer); + private static PackageRollbackInfo createPackageRollbackInfo(String packageName) { + return createPackageRollbackInfo(packageName, new int[] {}); } @Test @@ -128,18 +115,20 @@ public class AppDataRollbackHelperTest { Installer installer = mock(Installer.class); AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer)); - RollbackData rd = createInProgressRollbackData("com.foo"); - IntArray pendingBackups = rd.packages.get(0).getPendingBackups(); + PackageRollbackInfo info = createPackageRollbackInfo("com.foo"); + IntArray pendingBackups = info.getPendingBackups(); pendingBackups.add(10); pendingBackups.add(11); - assertTrue(helper.restoreAppData("com.foo", rd, 10 /* userId */, 1, 2, "seinfo")); + assertTrue(helper.restoreAppData(13 /* rollbackId */, info, 10 /* userId */, 1 /* appId */, + "seinfo")); // Should only require FLAG_STORAGE_DE here because we have a pending backup that we // didn't manage to execute. InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).restoreAppDataSnapshot( - eq("com.foo"), eq(1), eq(2L), eq("seinfo"), eq(10), eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */, + eq(13) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE)); inOrder.verifyNoMoreInteractions(); assertEquals(1, pendingBackups.size()); @@ -152,16 +141,18 @@ public class AppDataRollbackHelperTest { AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer)); doReturn(true).when(helper).isUserCredentialLocked(eq(10)); - RollbackData rd = createInProgressRollbackData("com.foo"); + PackageRollbackInfo info = createPackageRollbackInfo("com.foo"); - assertTrue(helper.restoreAppData("com.foo", rd, 10 /* userId */, 1, 2, "seinfo")); + assertTrue(helper.restoreAppData(73 /* rollbackId */, info, 10 /* userId */, 1 /* appId */, + "seinfo")); InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).restoreAppDataSnapshot( - eq("com.foo"), eq(1), eq(2L), eq("seinfo"), eq(10), eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */, + eq(73) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE)); inOrder.verifyNoMoreInteractions(); - ArrayList<RestoreInfo> pendingRestores = rd.packages.get(0).getPendingRestores(); + ArrayList<RestoreInfo> pendingRestores = info.getPendingRestores(); assertEquals(1, pendingRestores.size()); assertEquals(10, pendingRestores.get(0).userId); assertEquals(1, pendingRestores.get(0).appId); @@ -169,21 +160,23 @@ public class AppDataRollbackHelperTest { } @Test - public void testRestoreAppDataSnapshot_availableBackupForUnockedUser() throws Exception { + public void testRestoreAppDataSnapshot_availableBackupForUnlockedUser() throws Exception { Installer installer = mock(Installer.class); AppDataRollbackHelper helper = spy(new AppDataRollbackHelper(installer)); doReturn(false).when(helper).isUserCredentialLocked(eq(10)); - RollbackData rd = createInProgressRollbackData("com.foo"); - assertFalse(helper.restoreAppData("com.foo", rd, 10 /* userId */, 1, 2, "seinfo")); + PackageRollbackInfo info = createPackageRollbackInfo("com.foo"); + assertFalse(helper.restoreAppData(101 /* rollbackId */, info, 10 /* userId */, + 1 /* appId */, "seinfo")); InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).restoreAppDataSnapshot( - eq("com.foo"), eq(1), eq(2L), eq("seinfo"), eq(10), + eq("com.foo"), eq(1) /* appId */, eq("seinfo"), eq(10) /* userId */, + eq(101) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE)); inOrder.verifyNoMoreInteractions(); - ArrayList<RestoreInfo> pendingRestores = rd.packages.get(0).getPendingRestores(); + ArrayList<RestoreInfo> pendingRestores = info.getPendingRestores(); assertEquals(0, pendingRestores.size()); } @@ -191,48 +184,99 @@ public class AppDataRollbackHelperTest { public void destroyAppData() throws Exception { Installer installer = mock(Installer.class); AppDataRollbackHelper helper = new AppDataRollbackHelper(installer); - SparseLongArray ceSnapshotInodes = new SparseLongArray(); - ceSnapshotInodes.put(11, 239L); - helper.destroyAppDataSnapshot("com.foo.bar", 10, 0L); - helper.destroyAppDataSnapshot("com.foo.bar", 11, 239L); + PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar"); + info.putCeSnapshotInode(11, 239L); + helper.destroyAppDataSnapshot(5 /* rollbackId */, info, 10 /* userId */); + helper.destroyAppDataSnapshot(5 /* rollbackId */, info, 11 /* userId */); InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).destroyAppDataSnapshot( - eq("com.foo.bar"), eq(10), eq(0L), - eq(Installer.FLAG_STORAGE_DE)); + eq("com.foo.bar"), eq(10) /* userId */, eq(0L) /* ceSnapshotInode */, + eq(5) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE)); inOrder.verify(installer).destroyAppDataSnapshot( - eq("com.foo.bar"), eq(11), eq(239L), - eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE)); + eq("com.foo.bar"), eq(11) /* userId */, eq(239L) /* ceSnapshotInode */, + eq(5) /* rollbackId */, eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE)); inOrder.verifyNoMoreInteractions(); + + assertEquals(0, info.getCeSnapshotInodes().size()); } @Test - public void commitPendingBackupAndRestoreForUser_updatesRollbackData() throws Exception { + public void commitPendingBackupAndRestoreForUser() throws Exception { Installer installer = mock(Installer.class); AppDataRollbackHelper helper = new AppDataRollbackHelper(installer); - ArrayList<RollbackData> changedRollbackData = new ArrayList<>(); - changedRollbackData.add(createInProgressRollbackData("com.foo.bar")); - - when(installer.snapshotAppData(anyString(), anyInt(), anyInt())).thenReturn(239L); + when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(53L); + + // This one should be backed up. + PackageRollbackInfo pendingBackup = createPackageRollbackInfo("com.foo", new int[]{37, 73}); + pendingBackup.addPendingBackup(37); + + // Nothing should be done for this one. + PackageRollbackInfo wasRecentlyRestored = createPackageRollbackInfo("com.bar", + new int[]{37, 73}); + wasRecentlyRestored.addPendingBackup(37); + wasRecentlyRestored.getPendingRestores().add( + new RestoreInfo(37 /* userId */, 239 /* appId*/, "seInfo")); + + // This one should be restored + PackageRollbackInfo pendingRestore = createPackageRollbackInfo("com.abc", + new int[]{37, 73}); + pendingRestore.putCeSnapshotInode(37, 1543L); + pendingRestore.getPendingRestores().add( + new RestoreInfo(37 /* userId */, 57 /* appId*/, "seInfo")); + + // This one shouldn't be processed, because it hasn't pending backups/restores for userId + // 37. + PackageRollbackInfo ignoredInfo = createPackageRollbackInfo("com.bar", + new int[]{3, 73}); + wasRecentlyRestored.addPendingBackup(3); + wasRecentlyRestored.addPendingBackup(73); + wasRecentlyRestored.getPendingRestores().add( + new RestoreInfo(73 /* userId */, 239 /* appId*/, "seInfo")); + + RollbackData dataWithPendingBackup = new RollbackData(101, new File("/does/not/exist"), -1, + true); + dataWithPendingBackup.packages.add(pendingBackup); + + RollbackData dataWithRecentRestore = new RollbackData(17239, new File("/does/not/exist"), + -1, true); + dataWithRecentRestore.packages.add(wasRecentlyRestored); + + RollbackData dataForDifferentUser = new RollbackData(17239, new File("/does/not/exist"), + -1, true); + dataForDifferentUser.packages.add(ignoredInfo); + + RollbackInfo rollbackInfo = new RollbackInfo(17239, + Arrays.asList(pendingRestore, wasRecentlyRestored), false); + + List<RollbackData> changed = helper.commitPendingBackupAndRestoreForUser(37, + Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser), + Collections.singletonList(rollbackInfo)); + InOrder inOrder = Mockito.inOrder(installer); - ArrayList<String> pendingBackups = new ArrayList<>(); - pendingBackups.add("com.foo.bar"); + // Check that pending backup and restore for the same package mutually destroyed each other. + assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37)); + assertNull(wasRecentlyRestored.getRestoreInfo(37)); - helper.commitPendingBackupAndRestoreForUser(11, pendingBackups, - new HashMap<>() /* pendingRestores */, changedRollbackData); + // Check that backup was performed. + 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)); - assertEquals(1, changedRollbackData.size()); - assertEquals(1, changedRollbackData.get(0).packages.size()); - PackageRollbackInfo info = changedRollbackData.get(0).packages.get(0); + // Check that changed returns correct RollbackData. + assertEquals(2, changed.size()); + assertEquals(dataWithPendingBackup, changed.get(0)); + assertEquals(dataWithRecentRestore, changed.get(1)); - assertEquals(1, info.getCeSnapshotInodes().size()); - assertEquals(239L, info.getCeSnapshotInodes().get(11)); + // Check that restore was performed. + inOrder.verify(installer).restoreAppDataSnapshot( + eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */, + eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE)); + assertNull(pendingRestore.getRestoreInfo(37)); - InOrder inOrder = Mockito.inOrder(installer); - inOrder.verify(installer).snapshotAppData("com.foo.bar", 11 /* userId */, - Installer.FLAG_STORAGE_CE); inOrder.verifyNoMoreInteractions(); } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index f3edf09c5e95..bd0881f18423 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -587,9 +587,13 @@ public class RollbackTest { RollbackTestUtils.installMultiPackage(false, "RollbackTestAppAv1.apk", "RollbackTestAppBv1.apk"); + processUserData(TEST_APP_A); + processUserData(TEST_APP_B); RollbackTestUtils.installMultiPackage(true, "RollbackTestAppAv2.apk", "RollbackTestAppBv2.apk"); + processUserData(TEST_APP_A); + processUserData(TEST_APP_B); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); @@ -614,6 +618,9 @@ public class RollbackTest { assertRollbackInfoForAandB(rollbackB); assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId()); + + processUserData(TEST_APP_A); + processUserData(TEST_APP_B); } finally { RollbackTestUtils.dropShellPermissionIdentity(); } |