Loading core/java/android/content/rollback/PackageRollbackInfo.java +34 −1 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.IntArray; import android.util.IntArray; import android.util.SparseLongArray; import java.util.ArrayList; import java.util.ArrayList; Loading Loading @@ -73,6 +74,18 @@ public final class PackageRollbackInfo implements Parcelable { */ */ private final boolean mIsApex; private final boolean mIsApex; /* * The list of users the package is installed for. */ // NOTE: Not a part of the Parcelable representation of this object. private final IntArray mInstalledUsers; /** * A mapping between user and an inode of theirs CE data snapshot. */ // NOTE: Not a part of the Parcelable representation of this object. private final SparseLongArray mCeSnapshotInodes; /** /** * Returns the name of the package to roll back from. * Returns the name of the package to roll back from. */ */ Loading Loading @@ -125,16 +138,34 @@ public final class PackageRollbackInfo implements Parcelable { return mIsApex; return mIsApex; } } /** @hide */ public IntArray getInstalledUsers() { return mInstalledUsers; } /** @hide */ public SparseLongArray getCeSnapshotInodes() { return mCeSnapshotInodes; } /** @hide */ public void putCeSnapshotInode(int userId, long ceSnapshotInode) { mCeSnapshotInodes.put(userId, ceSnapshotInode); } /** @hide */ /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, boolean isApex) { boolean isApex, @NonNull IntArray installedUsers, @NonNull SparseLongArray ceSnapshotInodes) { this.mVersionRolledBackFrom = packageRolledBackFrom; this.mVersionRolledBackFrom = packageRolledBackFrom; this.mVersionRolledBackTo = packageRolledBackTo; this.mVersionRolledBackTo = packageRolledBackTo; this.mPendingBackups = pendingBackups; this.mPendingBackups = pendingBackups; this.mPendingRestores = pendingRestores; this.mPendingRestores = pendingRestores; this.mIsApex = isApex; this.mIsApex = isApex; this.mInstalledUsers = installedUsers; this.mCeSnapshotInodes = ceSnapshotInodes; } } private PackageRollbackInfo(Parcel in) { private PackageRollbackInfo(Parcel in) { Loading @@ -143,6 +174,8 @@ public final class PackageRollbackInfo implements Parcelable { this.mIsApex = in.readBoolean(); this.mIsApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingRestores = null; this.mPendingBackups = null; this.mPendingBackups = null; this.mInstalledUsers = null; this.mCeSnapshotInodes = null; } } @Override @Override Loading services/core/java/com/android/server/pm/Installer.java +54 −4 Original line number Original line Diff line number Diff line Loading @@ -611,18 +611,43 @@ public class Installer extends SystemService { } } } } public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) /** * Snapshots user data of the given package. * * @param pkg name of the package to snapshot user data for. * @param userId id of the user whose data to 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 * shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to snapshot user data. */ public long snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) throws InstallerException { throws InstallerException { if (!checkBeforeRemote()) return false; if (!checkBeforeRemote()) return 0; try { try { mInstalld.snapshotAppData(null, pkg, userId, storageFlags); return mInstalld.snapshotAppData(null, pkg, userId, storageFlags); return true; } catch (Exception e) { } catch (Exception e) { throw InstallerException.from(e); throw InstallerException.from(e); } } } } /** * Restores user data snapshot of the given package. * * @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 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 * shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to restore user data. */ public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; if (!checkBeforeRemote()) return false; Loading @@ -636,6 +661,31 @@ public class Installer extends SystemService { } } } } /** * Deletes user data snapshot of the given package. * * @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 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 * remote call shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to delete user data snapshot. */ public boolean destroyAppDataSnapshot(String pkg, @UserIdInt int userId, long ceSnapshotInode, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; try { mInstalld.destroyAppDataSnapshot(null, pkg, userId, ceSnapshotInode, storageFlags); return true; } catch (Exception e) { throw InstallerException.from(e); } } private static void assertValidInstructionSet(String instructionSet) private static void assertValidInstructionSet(String instructionSet) throws InstallerException { throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { for (String abi : Build.SUPPORTED_ABIS) { Loading services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +65 −9 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.rollback.RollbackInfo; import android.os.storage.StorageManager; import android.os.storage.StorageManager; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.Installer; import com.android.server.pm.Installer; Loading Loading @@ -51,11 +52,13 @@ public class AppDataRollbackHelper { * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, * a specified set of users for whom the package is installed. * a specified set of users for whom the package is installed. * * * @return a list of users for which the snapshot is pending, usually because data for one or * @return a {@link SnapshotAppDataResult}/ * more users is still credential locked. * @see SnapshotAppDataResult */ */ public IntArray snapshotAppData(String packageName, int[] installedUsers) { public SnapshotAppDataResult snapshotAppData(String packageName, int[] installedUsers) { final IntArray pendingBackups = new IntArray(); final IntArray pendingBackups = new IntArray(); final SparseLongArray ceSnapshotInodes = new SparseLongArray(); for (int user : installedUsers) { for (int user : installedUsers) { final int storageFlags; final int storageFlags; if (isUserCredentialLocked(user)) { if (isUserCredentialLocked(user)) { Loading @@ -69,14 +72,17 @@ public class AppDataRollbackHelper { } } try { try { mInstaller.snapshotAppData(packageName, user, storageFlags); long ceSnapshotInode = mInstaller.snapshotAppData(packageName, user, storageFlags); if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) { ceSnapshotInodes.put(user, ceSnapshotInode); } } catch (InstallerException ie) { } catch (InstallerException ie) { Log.e(TAG, "Unable to create app data snapshot for: " + packageName Log.e(TAG, "Unable to create app data snapshot for: " + packageName + ", userId: " + user, ie); + ", userId: " + user, ie); } } } } return pendingBackups; return new SnapshotAppDataResult(pendingBackups, ceSnapshotInodes); } } /** /** Loading Loading @@ -137,6 +143,22 @@ public class AppDataRollbackHelper { return changedRollbackData; return changedRollbackData; } } /** * Deletes an app data data snapshot for a specified package {@code packageName} for a * given {@code user}. */ public void destroyAppDataSnapshot(String packageName, int user, long ceSnapshotInode) { int storageFlags = Installer.FLAG_STORAGE_DE; if (ceSnapshotInode > 0) { storageFlags |= Installer.FLAG_STORAGE_CE; } try { mInstaller.destroyAppDataSnapshot(packageName, user, ceSnapshotInode, storageFlags); } catch (InstallerException ie) { Log.e(TAG, "Unable to delete app data snapshot for " + packageName, ie); } } /** /** * Computes the list of pending backups and restores for {@code userId} given lists of * 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 * available and recent rollbacks. Packages pending backup for the given user are added Loading Loading @@ -191,16 +213,28 @@ public class AppDataRollbackHelper { } } /** /** * Commits the list of pending backups and restores for a given {@code userId}. * 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. */ */ public void commitPendingBackupAndRestoreForUser(int userId, public void commitPendingBackupAndRestoreForUser(int userId, ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores) { ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores, List<RollbackData> changedRollbackData) { if (!pendingBackups.isEmpty()) { if (!pendingBackups.isEmpty()) { for (String packageName : pendingBackups) { for (String packageName : pendingBackups) { try { try { mInstaller.snapshotAppData(packageName, userId, Installer.FLAG_STORAGE_CE); 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); } } } } catch (InstallerException ie) { } catch (InstallerException ie) { Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); Log.e(TAG, "Unable to create app data snapshot for: " + packageName + ", userId: " + userId, ie); } } } } } } Loading Loading @@ -233,4 +267,26 @@ public class AppDataRollbackHelper { return StorageManager.isFileEncryptedNativeOrEmulated() return StorageManager.isFileEncryptedNativeOrEmulated() && !StorageManager.isUserKeyUnlocked(userId); && !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; } } } } services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +42 −20 Original line number Original line Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.os.Process; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.LocalServices; Loading Loading @@ -110,7 +111,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final HandlerThread mHandlerThread; private final HandlerThread mHandlerThread; private final Installer mInstaller; private final Installer mInstaller; private final RollbackPackageHealthObserver mPackageHealthObserver; private final RollbackPackageHealthObserver mPackageHealthObserver; private final AppDataRollbackHelper mUserdataHelper; private final AppDataRollbackHelper mAppDataRollbackHelper; RollbackManagerServiceImpl(Context context) { RollbackManagerServiceImpl(Context context) { mContext = context; mContext = context; Loading @@ -124,7 +125,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); mUserdataHelper = new AppDataRollbackHelper(mInstaller); mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller); // Kick off loading of the rollback data from strorage in a background // Kick off loading of the rollback data from strorage in a background // thread. // thread. Loading Loading @@ -449,7 +450,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { for (PackageRollbackInfo info : data.packages) { if (info.getPackageName().equals(packageName)) { if (info.getPackageName().equals(packageName)) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); break; break; } } } } Loading @@ -464,13 +465,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { final List<RollbackData> changed; final List<RollbackData> changed; synchronized (mLock) { synchronized (mLock) { ensureRollbackDataLoadedLocked(); ensureRollbackDataLoadedLocked(); changed = mUserdataHelper.computePendingBackupsAndRestores(userId, changed = mAppDataRollbackHelper.computePendingBackupsAndRestores(userId, pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, mRecentlyExecutedRollbacks); mRecentlyExecutedRollbacks); } } mUserdataHelper.commitPendingBackupAndRestoreForUser(userId, mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, pendingBackupPackages, pendingRestorePackages); pendingBackupPackages, pendingRestorePackages, changed); for (RollbackData rd : changed) { for (RollbackData rd : changed) { try { try { Loading Loading @@ -520,7 +521,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // mAvailableRollbacks, or is it okay to leave as // mAvailableRollbacks, or is it okay to leave as // unavailable until the next reboot when it will go // unavailable until the next reboot when it will go // away on its own? // away on its own? mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } } } } Loading Loading @@ -592,7 +593,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { info.getVersionRolledBackFrom(), info.getVersionRolledBackFrom(), installedVersion)) { installedVersion)) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); break; break; } } } } Loading Loading @@ -705,7 +706,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } else if (oldest == null || oldest.isAfter(data.timestamp)) { } else if (oldest == null || oldest.isAfter(data.timestamp)) { oldest = data.timestamp; oldest = data.timestamp; } } Loading Loading @@ -821,9 +822,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { String packageName = newPackage.packageName; String packageName = newPackage.packageName; for (PackageRollbackInfo info : rd.packages) { for (PackageRollbackInfo info : rd.packages) { if (info.getPackageName().equals(packageName)) { if (info.getPackageName().equals(packageName)) { IntArray pendingBackups = mUserdataHelper.snapshotAppData( AppDataRollbackHelper.SnapshotAppDataResult rs = packageName, installedUsers); mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); info.getPendingBackups().addAll(pendingBackups); info.getPendingBackups().addAll(rs.pendingBackups); for (int i = 0; i < rs.ceSnapshotInodes.size(); i++) { info.putCeSnapshotInode(rs.ceSnapshotInodes.keyAt(i), rs.ceSnapshotInodes.valueAt(i)); } try { try { mRollbackStore.saveAvailableRollback(rd); mRollbackStore.saveAvailableRollback(rd); } catch (IOException ioe) { } catch (IOException ioe) { Loading Loading @@ -892,13 +897,18 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { VersionedPackage installedVersion = new VersionedPackage(packageName, VersionedPackage installedVersion = new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); pkgInfo.getLongVersionCode()); IntArray pendingBackups = IntArray.wrap(new int[0]); final AppDataRollbackHelper.SnapshotAppDataResult result; if (snapshotUserData && !isApex) { if (snapshotUserData && !isApex) { pendingBackups = mUserdataHelper.snapshotAppData(packageName, installedUsers); result = mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); } else { result = new AppDataRollbackHelper.SnapshotAppDataResult(IntArray.wrap(new int[0]), new SparseLongArray()); } } PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, pendingBackups, new ArrayList<>(), isApex); result.pendingBackups, new ArrayList<>(), isApex, IntArray.wrap(installedUsers), result.ceSnapshotInodes); RollbackData data; RollbackData data; try { try { int childSessionId = session.getSessionId(); int childSessionId = session.getSessionId(); Loading Loading @@ -948,9 +958,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { getHandler().post(() -> { final RollbackData rollbackData = getRollbackForPackage(packageName); final RollbackData rollbackData = getRollbackForPackage(packageName); for (int userId : userIds) { for (int userId : userIds) { final boolean changedRollbackData = mUserdataHelper.restoreAppData(packageName, final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData( rollbackData, userId, appId, ceDataInode, seInfo); packageName, rollbackData, userId, appId, ceDataInode, seInfo); // We've updated metadata about this rollback, so save it to flash. // We've updated metadata about this rollback, so save it to flash. if (changedRollbackData) { if (changedRollbackData) { try { try { Loading Loading @@ -1142,12 +1151,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); Log.e(TAG, "Unable to enable rollback", e); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } else { } else { // The install session was aborted, clean up the pending // The install session was aborted, clean up the pending // install. // install. mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } } } } Loading Loading @@ -1246,4 +1255,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { throw new IOException("Failed to allocate rollback ID"); throw new IOException("Failed to allocate rollback ID"); } } 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)); } } mRollbackStore.deleteAvailableRollback(rollbackData); } } } services/core/java/com/android/server/rollback/RollbackStore.java +32 −3 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackInfo; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseLongArray; import libcore.io.IoUtils; import libcore.io.IoUtils; Loading Loading @@ -160,6 +161,28 @@ class RollbackStore { return restoreInfos; return restoreInfos; } } private static @NonNull JSONArray ceSnapshotInodesToJson( @NonNull SparseLongArray ceSnapshotInodes) throws JSONException { JSONArray array = new JSONArray(); for (int i = 0; i < ceSnapshotInodes.size(); i++) { JSONObject entryJson = new JSONObject(); entryJson.put("userId", ceSnapshotInodes.keyAt(i)); entryJson.put("ceSnapshotInode", ceSnapshotInodes.valueAt(i)); array.put(entryJson); } return array; } private static @NonNull SparseLongArray ceSnapshotInodesFromJson(JSONArray json) throws JSONException { SparseLongArray ceSnapshotInodes = new SparseLongArray(json.length()); for (int i = 0; i < json.length(); i++) { JSONObject entry = json.getJSONObject(i); ceSnapshotInodes.append(entry.getInt("userId"), entry.getLong("ceSnapshotInode")); } return ceSnapshotInodes; } /** /** * Reads the list of recently executed rollbacks from persistent storage. * Reads the list of recently executed rollbacks from persistent storage. */ */ Loading Loading @@ -263,8 +286,6 @@ class RollbackStore { * rollback. * rollback. */ */ void deleteAvailableRollback(RollbackData data) { void deleteAvailableRollback(RollbackData data) { // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the // actual app. removeFile(data.backupDir); removeFile(data.backupDir); } } Loading Loading @@ -341,11 +362,15 @@ class RollbackStore { IntArray pendingBackups = info.getPendingBackups(); IntArray pendingBackups = info.getPendingBackups(); List<RestoreInfo> pendingRestores = info.getPendingRestores(); List<RestoreInfo> pendingRestores = info.getPendingRestores(); IntArray installedUsers = info.getInstalledUsers(); json.put("pendingBackups", convertToJsonArray(pendingBackups)); json.put("pendingBackups", convertToJsonArray(pendingBackups)); json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); json.put("isApex", info.isApex()); json.put("installedUsers", convertToJsonArray(installedUsers)); json.put("ceSnapshotInodes", ceSnapshotInodesToJson(info.getCeSnapshotInodes())); return json; return json; } } Loading @@ -362,8 +387,12 @@ class RollbackStore { final boolean isApex = json.getBoolean("isApex"); final boolean isApex = json.getBoolean("isApex"); final IntArray installedUsers = convertToIntArray(json.getJSONArray("installedUsers")); final SparseLongArray ceSnapshotInodes = ceSnapshotInodesFromJson( json.getJSONArray("ceSnapshotInodes")); return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, pendingBackups, pendingRestores, isApex); pendingBackups, pendingRestores, isApex, installedUsers, ceSnapshotInodes); } } private JSONArray versionedPackagesToJson(List<VersionedPackage> packages) private JSONArray versionedPackagesToJson(List<VersionedPackage> packages) Loading Loading
core/java/android/content/rollback/PackageRollbackInfo.java +34 −1 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.IntArray; import android.util.IntArray; import android.util.SparseLongArray; import java.util.ArrayList; import java.util.ArrayList; Loading Loading @@ -73,6 +74,18 @@ public final class PackageRollbackInfo implements Parcelable { */ */ private final boolean mIsApex; private final boolean mIsApex; /* * The list of users the package is installed for. */ // NOTE: Not a part of the Parcelable representation of this object. private final IntArray mInstalledUsers; /** * A mapping between user and an inode of theirs CE data snapshot. */ // NOTE: Not a part of the Parcelable representation of this object. private final SparseLongArray mCeSnapshotInodes; /** /** * Returns the name of the package to roll back from. * Returns the name of the package to roll back from. */ */ Loading Loading @@ -125,16 +138,34 @@ public final class PackageRollbackInfo implements Parcelable { return mIsApex; return mIsApex; } } /** @hide */ public IntArray getInstalledUsers() { return mInstalledUsers; } /** @hide */ public SparseLongArray getCeSnapshotInodes() { return mCeSnapshotInodes; } /** @hide */ public void putCeSnapshotInode(int userId, long ceSnapshotInode) { mCeSnapshotInodes.put(userId, ceSnapshotInode); } /** @hide */ /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, boolean isApex) { boolean isApex, @NonNull IntArray installedUsers, @NonNull SparseLongArray ceSnapshotInodes) { this.mVersionRolledBackFrom = packageRolledBackFrom; this.mVersionRolledBackFrom = packageRolledBackFrom; this.mVersionRolledBackTo = packageRolledBackTo; this.mVersionRolledBackTo = packageRolledBackTo; this.mPendingBackups = pendingBackups; this.mPendingBackups = pendingBackups; this.mPendingRestores = pendingRestores; this.mPendingRestores = pendingRestores; this.mIsApex = isApex; this.mIsApex = isApex; this.mInstalledUsers = installedUsers; this.mCeSnapshotInodes = ceSnapshotInodes; } } private PackageRollbackInfo(Parcel in) { private PackageRollbackInfo(Parcel in) { Loading @@ -143,6 +174,8 @@ public final class PackageRollbackInfo implements Parcelable { this.mIsApex = in.readBoolean(); this.mIsApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingRestores = null; this.mPendingBackups = null; this.mPendingBackups = null; this.mInstalledUsers = null; this.mCeSnapshotInodes = null; } } @Override @Override Loading
services/core/java/com/android/server/pm/Installer.java +54 −4 Original line number Original line Diff line number Diff line Loading @@ -611,18 +611,43 @@ public class Installer extends SystemService { } } } } public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) /** * Snapshots user data of the given package. * * @param pkg name of the package to snapshot user data for. * @param userId id of the user whose data to 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 * shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to snapshot user data. */ public long snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) throws InstallerException { throws InstallerException { if (!checkBeforeRemote()) return false; if (!checkBeforeRemote()) return 0; try { try { mInstalld.snapshotAppData(null, pkg, userId, storageFlags); return mInstalld.snapshotAppData(null, pkg, userId, storageFlags); return true; } catch (Exception e) { } catch (Exception e) { throw InstallerException.from(e); throw InstallerException.from(e); } } } } /** * Restores user data snapshot of the given package. * * @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 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 * shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to restore user data. */ public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; if (!checkBeforeRemote()) return false; Loading @@ -636,6 +661,31 @@ public class Installer extends SystemService { } } } } /** * Deletes user data snapshot of the given package. * * @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 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 * remote call shouldn't be continued. See {@link #checkBeforeRemote}. * * @throws InstallerException if failed to delete user data snapshot. */ public boolean destroyAppDataSnapshot(String pkg, @UserIdInt int userId, long ceSnapshotInode, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; try { mInstalld.destroyAppDataSnapshot(null, pkg, userId, ceSnapshotInode, storageFlags); return true; } catch (Exception e) { throw InstallerException.from(e); } } private static void assertValidInstructionSet(String instructionSet) private static void assertValidInstructionSet(String instructionSet) throws InstallerException { throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { for (String abi : Build.SUPPORTED_ABIS) { Loading
services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +65 −9 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.rollback.RollbackInfo; import android.os.storage.StorageManager; import android.os.storage.StorageManager; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.Installer; import com.android.server.pm.Installer; Loading Loading @@ -51,11 +52,13 @@ public class AppDataRollbackHelper { * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, * a specified set of users for whom the package is installed. * a specified set of users for whom the package is installed. * * * @return a list of users for which the snapshot is pending, usually because data for one or * @return a {@link SnapshotAppDataResult}/ * more users is still credential locked. * @see SnapshotAppDataResult */ */ public IntArray snapshotAppData(String packageName, int[] installedUsers) { public SnapshotAppDataResult snapshotAppData(String packageName, int[] installedUsers) { final IntArray pendingBackups = new IntArray(); final IntArray pendingBackups = new IntArray(); final SparseLongArray ceSnapshotInodes = new SparseLongArray(); for (int user : installedUsers) { for (int user : installedUsers) { final int storageFlags; final int storageFlags; if (isUserCredentialLocked(user)) { if (isUserCredentialLocked(user)) { Loading @@ -69,14 +72,17 @@ public class AppDataRollbackHelper { } } try { try { mInstaller.snapshotAppData(packageName, user, storageFlags); long ceSnapshotInode = mInstaller.snapshotAppData(packageName, user, storageFlags); if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) { ceSnapshotInodes.put(user, ceSnapshotInode); } } catch (InstallerException ie) { } catch (InstallerException ie) { Log.e(TAG, "Unable to create app data snapshot for: " + packageName Log.e(TAG, "Unable to create app data snapshot for: " + packageName + ", userId: " + user, ie); + ", userId: " + user, ie); } } } } return pendingBackups; return new SnapshotAppDataResult(pendingBackups, ceSnapshotInodes); } } /** /** Loading Loading @@ -137,6 +143,22 @@ public class AppDataRollbackHelper { return changedRollbackData; return changedRollbackData; } } /** * Deletes an app data data snapshot for a specified package {@code packageName} for a * given {@code user}. */ public void destroyAppDataSnapshot(String packageName, int user, long ceSnapshotInode) { int storageFlags = Installer.FLAG_STORAGE_DE; if (ceSnapshotInode > 0) { storageFlags |= Installer.FLAG_STORAGE_CE; } try { mInstaller.destroyAppDataSnapshot(packageName, user, ceSnapshotInode, storageFlags); } catch (InstallerException ie) { Log.e(TAG, "Unable to delete app data snapshot for " + packageName, ie); } } /** /** * Computes the list of pending backups and restores for {@code userId} given lists of * 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 * available and recent rollbacks. Packages pending backup for the given user are added Loading Loading @@ -191,16 +213,28 @@ public class AppDataRollbackHelper { } } /** /** * Commits the list of pending backups and restores for a given {@code userId}. * 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. */ */ public void commitPendingBackupAndRestoreForUser(int userId, public void commitPendingBackupAndRestoreForUser(int userId, ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores) { ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores, List<RollbackData> changedRollbackData) { if (!pendingBackups.isEmpty()) { if (!pendingBackups.isEmpty()) { for (String packageName : pendingBackups) { for (String packageName : pendingBackups) { try { try { mInstaller.snapshotAppData(packageName, userId, Installer.FLAG_STORAGE_CE); 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); } } } } catch (InstallerException ie) { } catch (InstallerException ie) { Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); Log.e(TAG, "Unable to create app data snapshot for: " + packageName + ", userId: " + userId, ie); } } } } } } Loading Loading @@ -233,4 +267,26 @@ public class AppDataRollbackHelper { return StorageManager.isFileEncryptedNativeOrEmulated() return StorageManager.isFileEncryptedNativeOrEmulated() && !StorageManager.isUserKeyUnlocked(userId); && !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; } } } }
services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +42 −20 Original line number Original line Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.os.Process; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.LocalServices; Loading Loading @@ -110,7 +111,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final HandlerThread mHandlerThread; private final HandlerThread mHandlerThread; private final Installer mInstaller; private final Installer mInstaller; private final RollbackPackageHealthObserver mPackageHealthObserver; private final RollbackPackageHealthObserver mPackageHealthObserver; private final AppDataRollbackHelper mUserdataHelper; private final AppDataRollbackHelper mAppDataRollbackHelper; RollbackManagerServiceImpl(Context context) { RollbackManagerServiceImpl(Context context) { mContext = context; mContext = context; Loading @@ -124,7 +125,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); mUserdataHelper = new AppDataRollbackHelper(mInstaller); mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller); // Kick off loading of the rollback data from strorage in a background // Kick off loading of the rollback data from strorage in a background // thread. // thread. Loading Loading @@ -449,7 +450,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { for (PackageRollbackInfo info : data.packages) { if (info.getPackageName().equals(packageName)) { if (info.getPackageName().equals(packageName)) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); break; break; } } } } Loading @@ -464,13 +465,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { final List<RollbackData> changed; final List<RollbackData> changed; synchronized (mLock) { synchronized (mLock) { ensureRollbackDataLoadedLocked(); ensureRollbackDataLoadedLocked(); changed = mUserdataHelper.computePendingBackupsAndRestores(userId, changed = mAppDataRollbackHelper.computePendingBackupsAndRestores(userId, pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, mRecentlyExecutedRollbacks); mRecentlyExecutedRollbacks); } } mUserdataHelper.commitPendingBackupAndRestoreForUser(userId, mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, pendingBackupPackages, pendingRestorePackages); pendingBackupPackages, pendingRestorePackages, changed); for (RollbackData rd : changed) { for (RollbackData rd : changed) { try { try { Loading Loading @@ -520,7 +521,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // mAvailableRollbacks, or is it okay to leave as // mAvailableRollbacks, or is it okay to leave as // unavailable until the next reboot when it will go // unavailable until the next reboot when it will go // away on its own? // away on its own? mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } } } } Loading Loading @@ -592,7 +593,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { info.getVersionRolledBackFrom(), info.getVersionRolledBackFrom(), installedVersion)) { installedVersion)) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); break; break; } } } } Loading Loading @@ -705,7 +706,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { iter.remove(); iter.remove(); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } else if (oldest == null || oldest.isAfter(data.timestamp)) { } else if (oldest == null || oldest.isAfter(data.timestamp)) { oldest = data.timestamp; oldest = data.timestamp; } } Loading Loading @@ -821,9 +822,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { String packageName = newPackage.packageName; String packageName = newPackage.packageName; for (PackageRollbackInfo info : rd.packages) { for (PackageRollbackInfo info : rd.packages) { if (info.getPackageName().equals(packageName)) { if (info.getPackageName().equals(packageName)) { IntArray pendingBackups = mUserdataHelper.snapshotAppData( AppDataRollbackHelper.SnapshotAppDataResult rs = packageName, installedUsers); mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); info.getPendingBackups().addAll(pendingBackups); info.getPendingBackups().addAll(rs.pendingBackups); for (int i = 0; i < rs.ceSnapshotInodes.size(); i++) { info.putCeSnapshotInode(rs.ceSnapshotInodes.keyAt(i), rs.ceSnapshotInodes.valueAt(i)); } try { try { mRollbackStore.saveAvailableRollback(rd); mRollbackStore.saveAvailableRollback(rd); } catch (IOException ioe) { } catch (IOException ioe) { Loading Loading @@ -892,13 +897,18 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { VersionedPackage installedVersion = new VersionedPackage(packageName, VersionedPackage installedVersion = new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); pkgInfo.getLongVersionCode()); IntArray pendingBackups = IntArray.wrap(new int[0]); final AppDataRollbackHelper.SnapshotAppDataResult result; if (snapshotUserData && !isApex) { if (snapshotUserData && !isApex) { pendingBackups = mUserdataHelper.snapshotAppData(packageName, installedUsers); result = mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); } else { result = new AppDataRollbackHelper.SnapshotAppDataResult(IntArray.wrap(new int[0]), new SparseLongArray()); } } PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, pendingBackups, new ArrayList<>(), isApex); result.pendingBackups, new ArrayList<>(), isApex, IntArray.wrap(installedUsers), result.ceSnapshotInodes); RollbackData data; RollbackData data; try { try { int childSessionId = session.getSessionId(); int childSessionId = session.getSessionId(); Loading Loading @@ -948,9 +958,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { getHandler().post(() -> { final RollbackData rollbackData = getRollbackForPackage(packageName); final RollbackData rollbackData = getRollbackForPackage(packageName); for (int userId : userIds) { for (int userId : userIds) { final boolean changedRollbackData = mUserdataHelper.restoreAppData(packageName, final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData( rollbackData, userId, appId, ceDataInode, seInfo); packageName, rollbackData, userId, appId, ceDataInode, seInfo); // We've updated metadata about this rollback, so save it to flash. // We've updated metadata about this rollback, so save it to flash. if (changedRollbackData) { if (changedRollbackData) { try { try { Loading Loading @@ -1142,12 +1151,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); Log.e(TAG, "Unable to enable rollback", e); mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } else { } else { // The install session was aborted, clean up the pending // The install session was aborted, clean up the pending // install. // install. mRollbackStore.deleteAvailableRollback(data); deleteRollback(data); } } } } } } Loading Loading @@ -1246,4 +1255,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { throw new IOException("Failed to allocate rollback ID"); throw new IOException("Failed to allocate rollback ID"); } } 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)); } } mRollbackStore.deleteAvailableRollback(rollbackData); } } }
services/core/java/com/android/server/rollback/RollbackStore.java +32 −3 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackInfo; import android.util.IntArray; import android.util.IntArray; import android.util.Log; import android.util.Log; import android.util.SparseLongArray; import libcore.io.IoUtils; import libcore.io.IoUtils; Loading Loading @@ -160,6 +161,28 @@ class RollbackStore { return restoreInfos; return restoreInfos; } } private static @NonNull JSONArray ceSnapshotInodesToJson( @NonNull SparseLongArray ceSnapshotInodes) throws JSONException { JSONArray array = new JSONArray(); for (int i = 0; i < ceSnapshotInodes.size(); i++) { JSONObject entryJson = new JSONObject(); entryJson.put("userId", ceSnapshotInodes.keyAt(i)); entryJson.put("ceSnapshotInode", ceSnapshotInodes.valueAt(i)); array.put(entryJson); } return array; } private static @NonNull SparseLongArray ceSnapshotInodesFromJson(JSONArray json) throws JSONException { SparseLongArray ceSnapshotInodes = new SparseLongArray(json.length()); for (int i = 0; i < json.length(); i++) { JSONObject entry = json.getJSONObject(i); ceSnapshotInodes.append(entry.getInt("userId"), entry.getLong("ceSnapshotInode")); } return ceSnapshotInodes; } /** /** * Reads the list of recently executed rollbacks from persistent storage. * Reads the list of recently executed rollbacks from persistent storage. */ */ Loading Loading @@ -263,8 +286,6 @@ class RollbackStore { * rollback. * rollback. */ */ void deleteAvailableRollback(RollbackData data) { void deleteAvailableRollback(RollbackData data) { // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the // actual app. removeFile(data.backupDir); removeFile(data.backupDir); } } Loading Loading @@ -341,11 +362,15 @@ class RollbackStore { IntArray pendingBackups = info.getPendingBackups(); IntArray pendingBackups = info.getPendingBackups(); List<RestoreInfo> pendingRestores = info.getPendingRestores(); List<RestoreInfo> pendingRestores = info.getPendingRestores(); IntArray installedUsers = info.getInstalledUsers(); json.put("pendingBackups", convertToJsonArray(pendingBackups)); json.put("pendingBackups", convertToJsonArray(pendingBackups)); json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); json.put("isApex", info.isApex()); json.put("installedUsers", convertToJsonArray(installedUsers)); json.put("ceSnapshotInodes", ceSnapshotInodesToJson(info.getCeSnapshotInodes())); return json; return json; } } Loading @@ -362,8 +387,12 @@ class RollbackStore { final boolean isApex = json.getBoolean("isApex"); final boolean isApex = json.getBoolean("isApex"); final IntArray installedUsers = convertToIntArray(json.getJSONArray("installedUsers")); final SparseLongArray ceSnapshotInodes = ceSnapshotInodesFromJson( json.getJSONArray("ceSnapshotInodes")); return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, pendingBackups, pendingRestores, isApex); pendingBackups, pendingRestores, isApex, installedUsers, ceSnapshotInodes); } } private JSONArray versionedPackagesToJson(List<VersionedPackage> packages) private JSONArray versionedPackagesToJson(List<VersionedPackage> packages) Loading