Delete runtime images when deleting artifacts and after dexopt.

The deletion is performed on `dexoptPackage` (`pm compile`),
`resetDexoptStatus` (`pm compile --reset`), and `deleteDexoptArtifacts`
(`pm delete-dexopt`).

Bug: 299442352
Test: atest ArtServiceTests
Test: adb shell pm compile -m verify -f com.android.settings
Test: adb shell pm compile --reset com.android.settings
Test: adb shell pm delete-dexopt com.android.settings
Change-Id: I165ef7c500d8dddbc1cf05c8d6b29400ff593d3a
Merged-In: I165ef7c500d8dddbc1cf05c8d6b29400ff593d3a
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index 4445ecd..4a1b75e 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -179,6 +179,16 @@
     }
 
     @NonNull
+    public static RuntimeArtifactsPath buildRuntimeArtifactsPath(
+            @NonNull String packageName, @NonNull String dexPath, @NonNull String isa) {
+        var runtimeArtifactsPath = new RuntimeArtifactsPath();
+        runtimeArtifactsPath.packageName = packageName;
+        runtimeArtifactsPath.dexPath = dexPath;
+        runtimeArtifactsPath.isa = isa;
+        return runtimeArtifactsPath;
+    }
+
+    @NonNull
     public static String toString(@NonNull PrimaryRefProfilePath profile) {
         return String.format("PrimaryRefProfilePath[packageName = %s, profileName = %s]",
                 profile.packageName, profile.profileName);
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 4a3c28f..1b6c98e 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -179,6 +179,8 @@
      * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the
      * ones for secondary dex files. This includes VDEX, ODEX, and ART files.
      *
+     * Also deletes runtime artifacts of the package, though they are not dexopt artifacts.
+     *
      * @throws IllegalArgumentException if the package is not found or the flags are illegal
      * @throws IllegalStateException if the operation encounters an error that should never happen
      *         (e.g., an internal logic error).
@@ -201,6 +203,9 @@
                 for (Abi abi : Utils.getAllAbis(pkgState)) {
                     freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
                             dexInfo.dexPath(), abi.isa(), isInDalvikCache));
+                    freedBytes += mInjector.getArtd().deleteRuntimeArtifacts(
+                            AidlUtils.buildRuntimeArtifactsPath(
+                                    packageName, dexInfo.dexPath(), abi.isa()));
                 }
             }
 
@@ -357,6 +362,10 @@
      * Dexopts a package. The time this operation takes ranges from a few milliseconds to several
      * minutes, depending on the params and the code size of the package.
      *
+     * When dexopt is successfully performed for a dex container file, this operation also deletes
+     * the corresponding runtime artifacts (the ART files in the package's data directory, which are
+     * generated by the runtime, not by dexopt).
+     *
      * When this operation ends (either completed or cancelled), callbacks added by {@link
      * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
      *
@@ -389,9 +398,9 @@
     /**
      * Resets the dexopt state of the package as if the package is newly installed.
      *
-     * More specifically, it clears reference profiles, current profiles, and any code compiled from
-     * those local profiles. If there is an external profile (e.g., a cloud profile), the code
-     * compiled from that profile will be kept.
+     * More specifically, it clears reference profiles, current profiles, any code compiled from
+     * those local profiles, and runtime artifacts. If there is an external profile (e.g., a cloud
+     * profile), the code compiled from that profile will be kept.
      *
      * For secondary dex files, it also clears all dexopt artifacts.
      *
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
index e822d31..dbfe43d 100644
--- a/libartservice/service/java/com/android/server/art/Dexopter.java
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -476,9 +476,25 @@
             dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
         }
 
-        return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
-                target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
-                dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+        ArtdDexoptResult result = mInjector.getArtd().dexopt(outputArtifacts,
+                target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+                target.compilerFilter(), profile, inputVdex, dmFile, priorityClass, dexoptOptions,
+                artdCancellationSignal);
+
+        // Delete the existing runtime images after the dexopt is performed, even if they are still
+        // usable (e.g., the compiler filter is "verify"). This is to make sure the dexopt puts the
+        // dex file into a certain dexopt state, to make it easier for debugging and testing. It's
+        // also an optimization to release disk space as soon as possible. However, not doing the
+        // deletion here does not affect correctness or waste disk space: if the existing runtime
+        // images are still usable, technically, they can still be used to improve runtime
+        // performance; if they are no longer usable, they will be deleted by the file GC during the
+        // daily background dexopt job anyway.
+        if (!result.cancelled) {
+            mInjector.getArtd().deleteRuntimeArtifacts(AidlUtils.buildRuntimeArtifactsPath(
+                    mPkgState.getPackageName(), target.dexInfo().dexPath(), target.isa()));
+        }
+
+        return result;
     }
 
     @Nullable
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 983bc9c..7e41f32 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -237,10 +237,15 @@
 
     @Test
     public void testdeleteDexoptArtifacts() throws Exception {
-        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+        final long DEXOPT_ARTIFACTS_FREED = 1l;
+        final long RUNTIME_ARTIFACTS_FREED = 100l;
+
+        when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
+        when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
 
         DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
-        assertThat(result.getFreedBytes()).isEqualTo(5);
+        assertThat(result.getFreedBytes())
+                .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
 
         verify(mArtd).deleteArtifacts(deepEq(
                 AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64", mIsInDalvikCache)));
@@ -253,12 +258,25 @@
         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
                 "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
 
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/base.apk", "arm64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(
+                AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "arm")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
+
         // Verify that there are no more calls than the ones above.
         verify(mArtd, times(5)).deleteArtifacts(any());
+        verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
     }
 
     @Test
     public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception {
+        final long DEXOPT_ARTIFACTS_FREED = 1l;
+        final long RUNTIME_ARTIFACTS_FREED = 100l;
+
         lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
         lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
         lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
@@ -266,10 +284,12 @@
         lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
         when(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64"));
 
-        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+        when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
+        when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
 
         DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
-        assertThat(result.getFreedBytes()).isEqualTo(5);
+        assertThat(result.getFreedBytes())
+                .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
 
         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
                 "/data/app/foo/base.apk", "x86_64", mIsInDalvikCache)));
@@ -279,12 +299,23 @@
                 "/data/app/foo/split_0.apk", "x86_64", mIsInDalvikCache)));
         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
                 "/data/app/foo/split_0.apk", "x86", mIsInDalvikCache)));
+
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/base.apk", "x86_64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(
+                AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "x86")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "x86_64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "x86")));
+
         // We assume that the ISA got from `DexUseManagerLocal` is already the translated one.
         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
                 "/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */)));
 
         // Verify that there are no more calls than the ones above.
         verify(mArtd, times(5)).deleteArtifacts(any());
+        verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -450,6 +481,15 @@
         verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
                 "/data/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
 
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/base.apk", "arm64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(
+                AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "arm")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64")));
+        verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
+
         verify(mArtd).deleteProfile(
                 deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
         verify(mArtd).deleteProfile(
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
index a9f0cd1..04f70dd 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -272,6 +272,13 @@
                         isNull() /* inputVdex */, isNull() /* dmFile */,
                         eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
 
+        // Only delete runtime artifacts for successful dexopt operations, namely the first one and
+        // the fourth one.
+        doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(
+                AidlUtils.buildRuntimeArtifactsPath(PKG_NAME, "/data/app/foo/base.apk", "arm64")));
+        doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(
+                AidlUtils.buildRuntimeArtifactsPath(PKG_NAME, "/data/app/foo/split_0.apk", "arm")));
+
         assertThat(mPrimaryDexopter.dexopt())
                 .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
                 .containsExactly(