Have RollbackData reuse RollbackInfo.

Rather than duplicating the same information.

This is in preparation for storing available and recently committed
rollbacks the same way so we don't end up with duplicate copies of
PackageRollbackInfo for a rollback so we can fix the bug when doing
userdata restore for staged installs.

Bug: 124044231
Test: atest RollbackTest
Test: atest StagedRollbackTest
Test: atest AppDataRollbackHelperTest
Change-Id: I6ca164adc4351b778d153d4b33296386f6833b61
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0cde6ba..9459ad3 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -17,12 +17,10 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
-import android.content.pm.PackageInstaller;
 import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -44,13 +42,7 @@
     private final List<VersionedPackage> mCausePackages;
 
     private final boolean mIsStaged;
-    private final int mCommittedSessionId;
-
-    /** @hide */
-    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged) {
-        this(rollbackId, packages, isStaged, Collections.emptyList(),
-                PackageInstaller.SessionInfo.INVALID_ID);
-    }
+    private int mCommittedSessionId;
 
     /** @hide */
     public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged,
@@ -101,6 +93,14 @@
     }
 
     /**
+     * Sets the session ID for the committed rollback for staged rollbacks.
+     * @hide
+     */
+    public void setCommittedSessionId(int sessionId) {
+        mCommittedSessionId = sessionId;
+    }
+
+    /**
      * Gets the list of package versions that motivated this rollback.
      * As provided to {@link #commitRollback} when the rollback was committed.
      * This is only applicable for rollbacks that have been committed.
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index e9ccea54..36f18f0 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -166,7 +166,7 @@
         List<RollbackData> rd = new ArrayList<>();
 
         for (RollbackData data : availableRollbacks) {
-            for (PackageRollbackInfo info : data.packages) {
+            for (PackageRollbackInfo info : data.info.getPackages()) {
                 final IntArray pendingBackupUsers = info.getPendingBackups();
                 if (pendingBackupUsers != null) {
                     final int idx = pendingBackupUsers.indexOf(userId);
@@ -246,13 +246,13 @@
 
         if (!pendingBackupPackages.isEmpty()) {
             for (RollbackData data : pendingBackups) {
-                for (PackageRollbackInfo info : data.packages) {
+                for (PackageRollbackInfo info : data.info.getPackages()) {
                     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);
+                                    userId, data.info.getRollbackId(), Installer.FLAG_STORAGE_CE);
                             info.putCeSnapshotInode(userId, ceSnapshotInode);
                             pendingBackupUsers.remove(idx);
                         } catch (InstallerException ie) {
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index bad3963..655bf4a 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -16,12 +16,11 @@
 
 package com.android.server.rollback;
 
-import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
 
 import java.io.File;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Information about a rollback available for a set of atomically installed
@@ -29,14 +28,9 @@
  */
 class RollbackData {
     /**
-     * A unique identifier for this rollback.
+     * The rollback info for this rollback.
      */
-    public final int rollbackId;
-
-    /**
-     * The per-package rollback information.
-     */
-    public final List<PackageRollbackInfo> packages = new ArrayList<>();
+    public final RollbackInfo info;
 
     /**
      * The directory where the rollback data is stored.
@@ -76,17 +70,42 @@
     // NOTE: All accesses to this field are from the RollbackManager handler thread.
     public boolean restoreUserDataInProgress = false;
 
-    RollbackData(int rollbackId, File backupDir, int stagedSessionId, boolean isAvailable) {
-        this.rollbackId = rollbackId;
+    /**
+     * Constructs a new, empty RollbackData instance.
+     *
+     * @param rollbackId the id of the rollback.
+     * @param backupDir the directory where the rollback data is stored.
+     * @param stagedSessionId the session id if this is a staged rollback, -1 otherwise.
+     */
+    RollbackData(int rollbackId, File backupDir, int stagedSessionId) {
+        this.info = new RollbackInfo(rollbackId,
+                /* packages */ new ArrayList<>(),
+                /* isStaged */ stagedSessionId != -1,
+                /* causePackages */ new ArrayList<>(),
+                /* committedSessionId */ -1);
         this.backupDir = backupDir;
         this.stagedSessionId = stagedSessionId;
+        this.isAvailable = (stagedSessionId == -1);
+    }
+
+    /**
+     * Constructs a RollbackData instance with full rollback data information.
+     */
+    RollbackData(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
+            boolean isAvailable, int apkSessionId, boolean restoreUserDataInProgress) {
+        this.info = info;
+        this.backupDir = backupDir;
+        this.timestamp = timestamp;
+        this.stagedSessionId = stagedSessionId;
         this.isAvailable = isAvailable;
+        this.apkSessionId = apkSessionId;
+        this.restoreUserDataInProgress = restoreUserDataInProgress;
     }
 
     /**
      * Whether the rollback is for rollback of a staged install.
      */
     public boolean isStaged() {
-        return stagedSessionId != -1;
+        return info.isStaged();
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 767f56a..3e42b32 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -243,8 +243,7 @@
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
                 if (data.isAvailable) {
-                    rollbacks.add(new RollbackInfo(data.rollbackId,
-                                data.packages, data.isStaged()));
+                    rollbacks.add(data.info);
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -301,8 +300,8 @@
                         } catch (IOException ioe) {
                             // TODO: figure out the right way to deal with this, especially if
                             //  it fails for some data and succeeds for others.
-                            Log.e(TAG, "Unable to save rollback info for : " + data.rollbackId,
-                                    ioe);
+                            Log.e(TAG, "Unable to save rollback info for : "
+                                    + data.info.getRollbackId(), ioe);
                         }
                     }
 
@@ -349,7 +348,7 @@
         // rollback racing with a roll-forward fix of a buggy package.
         // Figure out how to ensure we don't commit the rollback if
         // roll forward happens at the same time.
-        for (PackageRollbackInfo info : data.packages) {
+        for (PackageRollbackInfo info : data.info.getPackages()) {
             VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
             if (installedVersion == null) {
                 // TODO: Test this case
@@ -391,7 +390,7 @@
             int parentSessionId = packageInstaller.createSession(parentParams);
             PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
 
-            for (PackageRollbackInfo info : data.packages) {
+            for (PackageRollbackInfo info : data.info.getPackages()) {
                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                 // TODO: We can't get the installerPackageName for apex
@@ -453,9 +452,9 @@
                                 return;
                             }
 
-                            addRecentlyExecutedRollback(new RollbackInfo(
-                                        data.rollbackId, data.packages, data.isStaged(),
-                                        causePackages, parentSessionId));
+                            data.info.setCommittedSessionId(parentSessionId);
+                            data.info.getCausePackages().addAll(causePackages);
+                            addRecentlyExecutedRollback(data.info);
                             sendSuccess(statusReceiver);
 
                             Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
@@ -510,7 +509,7 @@
             Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
-                for (PackageRollbackInfo info : data.packages) {
+                for (PackageRollbackInfo info : data.info.getPackages()) {
                     if (info.getPackageName().equals(packageName)) {
                         iter.remove();
                         deleteRollback(data);
@@ -539,7 +538,8 @@
                 try {
                     mRollbackStore.saveAvailableRollback(rd);
                 } catch (IOException ioe) {
-                    Log.e(TAG, "Unable to save rollback info for : " + rd.rollbackId, ioe);
+                    Log.e(TAG, "Unable to save rollback info for : "
+                            + rd.info.getRollbackId(), ioe);
                 }
             }
 
@@ -575,7 +575,7 @@
             synchronized (mLock) {
                 ensureRollbackDataLoadedLocked();
                 for (RollbackData data : mAvailableRollbacks) {
-                    if (data.stagedSessionId != -1) {
+                    if (!data.isAvailable && data.isStaged()) {
                         staged.add(data);
                     }
                 }
@@ -594,7 +594,7 @@
                             mRollbackStore.saveAvailableRollback(data);
                         } catch (IOException ioe) {
                             Log.e(TAG, "Unable to save rollback info for : "
-                                    + data.rollbackId, ioe);
+                                    + data.info.getRollbackId(), ioe);
                         }
                     } else if (session.isStagedSessionFailed()) {
                         // TODO: Do we need to remove this from
@@ -641,7 +641,7 @@
     private void loadAllRollbackDataLocked() {
         mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
         for (RollbackData data : mAvailableRollbacks) {
-            mAllocatedRollbackIds.put(data.rollbackId, true);
+            mAllocatedRollbackIds.put(data.info.getRollbackId(), true);
         }
 
         mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
@@ -665,7 +665,7 @@
             Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
-                for (PackageRollbackInfo info : data.packages) {
+                for (PackageRollbackInfo info : data.info.getPackages()) {
                     if (info.getPackageName().equals(packageName)
                             && !packageVersionsEqual(
                                         info.getVersionRolledBackFrom(),
@@ -897,10 +897,10 @@
                 return false;
             }
             String packageName = newPackage.packageName;
-            for (PackageRollbackInfo info : rd.packages) {
+            for (PackageRollbackInfo info : rd.info.getPackages()) {
                 if (info.getPackageName().equals(packageName)) {
                     info.getInstalledUsers().addAll(IntArray.wrap(installedUsers));
-                    mAppDataRollbackHelper.snapshotAppData(rd.rollbackId, info);
+                    mAppDataRollbackHelper.snapshotAppData(rd.info.getRollbackId(), info);
                     try {
                         mRollbackStore.saveAvailableRollback(rd);
                     } catch (IOException ioe) {
@@ -908,7 +908,8 @@
                         // again to save the rollback when the staged session
                         // is applied. Just so long as the device doesn't
                         // reboot before then.
-                        Log.e(TAG, "Unable to save rollback info for : " + rd.rollbackId, ioe);
+                        Log.e(TAG, "Unable to save rollback info for : "
+                                + rd.info.getRollbackId(), ioe);
                     }
                     return true;
                 }
@@ -989,14 +990,13 @@
                 if (data == null) {
                     int rollbackId = allocateRollbackIdLocked();
                     if (session.isStaged()) {
-                        data = mRollbackStore.createPendingStagedRollback(rollbackId,
-                            parentSessionId);
+                        data = mRollbackStore.createStagedRollback(rollbackId, parentSessionId);
                     } else {
-                        data = mRollbackStore.createAvailableRollback(rollbackId);
+                        data = mRollbackStore.createNonStagedRollback(rollbackId);
                     }
                     mPendingRollbacks.put(parentSessionId, data);
                 }
-                data.packages.add(info);
+                data.info.getPackages().add(info);
             }
         } catch (IOException e) {
             Log.e(TAG, "Unable to create rollback for " + packageName, e);
@@ -1004,7 +1004,7 @@
         }
 
         if (snapshotUserData && !isApex) {
-            mAppDataRollbackHelper.snapshotAppData(data.rollbackId, info);
+            mAppDataRollbackHelper.snapshotAppData(data.info.getRollbackId(), info);
         }
 
         try {
@@ -1053,7 +1053,7 @@
         for (int userId : userIds) {
             final PackageRollbackInfo info = getPackageRollbackInfo(rollbackData, packageName);
             final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData(
-                    rollbackData.rollbackId, info, userId, appId, seInfo);
+                    rollbackData.info.getRollbackId(), info, userId, appId, seInfo);
 
             // We've updated metadata about this rollback, so save it to flash.
             if (changedRollbackData) {
@@ -1139,7 +1139,8 @@
                 try {
                     mRollbackStore.saveAvailableRollback(rd);
                 } catch (IOException ioe) {
-                    Log.e(TAG, "Unable to save rollback info for : " + rd.rollbackId, ioe);
+                    Log.e(TAG, "Unable to save rollback info for : "
+                            + rd.info.getRollbackId(), ioe);
                 }
             }
         });
@@ -1233,8 +1234,8 @@
                     // After enabling and commiting any rollback, observe packages and
                     // prepare to rollback if packages crashes too frequently.
                     List<String> packages = new ArrayList<>();
-                    for (int i = 0; i < data.packages.size(); i++) {
-                        packages.add(data.packages.get(i).getPackageName());
+                    for (int i = 0; i < data.info.getPackages().size(); i++) {
+                        packages.add(data.info.getPackages().get(i).getPackageName());
                     }
                     mPackageHealthObserver.startObservingHealth(packages,
                             mRollbackLifetimeDurationInMillis);
@@ -1307,7 +1308,7 @@
             ensureRollbackDataLoadedLocked();
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
-                if (data.isAvailable && data.rollbackId == rollbackId) {
+                if (data.isAvailable && data.info.getRollbackId() == rollbackId) {
                     return data;
                 }
             }
@@ -1322,7 +1323,7 @@
      */
     private static PackageRollbackInfo getPackageRollbackInfo(RollbackData data,
             String packageName) {
-        for (PackageRollbackInfo info : data.packages) {
+        for (PackageRollbackInfo info : data.info.getPackages()) {
             if (info.getPackageName().equals(packageName)) {
                 return info;
             }
@@ -1347,12 +1348,12 @@
     }
 
     private void deleteRollback(RollbackData rollbackData) {
-        for (PackageRollbackInfo info : rollbackData.packages) {
+        for (PackageRollbackInfo info : rollbackData.info.getPackages()) {
             IntArray installedUsers = info.getInstalledUsers();
             for (int i = 0; i < installedUsers.size(); i++) {
                 int userId = installedUsers.get(i);
-                mAppDataRollbackHelper.destroyAppDataSnapshot(rollbackData.rollbackId, info,
-                        userId);
+                mAppDataRollbackHelper.destroyAppDataSnapshot(rollbackData.info.getRollbackId(),
+                        info, userId);
             }
         }
         mRollbackStore.deleteAvailableRollback(rollbackData);
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 23defad..b4cc6e4 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -229,17 +229,22 @@
     }
 
     /**
-     * Creates a new RollbackData instance with backupDir assigned.
+     * Creates a new RollbackData instance for a non-staged rollback with
+     * backupDir assigned.
      */
-    RollbackData createAvailableRollback(int rollbackId) throws IOException {
+    RollbackData createNonStagedRollback(int rollbackId) throws IOException {
         File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
-        return new RollbackData(rollbackId, backupDir, -1, true);
+        return new RollbackData(rollbackId, backupDir, -1);
     }
 
-    RollbackData createPendingStagedRollback(int rollbackId, int stagedSessionId)
+    /**
+     * Creates a new RollbackData instance for a staged rollback with
+     * backupDir assigned.
+     */
+    RollbackData createStagedRollback(int rollbackId, int stagedSessionId)
             throws IOException {
         File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
-        return new RollbackData(rollbackId, backupDir, stagedSessionId, false);
+        return new RollbackData(rollbackId, backupDir, stagedSessionId);
     }
 
     /**
@@ -277,8 +282,7 @@
     void saveAvailableRollback(RollbackData data) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
-            dataJson.put("rollbackId", data.rollbackId);
-            dataJson.put("packages", toJson(data.packages));
+            dataJson.put("info", rollbackInfoToJson(data.info));
             dataJson.put("timestamp", data.timestamp.toString());
             dataJson.put("stagedSessionId", data.stagedSessionId);
             dataJson.put("isAvailable", data.isAvailable);
@@ -334,16 +338,14 @@
             JSONObject dataJson = new JSONObject(
                     IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
 
-            RollbackData data = new RollbackData(
-                    dataJson.getInt("rollbackId"),
+            return new RollbackData(
+                    rollbackInfoFromJson(dataJson.getJSONObject("info")),
                     backupDir,
+                    Instant.parse(dataJson.getString("timestamp")),
                     dataJson.getInt("stagedSessionId"),
-                    dataJson.getBoolean("isAvailable"));
-            data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
-            data.timestamp = Instant.parse(dataJson.getString("timestamp"));
-            data.apkSessionId = dataJson.getInt("apkSessionId");
-            data.restoreUserDataInProgress = dataJson.getBoolean("restoreUserDataInProgress");
-            return data;
+                    dataJson.getBoolean("isAvailable"),
+                    dataJson.getInt("apkSessionId"),
+                    dataJson.getBoolean("restoreUserDataInProgress"));
         } catch (JSONException | DateTimeParseException e) {
             throw new IOException(e);
         }
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 d848b2d..fc7ccc5 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -236,20 +236,20 @@
         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 dataWithPendingBackup = new RollbackData(101, new File("/does/not/exist"), -1);
+        dataWithPendingBackup.info.getPackages().add(pendingBackup);
 
         RollbackData dataWithRecentRestore = new RollbackData(17239, new File("/does/not/exist"),
-                -1, true);
-        dataWithRecentRestore.packages.add(wasRecentlyRestored);
+                -1);
+        dataWithRecentRestore.info.getPackages().add(wasRecentlyRestored);
 
         RollbackData dataForDifferentUser = new RollbackData(17239, new File("/does/not/exist"),
-                -1, true);
-        dataForDifferentUser.packages.add(ignoredInfo);
+                -1);
+        dataForDifferentUser.info.getPackages().add(ignoredInfo);
 
         RollbackInfo rollbackInfo = new RollbackInfo(17239,
-                Arrays.asList(pendingRestore, wasRecentlyRestored), false);
+                Arrays.asList(pendingRestore, wasRecentlyRestored), false,
+                new ArrayList<>(), -1);
 
         List<RollbackData> changed = helper.commitPendingBackupAndRestoreForUser(37,
                 Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser),