Change the OptimizeResult API to allow multiple packages.

The API has to handle the case where FLAG_SHOULD_INCLUDE_DEPENDENCIES is
set, in which case more than one package are optimized. Also, the class
can be reused as the result class for batch optimization (optimizing
multiple packages).

Bug: 245301593
Test: atest ArtServiceTests
Ignore-AOSP-First: ART Services.
Change-Id: I5c634e536c9dbdbaedf2f161cbde62467fb27e91
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index ad988c2..c9ce7cd 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -67,10 +67,8 @@
   }
 
   public class OptimizeResult {
-    ctor public OptimizeResult(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<com.android.server.art.model.OptimizeResult.DexFileOptimizeResult>);
-    method @NonNull public java.util.List<com.android.server.art.model.OptimizeResult.DexFileOptimizeResult> getDexFileOptimizeResults();
     method public int getFinalStatus();
-    method @NonNull public String getPackageName();
+    method @NonNull public java.util.List<com.android.server.art.model.OptimizeResult.PackageOptimizeResult> getPackageOptimizeResults();
     method @NonNull public String getReason();
     method @NonNull public String getRequestedCompilerFilter();
     field public static final int OPTIMIZE_CANCELLED = 40; // 0x28
@@ -86,5 +84,11 @@
     method public int getStatus();
   }
 
+  public static class OptimizeResult.PackageOptimizeResult {
+    method @NonNull public java.util.List<com.android.server.art.model.OptimizeResult.DexFileOptimizeResult> getDexFileOptimizeResults();
+    method @NonNull public String getPackageName();
+    method public int getStatus();
+  }
+
 }
 
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index c207fc4..b797d8b 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -18,7 +18,11 @@
 
 import static com.android.server.art.model.ArtFlags.OptimizeFlags;
 import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
+import static com.android.server.art.model.OptimizeResult.DexFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.OptimizeStatus;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
 
+import android.annotation.NonNull;
 import android.os.Binder;
 import android.os.Process;
 
@@ -93,19 +97,17 @@
                 }
                 OptimizeResult result = mArtManagerLocal.optimizePackage(
                         snapshot, getNextArgRequired(), paramsBuilder.build());
-                switch (result.getFinalStatus()) {
-                    case OptimizeResult.OPTIMIZE_SKIPPED:
-                        pw.println("SKIPPED");
-                        break;
-                    case OptimizeResult.OPTIMIZE_PERFORMED:
-                        pw.println("PERFORMED");
-                        break;
-                    case OptimizeResult.OPTIMIZE_FAILED:
-                        pw.println("FAILED");
-                        break;
-                    case OptimizeResult.OPTIMIZE_CANCELLED:
-                        pw.println("CANCELLED");
-                        break;
+                pw.println(optimizeStatusToString(result.getFinalStatus()));
+                for (PackageOptimizeResult packageResult : result.getPackageOptimizeResults()) {
+                    pw.printf("[%s]\n", packageResult.getPackageName());
+                    for (DexFileOptimizeResult dexFileResult :
+                            packageResult.getDexFileOptimizeResults()) {
+                        pw.printf("dexFile = %s, instructionSet = %s, compilerFilter = %s, "
+                                        + "status = %s\n",
+                                dexFileResult.getDexFile(), dexFileResult.getInstructionSet(),
+                                dexFileResult.getActualCompilerFilter(),
+                                optimizeStatusToString(dexFileResult.getStatus()));
+                    }
                 }
                 return 0;
             }
@@ -150,4 +152,19 @@
             throw new SecurityException("ART service shell commands need root access");
         }
     }
+
+    @NonNull
+    private String optimizeStatusToString(@OptimizeStatus int status) {
+        switch (status) {
+            case OptimizeResult.OPTIMIZE_SKIPPED:
+                return "SKIPPED";
+            case OptimizeResult.OPTIMIZE_PERFORMED:
+                return "PERFORMED";
+            case OptimizeResult.OPTIMIZE_FAILED:
+                return "FAILED";
+            case OptimizeResult.OPTIMIZE_CANCELLED:
+                return "CANCELLED";
+        }
+        throw new IllegalArgumentException("Unknown optimize status " + status);
+    }
 }
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
index 6d9c85a..89bd8f4 100644
--- a/libartservice/service/java/com/android/server/art/DexOptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.art;
 
 import static com.android.server.art.model.OptimizeResult.DexFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -76,8 +77,8 @@
             @NonNull OptimizeParams params) throws RemoteException {
         List<DexFileOptimizeResult> results = new ArrayList<>();
         Supplier<OptimizeResult> createResult = ()
-                -> new OptimizeResult(pkgState.getPackageName(), params.getCompilerFilter(),
-                        params.getReason(), results);
+                -> new OptimizeResult(params.getCompilerFilter(), params.getReason(),
+                        List.of(new PackageOptimizeResult(pkgState.getPackageName(), results)));
 
         if (!canOptimizePackage(pkgState, pkg)) {
             return createResult.get();
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
index d6fdf28..f1c83d4 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
@@ -50,22 +50,16 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface OptimizeStatus {}
 
-    private final @NonNull String mPackageName;
     private final @NonNull String mRequestedCompilerFilter;
     private final @NonNull String mReason;
-    private final @NonNull List<DexFileOptimizeResult> mDexFileOptimizeResults;
+    private final @NonNull List<PackageOptimizeResult> mPackageOptimizeResult;
 
-    public OptimizeResult(@NonNull String packageName, @NonNull String requestedCompilerFilter,
-            @NonNull String reason, @NonNull List<DexFileOptimizeResult> dexFileOptimizeResults) {
-        mPackageName = packageName;
+    /** @hide */
+    public OptimizeResult(@NonNull String requestedCompilerFilter, @NonNull String reason,
+            @NonNull List<PackageOptimizeResult> packageOptimizeResult) {
         mRequestedCompilerFilter = requestedCompilerFilter;
         mReason = reason;
-        mDexFileOptimizeResults = dexFileOptimizeResults;
-    }
-
-    /** The package name. */
-    public @NonNull String getPackageName() {
-        return mPackageName;
+        mPackageOptimizeResult = packageOptimizeResult;
     }
 
     /**
@@ -84,18 +78,63 @@
         return mReason;
     }
 
+    /**
+     * The result of each individual package.
+     *
+     * If the request is to optimize a single package without optimizing dependencies, the only
+     * element is the result of the requested package.
+     *
+     * If the request is to optimize a single package with {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} set, the first element is the result of the
+     * requested package, and the rest are the results of the dependency packages.
+     *
+     * If the request is to optimize multiple packages, the list contains the results of all the
+     * requested packages. The results of their dependency packages are also included if {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} is set.
+     */
+    public @NonNull List<PackageOptimizeResult> getPackageOptimizeResults() {
+        return mPackageOptimizeResult;
+    }
+
     /** The final status. */
     public @OptimizeStatus int getFinalStatus() {
-        return mDexFileOptimizeResults.stream()
+        return mPackageOptimizeResult.stream()
                 .mapToInt(result -> result.getStatus())
                 .max()
                 .orElse(OPTIMIZE_SKIPPED);
     }
 
-    /** The result of each individual dex file. */
-    @NonNull
-    public List<DexFileOptimizeResult> getDexFileOptimizeResults() {
-        return mDexFileOptimizeResults;
+    /** Describes the result of a package. */
+    @Immutable
+    public static class PackageOptimizeResult {
+        private final @NonNull String mPackageName;
+        private final @NonNull List<DexFileOptimizeResult> mDexFileOptimizeResults;
+
+        /** @hide */
+        public PackageOptimizeResult(@NonNull String packageName,
+                @NonNull List<DexFileOptimizeResult> dexFileOptimizeResults) {
+            mPackageName = packageName;
+            mDexFileOptimizeResults = dexFileOptimizeResults;
+        }
+
+        /** The package name. */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        /** The result of each individual dex file. */
+        @NonNull
+        public List<DexFileOptimizeResult> getDexFileOptimizeResults() {
+            return mDexFileOptimizeResults;
+        }
+
+        /** The overall status of the package. */
+        public @OptimizeStatus int getStatus() {
+            return mDexFileOptimizeResults.stream()
+                    .mapToInt(result -> result.getStatus())
+                    .max()
+                    .orElse(OPTIMIZE_SKIPPED);
+        }
     }
 
     /** Describes the result of a dex file. */
diff --git a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
index 518b972..3b4e045 100644
--- a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -17,6 +17,7 @@
 package com.android.server.art;
 
 import static com.android.server.art.model.OptimizeResult.DexFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -109,11 +110,15 @@
         OptimizeResult result =
                 mDexOptHelper.dexopt(mock(PackageDataSnapshot.class), mPkgState, mPkg, mParams);
 
-        assertThat(result.getPackageName()).isEqualTo(PKG_NAME);
         assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
         assertThat(result.getReason()).isEqualTo("install");
         assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_FAILED);
-        assertThat(result.getDexFileOptimizeResults()).containsExactlyElementsIn(mPrimaryResults);
+        assertThat(result.getPackageOptimizeResults()).hasSize(1);
+
+        PackageOptimizeResult packageResult = result.getPackageOptimizeResults().get(0);
+        assertThat(packageResult.getPackageName()).isEqualTo(PKG_NAME);
+        assertThat(packageResult.getDexFileOptimizeResults())
+                .containsExactlyElementsIn(mPrimaryResults);
 
         InOrder inOrder = inOrder(mPrimaryDexOptimizer, mWakeLock);
         inOrder.verify(mWakeLock).acquire(anyLong());
@@ -129,7 +134,7 @@
                 mDexOptHelper.dexopt(mock(PackageDataSnapshot.class), mPkgState, mPkg, mParams);
 
         assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
-        assertThat(result.getDexFileOptimizeResults()).isEmpty();
+        assertThat(result.getPackageOptimizeResults().get(0).getDexFileOptimizeResults()).isEmpty();
     }
 
     @Test
@@ -140,7 +145,7 @@
                 mDexOptHelper.dexopt(mock(PackageDataSnapshot.class), mPkgState, mPkg, mParams);
 
         assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
-        assertThat(result.getDexFileOptimizeResults()).isEmpty();
+        assertThat(result.getPackageOptimizeResults().get(0).getDexFileOptimizeResults()).isEmpty();
     }
 
     @Test
@@ -154,7 +159,8 @@
         OptimizeResult result =
                 mDexOptHelper.dexopt(mock(PackageDataSnapshot.class), mPkgState, mPkg, mParams);
 
-        assertThat(result.getDexFileOptimizeResults()).containsExactlyElementsIn(mPrimaryResults);
+        assertThat(result.getPackageOptimizeResults().get(0).getDexFileOptimizeResults())
+                .containsExactlyElementsIn(mPrimaryResults);
     }
 
     @Test