Compile standalone system server jars.

This CL updates odrefresh to take an additional environment variable
`STANDALONE_SYSTEMSERVER_JARS` and compile standalone system server jars
accordingly.

Bug: 203198541
Test: atest odsign_e2e_tests
Test: atest art_standalone_odrefresh_tests
Test: atest art_standalone_artd_tests
Test: manual -
  1. Patch aosp/1874113, aosp/1876173, aosp/1875774 and aosp/1875775.
  2. Build a new system image and flash it to a device.
  3. adb logcat odrefresh:D odsign:D "*:S"
  4. See standalone system server jars being compiled and signed.

Change-Id: Ic824c7380cf9530437bc2a95b9ff816a63b22013
diff --git a/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
index b351c67..bfb94e1 100644
--- a/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
+++ b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
@@ -61,6 +61,7 @@
     String oatLocation;
     String[] classloaderContext;
     boolean isBootImageOnSystem;
+    boolean classloaderContextAsParent;
 
     // SECURITY: The server may accept the request to produce code for the specified architecture,
     // if supported.
diff --git a/artd/libdexopt.cc b/artd/libdexopt.cc
index b9d4f4e..853f950 100644
--- a/artd/libdexopt.cc
+++ b/artd/libdexopt.cc
@@ -312,7 +312,11 @@
     cmdline.emplace_back("--class-loader-context=PCL[]");
   } else {
     const std::string context_path = android::base::Join(args.classloaderContext, ':');
-    cmdline.emplace_back("--class-loader-context=PCL[" + context_path + "]");
+    if (args.classloaderContextAsParent) {
+      cmdline.emplace_back("--class-loader-context=PCL[];PCL[" + context_path + "]");
+    } else {
+      cmdline.emplace_back("--class-loader-context=PCL[" + context_path + "]");
+    }
     cmdline.emplace_back("--class-loader-context-fds=" +
                          android::base::Join(args.classloaderFds, ':'));
   }
diff --git a/artd/libdexopt_test.cc b/artd/libdexopt_test.cc
index 7536dcd..e73c5d6 100644
--- a/artd/libdexopt_test.cc
+++ b/artd/libdexopt_test.cc
@@ -270,7 +270,7 @@
         Contains("--compiler-filter=verify")));
   }
 
-  // Test classloader context
+  // Test empty classloader context
   {
     auto args = default_system_server_args_;
     args.classloaderFds = {};
@@ -281,6 +281,15 @@
         Not(Contains(HasSubstr("--class-loader-context-fds"))),
         Contains("--class-loader-context=PCL[]")));
   }
+
+  // Test classloader context as parent
+  {
+    auto args = default_system_server_args_;
+    args.classloaderContextAsParent = true;
+    std::vector<std::string> cmdline = Dex2oatArgsFromSystemServerArgs(args);
+
+    EXPECT_THAT(cmdline, Contains("--class-loader-context=PCL[];PCL[/cl/abc.jar:/cl/def.jar]"));
+  }
 }
 
 TEST_F(LibDexoptTest, AddDex2oatArgsFromSystemServerArgs_InvalidArguments) {
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index b527bbf..b4d0f49 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -52,6 +52,7 @@
         "libtinyxml2",
     ],
     tidy: true,
+    tidy_disabled_srcs: [":art-apex-cache-info"],
     tidy_flags: [
         "-format-style=file",
         "-header-filter=(art/odrefresh/|system/apex/)",
diff --git a/odrefresh/CacheInfo.xsd b/odrefresh/CacheInfo.xsd
index 44f733f..3aa94a5 100644
--- a/odrefresh/CacheInfo.xsd
+++ b/odrefresh/CacheInfo.xsd
@@ -29,7 +29,7 @@
       <xs:element name="moduleInfoList" minOccurs="1" maxOccurs="1" type="t:moduleInfoList" />
       <xs:element name="bootClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
       <xs:element name="dex2oatBootClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
-      <xs:element name="systemServerClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
+      <xs:element name="systemServerComponents" minOccurs="1" maxOccurs="1" type="t:systemServerComponents" />
     </xs:sequence>
     </xs:complexType>
   </xs:element>
@@ -69,4 +69,20 @@
     <xs:attribute name="checksums" type="xs:string" use="required" />
   </xs:complexType>
 
+  <xs:complexType name="systemServerComponents">
+    <xs:sequence>
+      <xs:element name="component" type="t:systemServerComponent" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="systemServerComponent">
+    <xs:complexContent>
+      <xs:extension base="component">
+        <!-- True if the component is in SYSTEMSERVERCLASSPATH. Otherwise, the component is a
+          standalone one. -->
+        <xs:attribute name="isInClasspath" type="xs:boolean" use="required" />
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
 </xs:schema>
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 1798095..495ccf1 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -68,6 +68,7 @@
   std::string artifact_dir_;
   time_t max_execution_seconds_ = kMaximumExecutionSeconds;
   time_t max_child_process_seconds_ = kMaxChildProcessSeconds;
+  std::string standalone_system_server_jars_;
 
   // Staging directory for artifacts. The directory must exist and will be automatically removed
   // after compilation. If empty, use the default directory.
@@ -185,6 +186,14 @@
     staging_dir_ = staging_dir;
   }
 
+  const std::string& GetStandaloneSystemServerJars() const {
+    return standalone_system_server_jars_;
+  }
+
+  void SetStandaloneSystemServerJars(const std::string& jars) {
+    standalone_system_server_jars_ = jars;
+  }
+
  private:
   // Returns a pair for the possible instruction sets for the configured instruction set
   // architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 2bfc49a..72e0793 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -205,52 +205,77 @@
   return module_info_list;
 }
 
-bool CheckComponents(const std::vector<art_apex::Component>& expected_components,
-                     const std::vector<art_apex::Component>& actual_components,
-                     std::string* error_msg) {
+template <typename T>
+Result<void> CheckComponents(
+    const std::vector<T>& expected_components,
+    const std::vector<T>& actual_components,
+    const std::function<Result<void>(const T& expected, const T& actual)>& custom_checker =
+        [](const T&, const T&) -> Result<void> { return {}; }) {
   if (expected_components.size() != actual_components.size()) {
-    return false;
+    return Errorf(
+        "Component count differs ({} != {})", expected_components.size(), actual_components.size());
   }
 
   for (size_t i = 0; i < expected_components.size(); ++i) {
-    const art_apex::Component& expected = expected_components[i];
-    const art_apex::Component& actual = actual_components[i];
+    const T& expected = expected_components[i];
+    const T& actual = actual_components[i];
 
     if (expected.getFile() != actual.getFile()) {
-      *error_msg = android::base::StringPrintf("Component %zu file differs ('%s' != '%s')",
-                                               i,
-                                               expected.getFile().c_str(),
-                                               actual.getFile().c_str());
-      return false;
+      return Errorf(
+          "Component {} file differs ('{}' != '{}')", i, expected.getFile(), actual.getFile());
     }
+
     if (expected.getSize() != actual.getSize()) {
-      *error_msg =
-          android::base::StringPrintf("Component %zu size differs (%" PRIu64 " != %" PRIu64 ")",
-                                      i,
-                                      expected.getSize(),
-                                      actual.getSize());
-      return false;
+      return Errorf(
+          "Component {} size differs ({} != {})", i, expected.getSize(), actual.getSize());
     }
+
     if (expected.getChecksums() != actual.getChecksums()) {
-      *error_msg = android::base::StringPrintf("Component %zu checksums differ ('%s' != '%s')",
-                                               i,
-                                               expected.getChecksums().c_str(),
-                                               actual.getChecksums().c_str());
-      return false;
+      return Errorf("Component {} checksums differ ('{}' != '{}')",
+                    i,
+                    expected.getChecksums(),
+                    actual.getChecksums());
+    }
+
+    Result<void> result = custom_checker(expected, actual);
+    if (!result.ok()) {
+      return Errorf("Component {} {}", i, result.error().message());
     }
   }
 
-  return true;
+  return {};
 }
 
-std::vector<art_apex::Component> GenerateComponents(const std::vector<std::string>& jars) {
-  std::vector<art_apex::Component> components;
+Result<void> CheckSystemServerComponents(
+    const std::vector<art_apex::SystemServerComponent>& expected_components,
+    const std::vector<art_apex::SystemServerComponent>& actual_components) {
+  return CheckComponents<art_apex::SystemServerComponent>(
+      expected_components,
+      actual_components,
+      [](const art_apex::SystemServerComponent& expected,
+         const art_apex::SystemServerComponent& actual) -> Result<void> {
+        if (expected.getIsInClasspath() != actual.getIsInClasspath()) {
+          return Errorf("isInClasspath differs ({} != {})",
+                        expected.getIsInClasspath(),
+                        actual.getIsInClasspath());
+        }
+
+        return {};
+      });
+}
+
+template <typename T>
+std::vector<T> GenerateComponents(
+    const std::vector<std::string>& jars,
+    const std::function<T(const std::string& path, uint64_t size, const std::string& checksum)>&
+        custom_generator) {
+  std::vector<T> components;
 
   ArtDexFileLoader loader;
   for (const std::string& path : jars) {
     struct stat sb;
     if (stat(path.c_str(), &sb) == -1) {
-      PLOG(ERROR) << "Failed to get component: " << QuotePath(path);
+      PLOG(ERROR) << "Failed to stat component: " << QuotePath(path);
       return {};
     }
 
@@ -258,7 +283,7 @@
     std::vector<std::string> dex_locations;
     std::string error_msg;
     if (!loader.GetMultiDexChecksums(path.c_str(), &checksums, &dex_locations, &error_msg)) {
-      LOG(ERROR) << "Failed to get components: " << error_msg;
+      LOG(ERROR) << "Failed to get multi-dex checksums: " << error_msg;
       return {};
     }
 
@@ -271,12 +296,25 @@
     }
     const std::string checksum = oss.str();
 
-    components.emplace_back(art_apex::Component{path, static_cast<uint64_t>(sb.st_size), checksum});
+    Result<T> component = custom_generator(path, static_cast<uint64_t>(sb.st_size), checksum);
+    if (!component.ok()) {
+      LOG(ERROR) << "Failed to generate component: " << component.error();
+      return {};
+    }
+
+    components.push_back(*std::move(component));
   }
 
   return components;
 }
 
+std::vector<art_apex::Component> GenerateComponents(const std::vector<std::string>& jars) {
+  return GenerateComponents<art_apex::Component>(
+      jars, [](const std::string& path, uint64_t size, const std::string& checksum) {
+        return art_apex::Component{path, size, checksum};
+      });
+}
+
 // Checks whether a group of artifacts exists. Returns true if all are present, false otherwise.
 // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
 bool ArtifactsExist(const OdrArtifacts& artifacts,
@@ -501,8 +539,17 @@
     }
   }
 
-  systemserver_compilable_jars_ = android::base::Split(config_.GetSystemServerClasspath(), ":");
+  all_systemserver_jars_ = android::base::Split(config_.GetSystemServerClasspath(), ":");
+  systemserver_classpath_jars_ = {all_systemserver_jars_.begin(), all_systemserver_jars_.end()};
   boot_classpath_jars_ = android::base::Split(config_.GetBootClasspath(), ":");
+  std::string standalone_system_server_jars_str = config_.GetStandaloneSystemServerJars();
+  if (!standalone_system_server_jars_str.empty()) {
+    std::vector<std::string> standalone_systemserver_jars =
+        android::base::Split(standalone_system_server_jars_str, ":");
+    std::move(standalone_systemserver_jars.begin(),
+              standalone_systemserver_jars.end(),
+              std::back_inserter(all_systemserver_jars_));
+  }
 }
 
 time_t OnDeviceRefresh::GetExecutionTimeUsed() const {
@@ -580,7 +627,7 @@
     return;
   }
 
-  std::optional<std::vector<art_apex::Component>> system_server_components =
+  std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components =
       GenerateSystemServerComponents();
   if (!system_server_components.has_value()) {
     LOG(ERROR) << "No system_server extension components.";
@@ -592,7 +639,7 @@
                            {art_apex::ModuleInfoList(module_info_list)},
                            {art_apex::Classpath(bcp_components.value())},
                            {art_apex::Classpath(bcp_compilable_components.value())},
-                           {art_apex::Classpath(system_server_components.value())});
+                           {art_apex::SystemServerComponents(system_server_components.value())});
 
   art_apex::write(out, info);
 }
@@ -614,8 +661,14 @@
   return GenerateComponents(boot_extension_compilable_jars_);
 }
 
-std::vector<art_apex::Component> OnDeviceRefresh::GenerateSystemServerComponents() const {
-  return GenerateComponents(systemserver_compilable_jars_);
+std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServerComponents()
+    const {
+  return GenerateComponents<art_apex::SystemServerComponent>(
+      all_systemserver_jars_,
+      [&](const std::string& path, uint64_t size, const std::string& checksum) {
+        bool isInClasspath = ContainsElement(systemserver_classpath_jars_, path);
+        return art_apex::SystemServerComponent{path, size, checksum, isInClasspath};
+      });
 }
 
 std::string OnDeviceRefresh::GetBootImageExtensionImage(bool on_system) const {
@@ -673,7 +726,7 @@
     /*out*/ std::string* error_msg,
     /*out*/ std::set<std::string>* jars_missing_artifacts,
     /*out*/ std::vector<std::string>* checked_artifacts) const {
-  for (const std::string& jar_path : systemserver_compilable_jars_) {
+  for (const std::string& jar_path : all_systemserver_jars_) {
     const std::string image_location = GetSystemServerImagePath(on_system, jar_path);
     const OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
     // .art files are optional and are not generated for all jars by the build system.
@@ -693,12 +746,11 @@
     const apex::ApexInfo& art_apex_info,
     const std::optional<art_apex::CacheInfo>& cache_info,
     /*out*/ std::vector<std::string>* checked_artifacts) const {
-  std::string error_msg;
-
   if (art_apex_info.getIsFactory()) {
     LOG(INFO) << "Factory ART APEX mounted.";
 
     // ART is not updated, so we can use the artifacts on /system. Check if they exist.
+    std::string error_msg;
     if (BootExtensionArtifactsExist(/*on_system=*/true, isa, &error_msg)) {
       return true;
     }
@@ -770,13 +822,16 @@
 
   const std::vector<art_apex::Component>& bcp_compilable_components =
       cache_info->getFirstDex2oatBootClasspath()->getComponent();
-  if (!CheckComponents(expected_bcp_compilable_components, bcp_compilable_components, &error_msg)) {
-    LOG(INFO) << "Dex2OatClasspath components mismatch: " << error_msg;
+  Result<void> result =
+      CheckComponents(expected_bcp_compilable_components, bcp_compilable_components);
+  if (!result.ok()) {
+    LOG(INFO) << "Dex2OatClasspath components mismatch: " << result.error();
     metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
     return false;
   }
 
   // Cache info looks good, check all compilation artifacts exist.
+  std::string error_msg;
   if (!BootExtensionArtifactsExist(/*on_system=*/false, isa, &error_msg, checked_artifacts)) {
     LOG(INFO) << "Incomplete boot extension artifacts. " << error_msg;
     metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
@@ -797,7 +852,6 @@
     return false;
   };
 
-  std::string error_msg;
   std::set<std::string> jars_missing_artifacts_on_system;
   bool artifacts_on_system_up_to_date = false;
 
@@ -807,6 +861,7 @@
     LOG(INFO) << "Factory APEXes mounted.";
 
     // APEXes are not updated, so we can use the artifacts on /system. Check if they exist.
+    std::string error_msg;
     if (SystemServerArtifactsExist(
             /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) {
       return true;
@@ -894,20 +949,22 @@
   //
   // The system_server components may change unexpectedly, for example an OTA could update
   // services.jar.
-  const std::vector<art_apex::Component> expected_system_server_components =
+  const std::vector<art_apex::SystemServerComponent> expected_system_server_components =
       GenerateSystemServerComponents();
   if (expected_system_server_components.size() != 0 &&
-      (!cache_info->hasSystemServerClasspath() ||
-       !cache_info->getFirstSystemServerClasspath()->hasComponent())) {
-    LOG(INFO) << "Missing SystemServerClasspath components.";
+      (!cache_info->hasSystemServerComponents() ||
+       !cache_info->getFirstSystemServerComponents()->hasComponent())) {
+    LOG(INFO) << "Missing SystemServerComponents.";
     metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
     return compile_all();
   }
 
-  const std::vector<art_apex::Component>& system_server_components =
-      cache_info->getFirstSystemServerClasspath()->getComponent();
-  if (!CheckComponents(expected_system_server_components, system_server_components, &error_msg)) {
-    LOG(INFO) << "SystemServerClasspath components mismatch: " << error_msg;
+  const std::vector<art_apex::SystemServerComponent>& system_server_components =
+      cache_info->getFirstSystemServerComponents()->getComponent();
+  Result<void> result =
+      CheckSystemServerComponents(expected_system_server_components, system_server_components);
+  if (!result.ok()) {
+    LOG(INFO) << "SystemServerComponents mismatch: " << result.error();
     metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
     return compile_all();
   }
@@ -923,14 +980,16 @@
 
   const std::vector<art_apex::Component>& bcp_components =
       cache_info->getFirstBootClasspath()->getComponent();
-  if (!CheckComponents(expected_bcp_components, bcp_components, &error_msg)) {
-    LOG(INFO) << "BootClasspath components mismatch: " << error_msg;
+  result = CheckComponents(expected_bcp_components, bcp_components);
+  if (!result.ok()) {
+    LOG(INFO) << "BootClasspath components mismatch: " << result.error();
     metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
     // Boot classpath components can be dependencies of system_server components, so system_server
     // components need to be recompiled if boot classpath components are changed.
     return compile_all();
   }
 
+  std::string error_msg;
   std::set<std::string> jars_missing_artifacts_on_data;
   if (!SystemServerArtifactsExist(
           /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts)) {
@@ -1238,9 +1297,12 @@
 
   const std::string dex2oat = config_.GetDex2Oat();
   const InstructionSet isa = config_.GetSystemServerIsa();
-  for (const std::string& jar : systemserver_compilable_jars_) {
-    auto scope_guard =
-        android::base::make_scope_guard([&]() { classloader_context.emplace_back(jar); });
+  for (const std::string& jar : all_systemserver_jars_) {
+    auto scope_guard = android::base::make_scope_guard([&]() {
+      if (ContainsElement(systemserver_classpath_jars_, jar)) {
+        classloader_context.emplace_back(jar);
+      }
+    });
 
     if (!ContainsElement(system_server_jars_to_compile, jar)) {
       continue;
@@ -1332,6 +1394,9 @@
       }
       dexopt_args.classloaderFds = fds;
     }
+    if (!art::ContainsElement(systemserver_classpath_jars_, jar)) {
+      dexopt_args.classloaderContextAsParent = true;
+    }
 
     if (!PrepareDex2OatConcurrencyArguments(&dexopt_args.threads, &dexopt_args.cpuSet)) {
       return false;
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index 17003ee..7969aa4 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -23,6 +23,7 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <unordered_set>
 #include <vector>
 
 #include "android-base/result.h"
@@ -69,7 +70,7 @@
 
   // Returns a set of all system server jars.
   std::set<std::string> AllSystemServerJars() const {
-    return {systemserver_compilable_jars_.begin(), systemserver_compilable_jars_.end()};
+    return {all_systemserver_jars_.begin(), all_systemserver_jars_.end()};
   }
 
  private:
@@ -92,7 +93,7 @@
 
   std::vector<com::android::art::Component> GenerateBootExtensionCompilableComponents() const;
 
-  std::vector<com::android::art::Component> GenerateSystemServerComponents() const;
+  std::vector<com::android::art::SystemServerComponent> GenerateSystemServerComponents() const;
 
   std::string GetBootImageExtensionImage(bool on_system) const;
 
@@ -168,13 +169,18 @@
   // List of boot extension components that should be compiled.
   std::vector<std::string> boot_extension_compilable_jars_;
 
-  // List of system_server components that should be compiled.
-  std::vector<std::string> systemserver_compilable_jars_;
+  // Set of system_server components in SYSTEMSERVERCLASSPATH that should be compiled.
+  std::unordered_set<std::string> systemserver_classpath_jars_;
 
   // List of all boot classpath components. Used as the dependencies for compiling the
   // system_server.
   std::vector<std::string> boot_classpath_jars_;
 
+  // List of all system_server components, including those in SYSTEMSERVERCLASSPATH and those in
+  // STANDALONE_SYSTEMSERVER_JARS (jars that system_server loads dynamically using separate
+  // classloaders).
+  std::vector<std::string> all_systemserver_jars_;
+
   const time_t start_time_;
 
   std::unique_ptr<ExecUtils> exec_utils_;
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 1cd4d5a..cd16e9c 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -95,6 +95,14 @@
   return value;
 }
 
+std::string GetEnvironmentVariableOrDefault(const char* name, std::string default_value) {
+  const char* value = getenv(name);
+  if (value == nullptr) {
+    return default_value;
+  }
+  return value;
+}
+
 bool ArgumentMatches(std::string_view argument, std::string_view prefix, std::string* value) {
   if (android::base::StartsWith(argument, prefix)) {
     *value = std::string(argument.substr(prefix.size()));
@@ -162,6 +170,10 @@
       config->SetIsa(art::GetInstructionSetFromString(value.c_str()));
     } else if (ArgumentMatches(arg, "--system-server-classpath=", &value)) {
       config->SetSystemServerClasspath(arg);
+    } else if (ArgumentMatches(arg, "--bootclasspath=", &value)) {
+      config->SetBootClasspath(arg);
+    } else if (ArgumentMatches(arg, "--standalone-system-server-jars=", &value)) {
+      config->SetStandaloneSystemServerJars(arg);
     } else if (ArgumentMatches(arg, "--zygote-arch=", &value)) {
       ZygoteKind zygote_kind;
       if (!ParseZygoteKind(value.c_str(), &zygote_kind)) {
@@ -184,6 +196,8 @@
   UsageError("--isa-root");
   UsageError("--system-server-classpath");
   UsageError("--zygote-arch");
+  UsageError("--bootclasspath");
+  UsageError("--standalone-system-server-jars");
 }
 
 int InitializeTargetConfig(int argc, char** argv, OdrConfig* config) {
@@ -192,6 +206,8 @@
   config->SetBootClasspath(GetEnvironmentVariableOrDie("BOOTCLASSPATH"));
   config->SetDex2oatBootclasspath(GetEnvironmentVariableOrDie("DEX2OATBOOTCLASSPATH"));
   config->SetSystemServerClasspath(GetEnvironmentVariableOrDie("SYSTEMSERVERCLASSPATH"));
+  config->SetStandaloneSystemServerJars(
+      GetEnvironmentVariableOrDefault("STANDALONE_SYSTEMSERVER_JARS", /*default_value=*/""));
   config->SetIsa(art::kRuntimeISA);
 
   std::string zygote;
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 5882424..dabfeb0 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -56,6 +56,7 @@
 using ::aidl::com::android::art::DexoptBcpExtArgs;
 using ::aidl::com::android::art::DexoptSystemServerArgs;
 using ::aidl::com::android::art::Isa;
+using ::testing::_;
 using ::testing::AllOf;
 using ::testing::Contains;
 using ::testing::ElementsAre;
@@ -90,17 +91,11 @@
  public:
   // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
   // to a conflict between gmock and android-base/logging.h (b/132668253).
-  int DexoptBcpExtension(const DexoptBcpExtArgs& args,
-                         time_t,
-                         bool*,
-                         std::string*) override {
+  int DexoptBcpExtension(const DexoptBcpExtArgs& args, time_t, bool*, std::string*) override {
     return DoDexoptBcpExtension(args);
   }
 
-  int DexoptSystemServer(const DexoptSystemServerArgs& args,
-                         time_t,
-                         bool*,
-                         std::string*) override {
+  int DexoptSystemServer(const DexoptSystemServerArgs& args, time_t, bool*, std::string*) override {
     return DoDexoptSystemServer(args);
   }
 
@@ -160,6 +155,8 @@
     framework_jar_ = framework_dir_ + "/framework.jar";
     location_provider_jar_ = framework_dir_ + "/com.android.location.provider.jar";
     services_jar_ = framework_dir_ + "/services.jar";
+    services_foo_jar_ = framework_dir_ + "/services-foo.jar";
+    services_bar_jar_ = framework_dir_ + "/services-bar.jar";
     std::string services_jar_prof = framework_dir_ + "/services.jar.prof";
     std::string javalib_dir = android_art_root_path + "/javalib";
     std::string boot_art = javalib_dir + "/boot.art";
@@ -169,6 +166,8 @@
     CreateEmptyFile(framework_jar_);
     CreateEmptyFile(location_provider_jar_);
     CreateEmptyFile(services_jar_);
+    CreateEmptyFile(services_foo_jar_);
+    CreateEmptyFile(services_bar_jar_);
     CreateEmptyFile(services_jar_prof);
     ASSERT_TRUE(EnsureDirectoryExists(javalib_dir));
     CreateEmptyFile(boot_art);
@@ -178,6 +177,7 @@
     config_.SetBootClasspath(framework_jar_);
     config_.SetDex2oatBootclasspath(framework_jar_);
     config_.SetSystemServerClasspath(Concatenate({location_provider_jar_, ":", services_jar_}));
+    config_.SetStandaloneSystemServerJars(Concatenate({services_foo_jar_, ":", services_bar_jar_}));
     config_.SetIsa(InstructionSet::kX86_64);
     config_.SetZygoteKind(ZygoteKind::kZygote64_32);
 
@@ -201,9 +201,10 @@
   std::pair<std::unique_ptr<OnDeviceRefresh>, MockOdrDexopt*> CreateOdRefresh() {
     auto mock_odr_dexopt = std::make_unique<MockOdrDexopt>();
     MockOdrDexopt* mock_odr_dexopt_ptr = mock_odr_dexopt.get();
-    auto odrefresh = std::make_unique<OnDeviceRefresh>(
-        config_, dalvik_cache_dir_ + "/cache-info.xml", std::make_unique<ExecUtils>(),
-        std::move(mock_odr_dexopt));
+    auto odrefresh = std::make_unique<OnDeviceRefresh>(config_,
+                                                       dalvik_cache_dir_ + "/cache-info.xml",
+                                                       std::make_unique<ExecUtils>(),
+                                                       std::move(mock_odr_dexopt));
     return std::make_pair(std::move(odrefresh), mock_odr_dexopt_ptr);
   }
 
@@ -216,6 +217,8 @@
   std::string framework_jar_;
   std::string location_provider_jar_;
   std::string services_jar_;
+  std::string services_foo_jar_;
+  std::string services_bar_jar_;
   std::string dalvik_cache_dir_;
   std::string framework_dir_;
   std::string boot_profile_file_;
@@ -234,12 +237,29 @@
   EXPECT_CALL(*mock_odr_dexopt,
               DoDexoptSystemServer(
                   AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
-                        Field(&DexoptSystemServerArgs::classloaderContext, IsEmpty()))))
+                        Field(&DexoptSystemServerArgs::classloaderContext, IsEmpty()),
+                        Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(false)))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(
+      *mock_odr_dexopt,
+      DoDexoptSystemServer(AllOf(
+          Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+          Field(&DexoptSystemServerArgs::classloaderContext, ElementsAre(location_provider_jar_)),
+          Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(false)))))
       .WillOnce(Return(0));
   EXPECT_CALL(*mock_odr_dexopt,
-              DoDexoptSystemServer(AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
-                                         Field(&DexoptSystemServerArgs::classloaderContext,
-                                               ElementsAre(location_provider_jar_)))))
+              DoDexoptSystemServer(
+                  AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(services_foo_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContext,
+                              ElementsAre(location_provider_jar_, services_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(true)))))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_odr_dexopt,
+              DoDexoptSystemServer(
+                  AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(services_bar_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContext,
+                              ElementsAre(location_provider_jar_, services_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(true)))))
       .WillOnce(Return(0));
 
   EXPECT_EQ(
@@ -253,17 +273,40 @@
 TEST_F(OdRefreshTest, PartialSystemServerJars) {
   auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
 
+  EXPECT_CALL(
+      *mock_odr_dexopt,
+      DoDexoptSystemServer(AllOf(
+          Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+          Field(&DexoptSystemServerArgs::classloaderContext, ElementsAre(location_provider_jar_)),
+          Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(false)))))
+      .WillOnce(Return(0));
   EXPECT_CALL(*mock_odr_dexopt,
-              DoDexoptSystemServer(AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
-                                         Field(&DexoptSystemServerArgs::classloaderContext,
-                                               ElementsAre(location_provider_jar_)))))
+              DoDexoptSystemServer(
+                  AllOf(Field(&DexoptSystemServerArgs::dexPath, Eq(services_bar_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContext,
+                              ElementsAre(location_provider_jar_, services_jar_)),
+                        Field(&DexoptSystemServerArgs::classloaderContextAsParent, Eq(true)))))
       .WillOnce(Return(0));
 
-  EXPECT_EQ(odrefresh->Compile(*metrics_,
-                               CompilationOptions{
-                                   .system_server_jars_to_compile = {services_jar_},
-                               }),
-            ExitCode::kCompilationSuccess);
+  EXPECT_EQ(
+      odrefresh->Compile(*metrics_,
+                         CompilationOptions{
+                             .system_server_jars_to_compile = {services_jar_, services_bar_jar_},
+                         }),
+      ExitCode::kCompilationSuccess);
+}
+
+// Verifies that odrefresh can run properly when the STANDALONE_SYSTEM_SERVER_JARS variable is
+// missing, which is expected on Android S.
+TEST_F(OdRefreshTest, MissingStandaloneSystemServerJars) {
+  config_.SetStandaloneSystemServerJars("");
+  auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+  EXPECT_EQ(
+      odrefresh->Compile(*metrics_,
+                         CompilationOptions{
+                             .system_server_jars_to_compile = odrefresh->AllSystemServerJars(),
+                         }),
+      ExitCode::kCompilationSuccess);
 }
 
 TEST_F(OdRefreshTest, CompileSetsCompilerFilter) {
@@ -275,24 +318,23 @@
 
   {
     auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+    ON_CALL(*mock_odr_dexopt, DoDexoptSystemServer(_)).WillByDefault(Return(0));
 
     // Test setup: default compiler filter should be "speed".
     auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "");
 
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(
-            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
-            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(
-            Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
-            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+                    Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+                    Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
     EXPECT_EQ(
         odrefresh->Compile(*metrics_,
                            CompilationOptions{
@@ -315,15 +357,14 @@
             Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
             Field(&DexoptSystemServerArgs::profileFd, Ge(0)),
             Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED_PROFILE)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(
-            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
-            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+                    Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::SPEED)))))
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
     EXPECT_EQ(
         odrefresh->Compile(*metrics_,
                            CompilationOptions{
@@ -338,20 +379,18 @@
     // Test setup: "verify" compiler filter should be simply applied.
     auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "verify");
 
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(
-            Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
-            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(
-            Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
-            Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
-      .WillOnce(Return(0))
-      .RetiresOnSaturation();
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::dexPath, Eq(location_provider_jar_)),
+                    Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::dexPath, Eq(services_jar_)),
+                    Field(&DexoptSystemServerArgs::compilerFilter, Eq(CompilerFilter::VERIFY)))))
+        .WillOnce(Return(0))
+        .RetiresOnSaturation();
     EXPECT_EQ(
         odrefresh->Compile(*metrics_,
                            CompilationOptions{
@@ -364,13 +403,11 @@
 TEST_F(OdRefreshTest, OutputFilesAndIsa) {
   auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
 
-  EXPECT_CALL(
-      *mock_odr_dexopt,
-      DoDexoptBcpExtension(AllOf(
-          Field(&DexoptBcpExtArgs::isa, Eq(Isa::X86_64)),
-          Field(&DexoptBcpExtArgs::imageFd, Ge(0)),
-          Field(&DexoptBcpExtArgs::vdexFd, Ge(0)),
-          Field(&DexoptBcpExtArgs::oatFd, Ge(0)))))
+  EXPECT_CALL(*mock_odr_dexopt,
+              DoDexoptBcpExtension(AllOf(Field(&DexoptBcpExtArgs::isa, Eq(Isa::X86_64)),
+                                         Field(&DexoptBcpExtArgs::imageFd, Ge(0)),
+                                         Field(&DexoptBcpExtArgs::vdexFd, Ge(0)),
+                                         Field(&DexoptBcpExtArgs::oatFd, Ge(0)))))
       .WillOnce(Return(0));
 
   EXPECT_CALL(*mock_odr_dexopt,
diff --git a/odrefresh/schema/current.txt b/odrefresh/schema/current.txt
index 4757c87..a7aa52e 100644
--- a/odrefresh/schema/current.txt
+++ b/odrefresh/schema/current.txt
@@ -7,12 +7,12 @@
     method public com.android.art.Classpath getBootClasspath();
     method public com.android.art.Classpath getDex2oatBootClasspath();
     method public com.android.art.ModuleInfoList getModuleInfoList();
-    method public com.android.art.Classpath getSystemServerClasspath();
+    method public com.android.art.SystemServerComponents getSystemServerComponents();
     method public void setArtModuleInfo(com.android.art.ModuleInfo);
     method public void setBootClasspath(com.android.art.Classpath);
     method public void setDex2oatBootClasspath(com.android.art.Classpath);
     method public void setModuleInfoList(com.android.art.ModuleInfoList);
-    method public void setSystemServerClasspath(com.android.art.Classpath);
+    method public void setSystemServerComponents(com.android.art.SystemServerComponents);
   }
 
   public class Classpath {
@@ -49,6 +49,18 @@
     method public void setModuleInfo(com.android.art.ModuleInfo);
   }
 
+  public class SystemServerComponent extends com.android.art.Component {
+    ctor public SystemServerComponent();
+    method public boolean getIsInClasspath();
+    method public void setIsInClasspath(boolean);
+  }
+
+  public class SystemServerComponents {
+    ctor public SystemServerComponents();
+    method public com.android.art.SystemServerComponent getComponent();
+    method public void setComponent(com.android.art.SystemServerComponent);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.art.CacheInfo read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index 53f6b21..bcce9a0 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -28,9 +28,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 import java.util.stream.Collectors;
 
 /**
@@ -105,10 +107,12 @@
         verifyGeneratedArtifactsLoaded();
     }
 
-    private String[] getSystemServerClasspath() throws Exception {
-        String systemServerClasspath =
-                getDevice().executeShellCommand("echo $SYSTEMSERVERCLASSPATH");
-        return systemServerClasspath.trim().split(":");
+    private String[] getListFromEnvironmentVariable(String name) throws Exception {
+        String systemServerClasspath = getDevice().executeShellCommand("echo $" + name).trim();
+        if (!systemServerClasspath.isEmpty()) {
+            return systemServerClasspath.split(":");
+        }
+        return new String[0];
     }
 
     private String getSystemServerIsa(String mappedArtifact) {
@@ -121,8 +125,12 @@
     }
 
     private void verifySystemServerLoadedArtifacts() throws Exception {
-        String[] classpathElements = getSystemServerClasspath();
+        String[] classpathElements = getListFromEnvironmentVariable("SYSTEMSERVERCLASSPATH");
         assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
+        String[] standaloneJars = getListFromEnvironmentVariable("STANDALONE_SYSTEMSERVER_JARS");
+        String[] allSystemServerJars = Stream
+                .concat(Arrays.stream(classpathElements), Arrays.stream(standaloneJars))
+                .toArray(String[]::new);
 
         final Set<String> mappedArtifacts = sTestUtils.getSystemServerLoadedArtifacts();
         assertTrue(
@@ -133,7 +141,7 @@
                 String.format("%s/%s", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME, isa);
 
         // Check components in the system_server classpath have mapped artifacts.
-        for (String element : classpathElements) {
+        for (String element : allSystemServerJars) {
           String escapedPath = element.substring(1).replace('/', '@');
           for (String extension : OdsignTestUtils.APP_ARTIFACT_EXTENSIONS) {
             final String fullArtifactPath =