Check whether the APK has code before recording JIT profile information.

In the past, Package Manager created empty profile files for APKs that
have code, and the runtime did a file existence check (removed by
aosp/2407152). With ART Service, no empty profile files are created, so
we have no clue whether the APK has code or not.

After this change, the runtime opens the APK to check whether the APK
has code or not.

This change prevents the runtime from writing bootclasspath profiling
info to profile files for APKs that have no code. Such files are not
suitable for bootclasspath profiling because ART Service has a file GC
that cleans them up during daily bg dexopt.

This change admittedly makes `Runtime::RegisterAppInfo` slower because
it takes some time to open the APK, but in practice, it's okay because
Framework creates a classloader for the APK right after calling
`Runtime::RegisterAppInfo`, which also involves opening the APK, and
that open will be faster after this change.

Bug: 282191456
Test: art/test.py -b --host -r -t 595-profile-saving
Change-Id: If6ed139dcb7993402ded60a3bbd8fc0530a9c20e
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 69f0fbb..819a982 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2763,6 +2763,31 @@
     return;
   }
 
+  // Framework calls this method for all split APKs. Ignore the calls for the ones with no dex code
+  // so that we don't unnecessarily create profiles for them or write bootclasspath profiling info
+  // to those profiles.
+  bool has_code = false;
+  for (const std::string& path : code_paths) {
+    std::string error_msg;
+    std::vector<uint32_t> checksums;
+    std::vector<std::string> dex_locations;
+    if (!ArtDexFileLoader::GetMultiDexChecksums(
+            path.c_str(), &checksums, &dex_locations, &error_msg)) {
+      LOG(WARNING) << error_msg;
+      continue;
+    }
+    if (dex_locations.size() > 0) {
+      has_code = true;
+      break;
+    }
+  }
+  if (!has_code) {
+    VLOG(profiler) << ART_FORMAT(
+        "JIT profile information will not be recorded: no dex code in '{}'.",
+        android::base::Join(code_paths, ','));
+    return;
+  }
+
   jit_->StartProfileSaver(profile_output_filename, code_paths, ref_profile_filename);
 }
 
diff --git a/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar b/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar
new file mode 100644
index 0000000..252879f
--- /dev/null
+++ b/test/595-profile-saving/res/art-gtest-jars-MainEmptyUncompressed.jar
Binary files differ
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 37a8e6c..31dc4d2 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -24,9 +24,21 @@
     System.loadLibrary(args[0]);
 
     File file = null;
+    File file2 = null;
     try {
+      // Register `file2` with an empty jar. Even though `file2` is registered before `file`, the
+      // runtime should not write bootclasspath methods to `file2`, and it should not even create
+      // `file2`.
+      file2 = createTempFile();
+      String emptyJarPath =
+          System.getenv("DEX_LOCATION") + "/res/art-gtest-jars-MainEmptyUncompressed.jar";
+      VMRuntime.registerAppInfo("test.app",
+                                file2.getPath(),
+                                file2.getPath(),
+                                new String[] {emptyJarPath},
+                                VMRuntime.CODE_PATH_TYPE_SPLIT_APK);
+
       file = createTempFile();
-      // String codePath = getDexBaseLocation();
       String codePath = System.getenv("DEX_LOCATION") + "/595-profile-saving.jar";
       VMRuntime.registerAppInfo("test.app",
                                 file.getPath(),
@@ -39,9 +51,10 @@
           File.class, Method.class);
       testAddMethodToProfile(file, appMethod);
 
-      // Delete the file to check that the runtime can save the profile even if the file doesn't
-      // exist.
+      // Delete the files so that we can check if the runtime creates them. The runtime should
+      // create `file` but not `file2`.
       file.delete();
+      file2.delete();
 
       // Test that the profile saves a boot class path method with a profiling info.
       Method bootMethod = File.class.getDeclaredMethod("exists");
@@ -55,11 +68,16 @@
       Method bootNotInProfileMethod = System.class.getDeclaredMethod("console");
       testMethodNotInProfile(file, bootNotInProfileMethod);
 
+      testProfileNotExist(file2);
+
       System.out.println("IsForBootImage: " + isForBootImage(file.getPath()));
     } finally {
       if (file != null) {
         file.delete();
       }
+      if (file2 != null) {
+        file2.delete();
+      }
     }
   }
 
@@ -83,6 +101,15 @@
     }
   }
 
+  static void testProfileNotExist(File file) {
+    // Make sure the profile saving has been attempted.
+    ensureProfileProcessing();
+    // Verify that the profile does not exist.
+    if (file.exists()) {
+      throw new RuntimeException("Did not expect " + file + " to exist");
+    }
+  }
+
   // Ensure a method has a profiling info.
   public static native void ensureProfilingInfo(Method method);
   // Ensures the profile saver does its usual processing.
@@ -113,7 +140,8 @@
   }
 
   private static class VMRuntime {
-    public static final int CODE_PATH_TYPE_PRIMARY_APK = 1;
+    public static final int CODE_PATH_TYPE_PRIMARY_APK = 1 << 0;
+    public static final int CODE_PATH_TYPE_SPLIT_APK = 1 << 1;
     private static final Method registerAppInfoMethod;
 
     static {