Update the file GC to cleanup unused runtime images.

Bug: 299442352
Test: atest ArtServiceTests
Test: atest art_standalone_artd_tests
Test: adb shell pm art cleanup
Change-Id: I1e46a442a909d39f01cdf3d843839564115b2ef5
Merged-In: I1e46a442a909d39f01cdf3d843839564115b2ef5
diff --git a/artd/artd.cc b/artd/artd.cc
index 89156c2..09168a2 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -29,6 +29,7 @@
 #include <cstring>
 #include <filesystem>
 #include <functional>
+#include <iterator>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -1100,6 +1101,7 @@
 ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep,
                             const std::vector<ArtifactsPath>& in_artifactsToKeep,
                             const std::vector<VdexPath>& in_vdexFilesToKeep,
+                            const std::vector<RuntimeArtifactsPath>& in_runtimeArtifactsToKeep,
                             int64_t* _aidl_return) {
   std::unordered_set<std::string> files_to_keep;
   for (const ProfilePath& profile : in_profilesToKeep) {
@@ -1114,8 +1116,16 @@
   for (const VdexPath& vdex : in_vdexFilesToKeep) {
     files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex)));
   }
+  std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError());
+  std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError());
+  for (const RuntimeArtifactsPath& runtime_image_path : in_runtimeArtifactsToKeep) {
+    OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(runtime_image_path));
+    std::vector<std::string> files =
+        ListRuntimeArtifactsFiles(android_data, android_expand, runtime_image_path);
+    std::move(files.begin(), files.end(), std::inserter(files_to_keep, files_to_keep.end()));
+  }
   *_aidl_return = 0;
-  for (const std::string& file : OR_RETURN_NON_FATAL(ListManagedFiles())) {
+  for (const std::string& file : ListManagedFiles(android_data, android_expand)) {
     if (files_to_keep.find(file) == files_to_keep.end()) {
       LOG(INFO) << ART_FORMAT("Cleaning up obsolete file '{}'", file);
       *_aidl_return += GetSizeAndDeleteFile(file);
@@ -1158,8 +1168,10 @@
 ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtimeArtifactsPath,
                                            int64_t* _aidl_return) {
   OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(in_runtimeArtifactsPath));
+  std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError());
+  std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError());
   for (const std::string& file :
-       OR_RETURN_NON_FATAL(ListRuntimeArtifactsFiles(in_runtimeArtifactsPath))) {
+       ListRuntimeArtifactsFiles(android_data, android_expand, in_runtimeArtifactsPath)) {
     *_aidl_return += GetSizeAndDeleteFile(file);
   }
   return ScopedAStatus::ok();
diff --git a/artd/artd.h b/artd/artd.h
index c6bce72..8d9fa6f 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -162,6 +162,8 @@
       const std::vector<aidl::com::android::server::art::ProfilePath>& in_profilesToKeep,
       const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifactsToKeep,
       const std::vector<aidl::com::android::server::art::VdexPath>& in_vdexFilesToKeep,
+      const std::vector<aidl::com::android::server::art::RuntimeArtifactsPath>&
+          in_runtimeArtifactsToKeep,
       int64_t* _aidl_return) override;
 
   ndk::ScopedAStatus isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) override;
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 41c1755..a1f5d64 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -2016,6 +2016,13 @@
   CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex");
   CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.vdex");
   CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.art");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateGcKeptFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateGcKeptFile(android_data_ + "/user/1/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateGcKeptFile(android_expand_ +
+                   "/123456-7890/user/1/com.android.foo/cache/oat_primary/arm64/base.art");
+  CreateGcKeptFile(android_data_ +
+                   "/user/0/com.android.foo/cache/not_oat_dir/oat_primary/arm64/base.art");
 
   // Files to remove.
   CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof");
@@ -2050,6 +2057,12 @@
   CreateGcRemovedFile(android_data_ +
                       "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art.123456.tmp");
   CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.bar/aaa/oat/arm64/1.vdex");
+  CreateGcRemovedFile(android_data_ +
+                      "/user/0/com.android.different_package/cache/oat_primary/arm64/base.art");
+  CreateGcRemovedFile(android_data_ +
+                      "/user/0/com.android.foo/cache/oat_primary/arm64/different_dex.art");
+  CreateGcRemovedFile(android_data_ +
+                      "/user/0/com.android.foo/cache/oat_primary/different_isa/base.art");
 
   int64_t aidl_return;
   ASSERT_TRUE(
@@ -2081,6 +2094,10 @@
                       .isa = "arm64",
                       .isInDalvikCache = false}},
               },
+              {
+                  RuntimeArtifactsPath{
+                      .packageName = "com.android.foo", .isa = "arm64", .dexPath = "/a/b/base.apk"},
+              },
               &aidl_return)
           .isOk());
 
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index f7b2251..a296337 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -172,7 +172,8 @@
      */
     long cleanup(in List<com.android.server.art.ProfilePath> profilesToKeep,
             in List<com.android.server.art.ArtifactsPath> artifactsToKeep,
-            in List<com.android.server.art.VdexPath> vdexFilesToKeep);
+            in List<com.android.server.art.VdexPath> vdexFilesToKeep,
+            in List<com.android.server.art.RuntimeArtifactsPath> runtimeArtifactsToKeep);
 
     /**
      * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index 0a77d9d..0bf50c3 100644
--- a/artd/path_utils.cc
+++ b/artd/path_utils.cc
@@ -97,6 +97,8 @@
   return {};
 }
 
+}  // namespace
+
 Result<std::string> GetAndroidDataOrError() {
   std::string error_msg;
   std::string result = GetAndroidDataSafe(&error_msg);
@@ -124,12 +126,8 @@
   return result;
 }
 
-}  // namespace
-
-Result<std::vector<std::string>> ListManagedFiles() {
-  std::string android_data = OR_RETURN(GetAndroidDataOrError());
-  std::string android_expand = OR_RETURN(GetAndroidExpandOrError());
-
+std::vector<std::string> ListManagedFiles(const std::string& android_data,
+                                          const std::string& android_expand) {
   // See `art::tools::Glob` for the syntax.
   std::vector<std::string> patterns = {
       // Profiles for primary dex files.
@@ -141,27 +139,30 @@
   for (const std::string& data_root : {android_data, android_expand + "/*"}) {
     // Artifacts for primary dex files.
     patterns.push_back(data_root + "/app/*/*/oat/**");
-    // Profiles and artifacts for secondary dex files. Those files are in app data directories, so
-    // we use more granular patterns to avoid accidentally deleting apps' files.
+
     for (const char* user_dir : {"/user", "/user_de"}) {
-      std::string secondary_oat_dir = data_root + user_dir + "/*/*/**/oat";
+      std::string data_dir = data_root + user_dir + "/*/*";
+      // Profiles and artifacts for secondary dex files. Those files are in app data directories, so
+      // we use more granular patterns to avoid accidentally deleting apps' files.
+      std::string secondary_oat_dir = data_dir + "/**/oat";
       for (const char* maybe_tmp_suffix : {"", ".*.tmp"}) {
         patterns.push_back(secondary_oat_dir + "/*.prof" + maybe_tmp_suffix);
         patterns.push_back(secondary_oat_dir + "/*/*.odex" + maybe_tmp_suffix);
         patterns.push_back(secondary_oat_dir + "/*/*.vdex" + maybe_tmp_suffix);
         patterns.push_back(secondary_oat_dir + "/*/*.art" + maybe_tmp_suffix);
       }
+      // Runtime image files.
+      patterns.push_back(RuntimeImage::GetRuntimeImageDir(data_dir) + "**");
     }
   }
 
   return tools::Glob(patterns);
 }
 
-Result<std::vector<std::string>> ListRuntimeArtifactsFiles(
+std::vector<std::string> ListRuntimeArtifactsFiles(
+    const std::string& android_data,
+    const std::string& android_expand,
     const RuntimeArtifactsPath& runtime_artifacts_path) {
-  std::string android_data = OR_RETURN(GetAndroidDataOrError());
-  std::string android_expand = OR_RETURN(GetAndroidExpandOrError());
-
   // See `art::tools::Glob` for the syntax.
   std::vector<std::string> patterns;
 
diff --git a/artd/path_utils.h b/artd/path_utils.h
index 48640d0..bb1597d 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -28,10 +28,19 @@
 namespace art {
 namespace artd {
 
-// Returns all existing files that are managed by artd.
-android::base::Result<std::vector<std::string>> ListManagedFiles();
+android::base::Result<std::string> GetAndroidDataOrError();
 
-android::base::Result<std::vector<std::string>> ListRuntimeArtifactsFiles(
+android::base::Result<std::string> GetAndroidExpandOrError();
+
+android::base::Result<std::string> GetArtRootOrError();
+
+// Returns all existing files that are managed by artd.
+std::vector<std::string> ListManagedFiles(const std::string& android_data,
+                                          const std::string& android_expand);
+
+std::vector<std::string> ListRuntimeArtifactsFiles(
+    const std::string& android_data,
+    const std::string& android_expand,
     const aidl::com::android::server::art::RuntimeArtifactsPath& runtime_artifacts_path);
 
 android::base::Result<void> ValidateRuntimeArtifactsPath(
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 1b6c98e..898e660 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -920,9 +920,14 @@
             // - The dexopt artifacts, if they are up-to-date and the app is not hibernating.
             // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated
             //   but the VDEX part is still usable and the app is not hibernating.
+            // - The runtime artifacts, if dexopt artifacts are fully or partially usable and the
+            //   usable parts don't contain AOT-compiled code. (This logic must be aligned with the
+            //   one that determines when runtime images can be loaded in
+            //   `OatFileManager::OpenDexFilesFromOat` in `art/runtime/oat_file_manager.cc`.)
             List<ProfilePath> profilesToKeep = new ArrayList<>();
             List<ArtifactsPath> artifactsToKeep = new ArrayList<>();
             List<VdexPath> vdexFilesToKeep = new ArrayList<>();
+            List<RuntimeArtifactsPath> runtimeArtifactsToKeep = new ArrayList<>();
 
             for (PackageState pkgState : snapshot.getPackageStates().values()) {
                 if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) {
@@ -942,8 +947,9 @@
                             mInjector.getUserManager(), pkgState, dexInfo));
                     if (keepArtifacts) {
                         for (Abi abi : Utils.getAllAbis(pkgState)) {
-                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
-                                    abi, isInDalvikCache);
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep,
+                                    runtimeArtifactsToKeep, pkgState, dexInfo, abi,
+                                    isInDalvikCache);
                         }
                     }
                 }
@@ -956,13 +962,15 @@
                             AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
                     if (keepArtifacts) {
                         for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
-                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
-                                    abi, false /* isInDalvikCache */);
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep,
+                                    runtimeArtifactsToKeep, pkgState, dexInfo, abi,
+                                    false /* isInDalvikCache */);
                         }
                     }
                 }
             }
-            return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep);
+            return mInjector.getArtd().cleanup(
+                    profilesToKeep, artifactsToKeep, vdexFilesToKeep, runtimeArtifactsToKeep);
         } catch (RemoteException e) {
             Utils.logArtdException(e);
             return 0;
@@ -975,9 +983,10 @@
      */
     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep,
-            @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState,
-            @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache)
-            throws RemoteException {
+            @NonNull List<VdexPath> vdexFilesToKeep,
+            @NonNull List<RuntimeArtifactsPath> runtimeArtifactsToKeep,
+            @NonNull PackageState pkgState, @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi,
+            boolean isInDalvikCache) throws RemoteException {
         try {
             GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
                     dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
@@ -994,6 +1003,12 @@
                 } else {
                     artifactsToKeep.add(artifacts);
                 }
+                // Runtime images are only generated for primary dex files.
+                if (dexInfo instanceof DetailedPrimaryDexInfo
+                        && !DexFile.isOptimizedCompilerFilter(result.compilerFilter)) {
+                    runtimeArtifactsToKeep.add(AidlUtils.buildRuntimeArtifactsPath(
+                            pkgState.getPackageName(), dexInfo.dexPath(), abi.isa()));
+                }
             }
         } catch (ServiceSpecificException e) {
             // Don't add the artifacts to the lists. They should be cleaned up.
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 7e41f32..900196e 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -942,7 +942,7 @@
 
     @Test
     public void testCleanup() throws Exception {
-        // It should keep all artifacts.
+        // It should keep all artifacts, but not runtime images.
         doReturn(createGetDexoptStatusResult("speed-profile", "bg-dexopt", "location"))
                 .when(mArtd)
                 .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm64"), any());
@@ -950,15 +950,17 @@
                 .when(mArtd)
                 .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
 
-        // It should only keep VDEX files.
-        doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
+        // It should keep all artifacts and runtime images.
+        doReturn(createGetDexoptStatusResult("verify", "bg-dexopt", "location"))
                 .when(mArtd)
                 .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm64"), any());
+
+        // It should only keep VDEX files and runtime images.
         doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
                 .when(mArtd)
                 .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm"), any());
 
-        // It should not keep any artifacts.
+        // It should not keep any artifacts or runtime images.
         doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown"))
                 .when(mArtd)
                 .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm"), any());
@@ -982,12 +984,15 @@
                 inAnyOrderDeepEquals(AidlUtils.buildArtifactsPath(
                                              "/data/app/foo/base.apk", "arm64", mIsInDalvikCache),
                         AidlUtils.buildArtifactsPath(
-                                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)),
-                inAnyOrderDeepEquals(
-                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */),
+                        AidlUtils.buildArtifactsPath(
                                 "/data/app/foo/split_0.apk", "arm64", mIsInDalvikCache)),
-                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
-                                "/data/app/foo/split_0.apk", "arm", mIsInDalvikCache))));
+                inAnyOrderDeepEquals(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                        "/data/app/foo/split_0.apk", "arm", mIsInDalvikCache))),
+                inAnyOrderDeepEquals(AidlUtils.buildRuntimeArtifactsPath(
+                                             PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64"),
+                        AidlUtils.buildRuntimeArtifactsPath(
+                                PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
     }
 
     private AndroidPackage createPackage(boolean multiSplit) {
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index bd31651..5c97db2 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -289,6 +289,9 @@
           // is executable.
           image_space = oat_file_assistant->OpenImageSpace(oat_file.get());
         }
+        // Load the runtime image. This logic must be aligned with the one that determines when to
+        // keep runtime images in `ArtManagerLocal.cleanup` in
+        // `art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java`.
         if (kEnableRuntimeAppImage && image_space == nullptr && !compilation_enabled) {
           std::string art_file = RuntimeImage::GetRuntimeImagePath(dex_location);
           std::string error_msg;
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index 23b7354..5fb27a9 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -1812,7 +1812,7 @@
   friend class NativePointerVisitor;
 };
 
-static std::string GetRuntimeImageDir(const std::string& app_data_dir) {
+std::string RuntimeImage::GetRuntimeImageDir(const std::string& app_data_dir) {
   if (app_data_dir.empty()) {
     // The data directory is empty for tests.
     return "";
diff --git a/runtime/runtime_image.h b/runtime/runtime_image.h
index ed891b4..cea72a2 100644
--- a/runtime/runtime_image.h
+++ b/runtime/runtime_image.h
@@ -36,6 +36,13 @@
 
   // Same as above, but takes data dir and ISA from the runtime.
   static std::string GetRuntimeImagePath(const std::string& dex_location);
+
+  // Gets the directory that stores runtime-generated app images. Note that the return value
+  // contains a trailing '/'.
+  //
+  // If the argument is a valid glob (a pattern that contains '**' or those documented in glob(7)),
+  // returns a valid glob.
+  static std::string GetRuntimeImageDir(const std::string& app_data_dir);
 };
 
 }  // namespace art