Merge "Add a way to record causePackages for rollbacks."
diff --git a/api/system-current.txt b/api/system-current.txt
index 82bafab..90191b6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1698,6 +1698,7 @@
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public java.util.List<android.content.pm.VersionedPackage> getCausePackages();
     method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
     method public int getRollbackId();
     method public int getSessionId();
@@ -1707,7 +1708,7 @@
   }
 
   public final class RollbackManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull android.content.IntentSender);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull java.util.List<android.content.pm.VersionedPackage>, @NonNull android.content.IntentSender);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 226d76a..32e2198 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -26,8 +26,8 @@
     ParceledListSlice getAvailableRollbacks();
     ParceledListSlice getRecentlyExecutedRollbacks();
 
-    void commitRollback(int rollbackId, String callerPackageName,
-            in IntentSender statusReceiver);
+    void commitRollback(int rollbackId, in ParceledListSlice causePackages,
+            String callerPackageName, in IntentSender statusReceiver);
 
     // Exposed for use from the system server only. Callback from the package
     // manager during the install flow when user data can be restored for a given
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 812f995..1111b43 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.content.pm.PackageInstaller;
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,15 +40,20 @@
 
     private final List<PackageRollbackInfo> mPackages;
 
+    private final List<VersionedPackage> mCausePackages;
+
     /** @hide */
-    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
+    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages,
+            List<VersionedPackage> causePackages) {
         this.mRollbackId = rollbackId;
         this.mPackages = packages;
+        this.mCausePackages = causePackages;
     }
 
     private RollbackInfo(Parcel in) {
         mRollbackId = in.readInt();
         mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
+        mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
     }
 
     /**
@@ -82,6 +88,15 @@
         return PackageInstaller.SessionInfo.INVALID_ID;
     }
 
+    /**
+     * 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.
+     */
+    public List<VersionedPackage> getCausePackages() {
+        return mCausePackages;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -91,6 +106,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mRollbackId);
         out.writeTypedList(mPackages);
+        out.writeTypedList(mCausePackages);
     }
 
     public static final Parcelable.Creator<RollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index f8abcfc..7795df5 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -22,6 +22,8 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.VersionedPackage;
 import android.os.RemoteException;
 
 import java.util.List;
@@ -103,14 +105,18 @@
      * TODO: Specify the returns status codes.
      *
      * @param rollbackId ID of the rollback to commit
+     * @param causePackages package versions to record as the motivation for this
+     *                      rollback.
      * @param statusReceiver where to deliver the results
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public void commitRollback(int rollbackId, @NonNull IntentSender statusReceiver) {
+    public void commitRollback(int rollbackId, @NonNull List<VersionedPackage> causePackages,
+            @NonNull IntentSender statusReceiver) {
         try {
-            mBinder.commitRollback(rollbackId, mCallerPackageName, statusReceiver);
+            mBinder.commitRollback(rollbackId, new ParceledListSlice(causePackages),
+                    mCallerPackageName, statusReceiver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e59228a..14d7442 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -54,6 +54,7 @@
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -207,7 +208,8 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
-                rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
+                rollbacks.add(new RollbackInfo(data.rollbackId, data.packages,
+                            Collections.emptyList()));
             }
             return new ParceledListSlice<>(rollbacks);
         }
@@ -227,8 +229,8 @@
     }
 
     @Override
-    public void commitRollback(int rollbackId, String callerPackageName,
-            IntentSender statusReceiver) {
+    public void commitRollback(int rollbackId, ParceledListSlice causePackages,
+            String callerPackageName, IntentSender statusReceiver) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
                 "executeRollback");
@@ -238,7 +240,8 @@
         appOps.checkPackage(callingUid, callerPackageName);
 
         getHandler().post(() ->
-                commitRollbackInternal(rollbackId, callerPackageName, statusReceiver));
+                commitRollbackInternal(rollbackId, causePackages.getList(),
+                    callerPackageName, statusReceiver));
     }
 
     /**
@@ -246,7 +249,7 @@
      * The work is done on the current thread. This may be a long running
      * operation.
      */
-    private void commitRollbackInternal(int rollbackId,
+    private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
             String callerPackageName, IntentSender statusReceiver) {
         Log.i(TAG, "Initiating rollback");
 
@@ -350,8 +353,8 @@
                                 return;
                             }
 
-                            addRecentlyExecutedRollback(
-                                    new RollbackInfo(data.rollbackId, data.packages));
+                            addRecentlyExecutedRollback(new RollbackInfo(
+                                        data.rollbackId, data.packages, causePackages));
                             sendSuccess(statusReceiver);
 
                             Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index fcae618..2880103 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -29,6 +29,7 @@
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -85,8 +86,12 @@
         });
 
         // TODO(zezeozue): Log initiated metrics
+        // TODO: Pass the package as a cause package instead of using
+        // Collections.emptyList once the version of the failing package is
+        // easily available.
         mHandler.post(() ->
                 mRollbackManager.commitRollback(rollback.getRollbackId(),
+                    Collections.emptyList(),
                     rollbackReceiver.getIntentSender()));
         // Assume rollback executed successfully
         return true;
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 3b24b3e..98ebb09 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -116,7 +116,9 @@
                     int rollbackId = element.getInt("rollbackId");
                     List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
                             element.getJSONArray("packages"));
-                    RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
+                    List<VersionedPackage> causePackages = versionedPackagesFromJson(
+                            element.getJSONArray("causePackages"));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, packages, causePackages);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -187,6 +189,7 @@
                 JSONObject element = new JSONObject();
                 element.put("rollbackId", rollback.getRollbackId());
                 element.put("packages", toJson(rollback.getPackages()));
+                element.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
                 array.put(element);
             }
 
@@ -219,21 +222,49 @@
         }
     }
 
+    private JSONObject toJson(VersionedPackage pkg) throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("packageName", pkg.getPackageName());
+        json.put("longVersionCode", pkg.getLongVersionCode());
+        return json;
+    }
+
+    private VersionedPackage versionedPackageFromJson(JSONObject json) throws JSONException {
+        String packageName = json.getString("packageName");
+        long longVersionCode = json.getLong("longVersionCode");
+        return new VersionedPackage(packageName, longVersionCode);
+    }
+
     private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
         JSONObject json = new JSONObject();
-        json.put("packageName", info.getPackageName());
-        json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
-        json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
+        json.put("versionRolledBackFrom", toJson(info.getVersionRolledBackFrom()));
+        json.put("versionRolledBackTo", toJson(info.getVersionRolledBackTo()));
         return json;
     }
 
     private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
-        String packageName = json.getString("packageName");
-        long higherVersionCode = json.getLong("higherVersionCode");
-        long lowerVersionCode = json.getLong("lowerVersionCode");
-        return new PackageRollbackInfo(
-                new VersionedPackage(packageName, higherVersionCode),
-                new VersionedPackage(packageName, lowerVersionCode));
+        VersionedPackage versionRolledBackFrom = versionedPackageFromJson(
+                json.getJSONObject("versionRolledBackFrom"));
+        VersionedPackage versionRolledBackTo = versionedPackageFromJson(
+                json.getJSONObject("versionRolledBackTo"));
+        return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo);
+    }
+
+    private JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
+            throws JSONException {
+        JSONArray json = new JSONArray();
+        for (VersionedPackage pkg : packages) {
+            json.put(toJson(pkg));
+        }
+        return json;
+    }
+
+    private List<VersionedPackage> versionedPackagesFromJson(JSONArray json) throws JSONException {
+        List<VersionedPackage> packages = new ArrayList<>();
+        for (int i = 0; i < json.length(); ++i) {
+            packages.add(versionedPackageFromJson(json.getJSONObject(i)));
+        }
+        return packages;
     }
 
     private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0493ef8..e128a6c 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
@@ -40,6 +41,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -303,14 +305,15 @@
                     rm.getAvailableRollbacks(), TEST_APP_A);
 
             // Roll back the app.
-            RollbackTestUtils.rollback(rollback.getRollbackId());
+            VersionedPackage cause = new VersionedPackage(
+                    "com.android.tests.rollback.testapp.Foo", 42);
+            RollbackTestUtils.rollback(rollback.getRollbackId(), cause);
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // Verify the recent rollback has been recorded.
             rollback = getUniqueRollbackInfoForPackage(
                     rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
-            assertNotNull(rollback);
-            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -318,8 +321,7 @@
             // Verify the recent rollback is still recorded.
             rollback = getUniqueRollbackInfoForPackage(
                     rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
-            assertNotNull(rollback);
-            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -544,7 +546,7 @@
         try {
             // TODO: What if the implementation checks arguments for non-null
             // first? Then this test isn't valid.
-            rm.commitRollback(0, null);
+            rm.commitRollback(0, Collections.emptyList(), null);
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -690,11 +692,18 @@
     // Helper function to test the value of a RollbackInfo with single package
     private void assertRollbackInfoEquals(String packageName,
             long versionRolledBackFrom, long versionRolledBackTo,
-            RollbackInfo info) {
+            RollbackInfo info, VersionedPackage... causePackages) {
         assertNotNull(info);
         assertEquals(1, info.getPackages().size());
         assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
                 info.getPackages().get(0));
+        assertEquals(causePackages.length, info.getCausePackages().size());
+        for (int i = 0; i < causePackages.length; ++i) {
+            assertEquals(causePackages[i].getPackageName(),
+                    info.getCausePackages().get(i).getPackageName());
+            assertEquals(causePackages[i].getLongVersionCode(),
+                    info.getCausePackages().get(i).getLongVersionCode());
+        }
     }
 
     // Helper function to test that the given rollback info is a rollback for
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index 1478657..ad560f3 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,12 +21,14 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.content.rollback.RollbackManager;
 import android.support.test.InstrumentationRegistry;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
 
 /**
  * Utilities to facilitate testing rollbacks.
@@ -92,9 +94,11 @@
      * Commit the given rollback.
      * @throws AssertionError if the rollback fails.
      */
-    static void rollback(int rollbackId) throws InterruptedException {
+    static void rollback(int rollbackId, VersionedPackage... causePackages)
+            throws InterruptedException {
         RollbackManager rm = getRollbackManager();
-        rm.commitRollback(rollbackId, LocalIntentSender.getIntentSender());
+        rm.commitRollback(rollbackId, Arrays.asList(causePackages),
+                LocalIntentSender.getIntentSender());
         assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
     }