Report bad external profile errors during compilation.

Bug: 278080573
Test: atest ArtServiceTests
Change-Id: I770ac90016d9ead4eb02e557b5aa9070bcd67a5c
Merged-In: I770ac90016d9ead4eb02e557b5aa9070bcd67a5c
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 2734c96..4a3c28f 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -23,6 +23,7 @@
 import static com.android.server.art.ReasonMapping.BatchDexoptReason;
 import static com.android.server.art.ReasonMapping.BootReason;
 import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.Utils.InitProfileResult;
 import static com.android.server.art.model.ArtFlags.GetStatusFlags;
 import static com.android.server.art.model.ArtFlags.ScheduleStatus;
 import static com.android.server.art.model.Config.Callback;
@@ -738,12 +739,18 @@
 
             List<ProfilePath> profiles = new ArrayList<>();
 
-            Pair<ProfilePath, Boolean> pair = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
+            InitProfileResult result = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
                     dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo),
                     PrimaryDexUtils.getExternalProfiles(dexInfo),
                     PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID,
                             Process.SYSTEM_UID, false /* isPublic */));
-            ProfilePath refProfile = pair != null ? pair.first : null;
+            if (!result.externalProfileErrors().isEmpty()) {
+                Log.e(TAG,
+                        "Error occurred when initializing from external profiles: "
+                                + result.externalProfileErrors());
+            }
+
+            ProfilePath refProfile = result.profile();
 
             if (refProfile != null) {
                 profiles.add(refProfile);
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
index 72fd22e..e822d31 100644
--- a/libartservice/service/java/com/android/server/art/Dexopter.java
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -20,6 +20,7 @@
 import static com.android.server.art.OutputArtifacts.PermissionSettings;
 import static com.android.server.art.ProfilePath.TmpProfilePath;
 import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.Utils.InitProfileResult;
 import static com.android.server.art.model.ArtFlags.DexoptFlags;
 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
 
@@ -104,6 +105,7 @@
         for (DexInfoType dexInfo : getDexInfoList()) {
             ProfilePath profile = null;
             boolean succeeded = true;
+            List<String> externalProfileErrors = List.of();
             try {
                 if (!isDexoptable(dexInfo)) {
                     continue;
@@ -120,13 +122,15 @@
                 boolean profileMerged = false;
                 if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
                     if (needsToBeShared) {
-                        profile = initReferenceProfile(dexInfo);
+                        InitProfileResult result = initReferenceProfile(dexInfo);
+                        profile = result.profile();
+                        isOtherReadable = result.isOtherReadable();
+                        externalProfileErrors = result.externalProfileErrors();
                     } else {
-                        Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo);
-                        if (pair != null) {
-                            profile = pair.first;
-                            isOtherReadable = pair.second;
-                        }
+                        InitProfileResult result = getOrInitReferenceProfile(dexInfo);
+                        profile = result.profile();
+                        isOtherReadable = result.isOtherReadable();
+                        externalProfileErrors = result.externalProfileErrors();
                         ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
                         if (mergedProfile != null) {
                             if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
@@ -241,9 +245,13 @@
                                 e);
                         status = DexoptResult.DEXOPT_FAILED;
                     } finally {
+                        if (!externalProfileErrors.isEmpty()) {
+                            extraStatus |= DexoptResult.EXTRA_BAD_EXTERNAL_PROFILE;
+                        }
                         var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(),
                                 abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
-                                cpuTimeMs, sizeBytes, sizeBeforeBytes, extraStatus);
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes, extraStatus,
+                                externalProfileErrors);
                         Log.i(TAG,
                                 String.format("Dexopt result: [packageName = %s] %s",
                                         mPkgState.getPackageName(), result));
@@ -344,7 +352,7 @@
 
     /** @see Utils#getOrInitReferenceProfile */
     @Nullable
-    private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+    private InitProfileResult getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
             throws RemoteException {
         return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
                 buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo),
@@ -352,7 +360,8 @@
     }
 
     @Nullable
-    private ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo) throws RemoteException {
+    private InitProfileResult initReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException {
         return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
                 getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */));
     }
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 273d8dd..252074a 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -57,6 +57,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -366,13 +367,9 @@
      * @param externalProfiles a list of external profiles to initialize the reference profile from,
      *         in the order of preference
      * @param initOutput the final location to initialize the reference profile to
-     *
-     * @return a pair where the first element is the found or initialized profile, and the second
-     *         element is true if the profile is readable by others. Returns null if there is no
-     *         reference profile or external profile to use
      */
-    @Nullable
-    public static Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull IArtd artd,
+    @NonNull
+    public static InitProfileResult getOrInitReferenceProfile(@NonNull IArtd artd,
             @NonNull String dexPath, @NonNull ProfilePath refProfile,
             @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)
             throws RemoteException {
@@ -380,7 +377,8 @@
             if (artd.isProfileUsable(refProfile, dexPath)) {
                 boolean isOtherReadable =
                         artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE;
-                return Pair.create(refProfile, isOtherReadable);
+                return InitProfileResult.create(
+                        refProfile, isOtherReadable, List.of() /* externalProfileErrors */);
             }
         } catch (ServiceSpecificException e) {
             Log.e(TAG,
@@ -389,22 +387,21 @@
                     e);
         }
 
-        ProfilePath initializedProfile =
-                initReferenceProfile(artd, dexPath, externalProfiles, initOutput);
-        return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+        return initReferenceProfile(artd, dexPath, externalProfiles, initOutput);
     }
 
     /**
      * Similar to above, but never uses an existing profile.
      *
-     * Unlike the one above, this method doesn't return a boolean flag to indicate if the profile is
-     * readable by others. The profile returned by this method is initialized form an external
-     * profile, meaning it has no user data, so it's always readable by others.
+     * The {@link InitProfileResult#isOtherReadable} field is always set to true. The profile
+     * returned by this method is initialized from an external profile, meaning it has no user data,
+     * so it's always readable by others.
      */
     @Nullable
-    public static ProfilePath initReferenceProfile(@NonNull IArtd artd, @NonNull String dexPath,
-            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output)
-            throws RemoteException {
+    public static InitProfileResult initReferenceProfile(@NonNull IArtd artd,
+            @NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles,
+            @NonNull OutputProfile output) throws RemoteException {
+        List<String> externalProfileErrors = new ArrayList<>();
         for (ProfilePath profile : externalProfiles) {
             try {
                 // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt
@@ -412,18 +409,22 @@
                 // build time and is correctly set in the profile header. However, the APK can also
                 // be an installed one, in which case partners may place a profile file next to the
                 // APK at install time. Rewriting the profile in the latter case is necessary.
-                // TODO(b/278080573): Make use of the detailed result.
                 CopyAndRewriteProfileResult result =
                         artd.copyAndRewriteProfile(profile, output, dexPath);
                 if (result.status == CopyAndRewriteProfileResult.Status.SUCCESS) {
-                    return ProfilePath.tmpProfilePath(output.profilePath);
+                    return InitProfileResult.create(ProfilePath.tmpProfilePath(output.profilePath),
+                            true /* isOtherReadable */, externalProfileErrors);
+                }
+                if (result.status == CopyAndRewriteProfileResult.Status.BAD_PROFILE) {
+                    externalProfileErrors.add(result.errorMsg);
                 }
             } catch (ServiceSpecificException e) {
                 Log.e(TAG, "Failed to initialize profile from " + AidlUtils.toString(profile), e);
             }
         }
 
-        return null;
+        return InitProfileResult.create(
+                null /* profile */, true /* isOtherReadable */, externalProfileErrors);
     }
 
     public static void logArtdException(@NonNull RemoteException e) {
@@ -490,4 +491,31 @@
             super.close();
         }
     }
+
+    /** The result of {@link #getOrInitReferenceProfile} and {@link #initReferenceProfile}. */
+    @AutoValue
+    @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava.
+    public abstract static class InitProfileResult {
+        static @NonNull InitProfileResult create(@Nullable ProfilePath profile,
+                boolean isOtherReadable, @NonNull List<String> externalProfileErrors) {
+            return new AutoValue_Utils_InitProfileResult(
+                    profile, isOtherReadable, Collections.unmodifiableList(externalProfileErrors));
+        }
+
+        /**
+         * The found or initialized profile, or null if there is no reference profile or external
+         * profile to use.
+         */
+        abstract @Nullable ProfilePath profile();
+
+        /**
+         * Whether the profile is readable by others.
+         *
+         * If {@link #profile} returns null, this field is always true.
+         */
+        abstract boolean isOtherReadable();
+
+        /** Errors encountered when initializing from external profiles. */
+        abstract @NonNull List<String> externalProfileErrors();
+    }
 }
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptResult.java b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
index b678a77..55298af 100644
--- a/libartservice/service/java/com/android/server/art/model/DexoptResult.java
+++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
@@ -23,12 +23,14 @@
 import android.annotation.SystemApi;
 
 import com.android.internal.annotations.Immutable;
+import com.android.internal.annotations.VisibleForTesting;
 
 import com.google.auto.value.AutoValue;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** @hide */
@@ -65,12 +67,15 @@
     public static final int EXTRA_SKIPPED_STORAGE_LOW = 1 << 0;
     /** @hide */
     public static final int EXTRA_SKIPPED_NO_DEX_CODE = 1 << 1;
+    /** @hide */
+    public static final int EXTRA_BAD_EXTERNAL_PROFILE = 1 << 2;
 
     /** @hide */
     // clang-format off
     @IntDef(flag = true, prefix = {"EXTRA_"}, value = {
         EXTRA_SKIPPED_STORAGE_LOW,
         EXTRA_SKIPPED_NO_DEX_CODE,
+        EXTRA_BAD_EXTERNAL_PROFILE,
     })
     // clang-format on
     @Retention(RetentionPolicy.SOURCE)
@@ -152,6 +157,9 @@
         if ((extraStatus & DexoptResult.EXTRA_SKIPPED_NO_DEX_CODE) != 0) {
             strs.add("EXTRA_SKIPPED_NO_DEX_CODE");
         }
+        if ((extraStatus & DexoptResult.EXTRA_BAD_EXTERNAL_PROFILE) != 0) {
+            strs.add("EXTRA_BAD_EXTERNAL_PROFILE");
+        }
         return String.join(", ", strs);
     }
 
@@ -213,6 +221,7 @@
     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
     @Immutable
     @AutoValue
+    @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava.
     public static abstract class DexContainerFileDexoptResult {
         /** @hide */
         protected DexContainerFileDexoptResult() {}
@@ -222,10 +231,23 @@
                 boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
                 @DexoptResultStatus int status, long dex2oatWallTimeMillis,
                 long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes,
-                @DexoptResultExtraStatus int extraStatus) {
+                @DexoptResultExtraStatus int extraStatus,
+                @NonNull List<String> externalProfileErrors) {
             return new AutoValue_DexoptResult_DexContainerFileDexoptResult(dexContainerFile,
                     isPrimaryAbi, abi, compilerFilter, status, dex2oatWallTimeMillis,
-                    dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, extraStatus);
+                    dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, extraStatus,
+                    Collections.unmodifiableList(externalProfileErrors));
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile,
+                boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
+                @DexoptResultStatus int status) {
+            return create(dexContainerFile, isPrimaryAbi, abi, compilerFilter, status,
+                    0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */,
+                    0 /* sizeBeforeBytes */, 0 /* extraStatus */,
+                    List.of() /* externalProfileErrors */);
         }
 
         /** The absolute path to the dex container file. */
@@ -282,6 +304,9 @@
         /** @hide */
         public abstract @DexoptResultExtraStatus int getExtraStatus();
 
+        /** @hide */
+        public abstract @NonNull List<String> getExternalProfileErrors();
+
         @Override
         @NonNull
         public String toString() {
diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
index 21d89e6..97bdb4f 100644
--- a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
@@ -819,14 +819,10 @@
 
     private List<DexContainerFileDexoptResult> createResults(
             String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2) {
-        return List.of(DexContainerFileDexoptResult.create(dexPath, true /* isPrimaryAbi */,
-                               "arm64-v8a", "verify", status1, 100 /* dex2oatWallTimeMillis */,
-                               400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */,
-                               0 /* sizeBeforeBytes */, 0 /* extraStatus */),
-                DexContainerFileDexoptResult.create(dexPath, false /* isPrimaryAbi */,
-                        "armeabi-v7a", "verify", status2, 100 /* dex2oatWallTimeMillis */,
-                        400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
-                        0 /* extraStatus */));
+        return List.of(DexContainerFileDexoptResult.create(
+                               dexPath, true /* isPrimaryAbi */, "arm64-v8a", "verify", status1),
+                DexContainerFileDexoptResult.create(
+                        dexPath, false /* isPrimaryAbi */, "armeabi-v7a", "verify", status2));
     }
 
     private void checkPackageResult(DexoptResult result, int index, String packageName,
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
index fe95847..a9f0cd1 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -280,23 +280,19 @@
                                 mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
                                 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
                                 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */,
-                                0 /* extraStatus */),
+                                0 /* extraStatus */, List.of() /* externalProfileErrors */),
                         DexContainerFileDexoptResult.create("/data/app/foo/base.apk",
                                 false /* isPrimaryAbi */, "armeabi-v7a",
-                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */),
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED),
                         DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
                                 true /* isPrimaryAbi */, "arm64-v8a",
-                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */),
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED),
                         DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
                                 false /* isPrimaryAbi */, "armeabi-v7a",
                                 mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
                                 200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
-                                10000 /* sizeBytes */, 0 /* sizeBeforeBytes */,
-                                0 /* extraStatus */));
+                                10000 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */,
+                                List.of() /* externalProfileErrors */));
 
         // Verify that there are no more calls than the ones above.
         verify(mArtd, times(3))
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
index 1525192..aa5203d 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -177,7 +177,8 @@
                 .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
                         anyInt(), any(), any());
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
     }
 
     @Test
@@ -186,7 +187,8 @@
                 .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
                 .thenReturn(FileVisibility.OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd, times(2))
                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
@@ -211,7 +213,8 @@
         makeProfileUsable(mPrebuiltProfile);
         makeProfileUsable(mDmProfile);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd).getDexoptNeeded(
                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
@@ -247,7 +250,8 @@
         makeProfileUsable(mPrebuiltProfile);
         makeProfileUsable(mDmProfile);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
                 true /* isOtherReadable */, true /* generateAppImage */);
@@ -264,7 +268,8 @@
         makeProfileUsable(mPrebuiltProfile);
         makeProfileUsable(mDmProfile);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         InOrder inOrder = inOrder(mArtd);
 
@@ -295,7 +300,8 @@
         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
                 .thenReturn(FileVisibility.OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         InOrder inOrder = inOrder(mArtd);
 
@@ -339,7 +345,8 @@
         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
                 .thenReturn(FileVisibility.OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         // It should still use "speed-profile", but with the existing reference profile only.
         verify(mArtd).getDexoptNeeded(
@@ -362,7 +369,8 @@
         makeProfileNotUsable(mPrebuiltProfile);
         makeProfileUsable(mDmProfile);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd).copyAndRewriteProfile(
                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
@@ -379,6 +387,30 @@
     }
 
     @Test
+    public void testDexoptExternalProfileErrors() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+
+        // Having no profile should not be reported.
+        makeProfileNotUsable(mPrebuiltProfile);
+
+        // Having a bad profile should be reported.
+        lenient()
+                .when(mArtd.copyAndRewriteProfile(deepEq(mDmProfile), any(), any()))
+                .thenReturn(TestingUtils.createCopyAndRewriteProfileBadProfile("error_msg"));
+
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+
+        assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(0).getExtraStatus() & DexoptResult.EXTRA_BAD_EXTERNAL_PROFILE)
+                .isNotEqualTo(0);
+        assertThat(results.get(0).getExternalProfileErrors()).containsExactly("error_msg");
+        assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(1).getExtraStatus() & DexoptResult.EXTRA_BAD_EXTERNAL_PROFILE)
+                .isNotEqualTo(0);
+        assertThat(results.get(1).getExternalProfileErrors()).containsExactly("error_msg");
+    }
+
+    @Test
     public void testDexoptDeletesProfileOnFailure() throws Exception {
         makeProfileNotUsable(mRefProfile);
         makeProfileNotUsable(mPrebuiltProfile);
@@ -413,7 +445,8 @@
                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd).copyAndRewriteProfile(
                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
@@ -453,7 +486,8 @@
                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
                 .thenReturn(FileVisibility.OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         // It should use the default dexopt trigger.
         verify(mArtd).getDexoptNeeded(
@@ -468,7 +502,8 @@
         when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
                 eq(mDefaultDexoptTrigger));
@@ -566,7 +601,8 @@
         mPrimaryDexopter =
                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd, times(2))
                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
@@ -587,7 +623,8 @@
         mPrimaryDexopter =
                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
 
-        mPrimaryDexopter.dexopt();
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        verifyStatusAllOk(results);
 
         verify(mArtd, never())
                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
@@ -703,4 +740,12 @@
                 .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
     }
+
+    private void verifyStatusAllOk(List<DexContainerFileDexoptResult> results) {
+        for (DexContainerFileDexoptResult result : results) {
+            assertThat(result.getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+            assertThat(result.getExtraStatus()).isEqualTo(0);
+            assertThat(result.getExternalProfileErrors()).isEmpty();
+        }
+    }
 }
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
index 35e256a..85d2983 100644
--- a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
@@ -165,21 +165,13 @@
                 .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
                 .containsExactly(
                         DexContainerFileDexoptResult.create(DEX_1, true /* isPrimaryAbi */,
-                                "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */),
+                                "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED),
                         DexContainerFileDexoptResult.create(DEX_2, true /* isPrimaryAbi */,
-                                "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */),
+                                "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED),
                         DexContainerFileDexoptResult.create(DEX_2, false /* isPrimaryAbi */,
-                                "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */),
+                                "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED),
                         DexContainerFileDexoptResult.create(DEX_3, true /* isPrimaryAbi */,
-                                "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
-                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */, 0 /* extraStatus */));
+                                "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED));
 
         // It should use profile for dex 1.