odrefresh: regenerate artifacts after ART APEX update

Replaces compile_bcp.sh to check and compile boot class path
extensions and system server jars.

Enable ART to load those artifacts when present in the ART APEX data
directory.

Bug: 160683548
Test: art_libartbase_tests
Test: adb root && adb odrefresh {--force-check,--force-compile}
Test: adb install com.android.art && adb reboot && adb root && \
      adb shell odrefresh {--check,--compile} && adb reboot && \
      adb shell cat /proc/<zygote>/maps | grep apexdata
Change-Id: I81bf520d38f9dc0109c91f192bc6e728099049fd
diff --git a/Android.mk b/Android.mk
index e51763c..2359bc8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -349,6 +349,7 @@
     $(call art_module_lib,libart-compiler) \
     $(call art_module_lib,libopenjdkjvm) \
     $(call art_module_lib,libopenjdkjvmti) \
+    $(call art_module_lib,odrefresh) \
     $(call art_module_lib,profman) \
     $(call art_module_lib,libadbconnection) \
     $(call art_module_lib,libperfetto_hprof) \
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index 268c659..67e0557 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -88,6 +88,7 @@
     dexoptanalyzer \
     imgdiag \
     oatdump \
+    odrefresh \
     profman \
 
 ART_CORE_EXECUTABLES := \
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 7ae6596..2bc2e8b7 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -136,6 +136,7 @@
     art_libdexfile_tests \
     art_libprofile_tests \
     art_oatdump_tests \
+    art_odrefresh_tests \
     art_profman_tests \
     art_runtime_compiler_tests \
     art_runtime_tests \
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 4c8502b..eaf0b5a 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -24,6 +24,7 @@
 //   only the "first" (likely 64-bit) version is required on host).
 art_runtime_base_binaries_prefer32_on_device_first_on_host = [
     "dexoptanalyzer",
+    "odrefresh",
     "profman",
 ]
 
@@ -131,7 +132,6 @@
 
 // Tools exclusively for the device APEX derived from art-tools in art/Android.mk.
 art_tools_device_only_binaries = [
-    "compile_bcp.sh",
     // oatdump cannot link with host linux_bionic due to not using clang lld;
     // TODO: Make it work with clang lld.
     "oatdump",
@@ -333,6 +333,7 @@
     "art_libdexfile_support_tests",
     "art_libprofile_tests",
     "art_oatdump_tests",
+    "art_odrefresh_tests",
     "art_profman_tests",
     "art_runtime_compiler_tests",
     "art_runtime_tests",
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 95e193d..91af3db 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -619,9 +619,9 @@
     # removed in Android R.
 
     # Check binaries for ART.
-    self._checker.check_executable("compile_bcp.sh")
-    self._checker.check_executable('oatdump')
     self._checker.check_multilib_executable('dex2oat')
+    self._checker.check_executable('oatdump')
+    self._checker.check_executable("odrefresh")
 
     # Check internal libraries for ART.
     self._checker.check_native_library('libperfetto_hprof')
@@ -646,6 +646,7 @@
     self._checker.check_executable('hprof-conv')
     self._checker.check_symlinked_first_executable('dex2oatd')
     self._checker.check_symlinked_first_executable('dex2oat')
+    self._checker.check_executable("odrefresh")
 
     # Check exported native libraries for Managed Core Library.
     self._checker.check_native_library('libicu')
@@ -753,6 +754,7 @@
     self._checker.check_art_test_executable('art_libdexfile_tests')
     self._checker.check_art_test_executable('art_libprofile_tests')
     self._checker.check_art_test_executable('art_oatdump_tests')
+    self._checker.check_art_test_executable('art_odrefresh_tests')
     self._checker.check_art_test_executable('art_profman_tests')
     self._checker.check_art_test_executable('art_runtime_compiler_tests')
     self._checker.check_art_test_executable('art_runtime_tests')
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index f627fc9..0f5fb0c 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -159,6 +159,34 @@
   std::vector<std::unique_ptr<FakeDex>> fake_dex_files;
 };
 
+// Helper class that removes an environment variable whilst in scope.
+class ScopedUnsetEnvironmentVariable {
+ public:
+  explicit ScopedUnsetEnvironmentVariable(const char* variable)
+      : variable_{variable}, old_value_{GetOldValue(variable)} {
+    unsetenv(variable);
+  }
+
+  ~ScopedUnsetEnvironmentVariable() {
+    if (old_value_.has_value()) {
+      static constexpr int kReplace = 1;  // tidy-issue: replace argument has libc dependent name.
+      setenv(variable_, old_value_.value().c_str(), kReplace);
+    } else {
+      unsetenv(variable_);
+    }
+  }
+
+ private:
+  static std::optional<std::string> GetOldValue(const char* variable) {
+    const char* value = getenv(variable);
+    return value != nullptr ? std::optional<std::string>{value} : std::nullopt;
+  }
+
+  const char* variable_;
+  std::optional<std::string> old_value_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
+};
+
 class CommonArtTestImpl {
  public:
   CommonArtTestImpl() = default;
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index 67caaa6..bdabf5a 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 
 #include <string>
+#include <string_view>
 
 #include <android-base/logging.h>
 
diff --git a/libartbase/base/file_utils_test.cc b/libartbase/base/file_utils_test.cc
index b7cc625..4dce3dc 100644
--- a/libartbase/base/file_utils_test.cc
+++ b/libartbase/base/file_utils_test.cc
@@ -28,34 +28,6 @@
 
 class FileUtilsTest : public CommonArtTest {};
 
-// Helper class that removes an environment variable whilst in scope.
-class ScopedUnsetEnvironmentVariable {
- public:
-  explicit ScopedUnsetEnvironmentVariable(const char* variable)
-      : variable_{variable}, old_value_{GetOldValue(variable)} {
-    unsetenv(variable);
-  }
-
-  ~ScopedUnsetEnvironmentVariable() {
-    if (old_value_.has_value()) {
-      static constexpr int kReplace = 1;  // tidy-issue: replace argument has libc dependent name.
-      setenv(variable_, old_value_.value().c_str(), kReplace);
-    } else {
-      unsetenv(variable_);
-    }
-  }
-
- private:
-  static std::optional<std::string> GetOldValue(const char* variable) {
-    const char* value = getenv(variable);
-    return value != nullptr ? std::optional<std::string>{value} : std::nullopt;
-  }
-
-  const char* variable_;
-  std::optional<std::string> old_value_;
-  DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
-};
-
 TEST_F(FileUtilsTest, GetDalvikCacheFilename) {
   std::string name;
   std::string error;
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
new file mode 100644
index 0000000..4a96944
--- /dev/null
+++ b/odrefresh/Android.bp
@@ -0,0 +1,119 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+    name: "odrefresh-defaults",
+    host_supported: true,
+    defaults: ["art_defaults"],
+    srcs: [
+        "odrefresh.cc",
+    ],
+    local_include_dirs: ["include"],
+    header_libs: ["dexoptanalyzer_headers"],
+    generated_sources: ["apex-info-list"],
+    shared_libs: [
+        "libartpalette",
+        "libbase",
+        "liblog",
+    ],
+    static_libs: ["libxml2"],
+    target: {
+        android: {
+            // Use the 32-bit version of odrefresh on devices.
+            compile_multilib: "prefer32",
+        },
+        linux: {
+            enabled: true,
+        },
+        host: {
+            shared_libs: [
+                // Both these libraries for libxml2 on host for code derived from apex-info-list.
+                "libicui18n",
+                "libicuuc",
+            ],
+        },
+    },
+    tidy: true,
+    tidy_flags: [
+        "-format-style='file'",
+        "--header-filter='system/apex/'",
+    ],
+}
+
+cc_library_headers {
+    name: "odrefresh_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+    stl: "none",
+    system_shared_libs: [],
+    min_sdk_version: "29", // As part of mainline modules(APEX), it should support at least 29(Q).
+    sdk_version: "minimum", // The minimum sdk version required by users of this module.
+    apex_available: [
+        "//apex_available:platform", // For odsign.
+    ],
+    visibility: ["//visibility:public"],
+}
+
+art_cc_binary {
+    name: "odrefresh",
+    defaults: ["odrefresh-defaults"],
+    required: [
+        "dexoptanalyzer",
+        "dex2oat",
+    ],
+    shared_libs: [
+        "libart",
+        "libartbase",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+art_cc_binary {
+    name: "odrefreshd",
+    defaults: [
+        "art_debug_defaults",
+        "odrefresh-defaults",
+    ],
+    required: [
+        "dexoptanalyzerd",
+        "dex2oatd",
+    ],
+    shared_libs: [
+        "libartd",
+        "libartbased",
+    ],
+    apex_available: [
+        "com.android.art.debug",
+    ],
+}
+
+art_cc_test {
+    name: "art_odrefresh_tests",
+    defaults: [
+        "art_gtest_defaults",
+    ],
+    header_libs: ["odrefresh_headers"],
+    srcs: [
+        "odr_artifacts_test.cc",
+        "odrefresh_test.cc",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
diff --git a/odrefresh/README.md b/odrefresh/README.md
new file mode 100644
index 0000000..181ef72
--- /dev/null
+++ b/odrefresh/README.md
@@ -0,0 +1,7 @@
+# On-Device Refresh (odrefresh)
+
+This tool is part of the ART APEX and is used to refresh compilation artifacts following an
+ART APEX update. It provides two key features:
+
+* checking the freshness of compilation artifacts for boot class path extensions and system_server.
+* regenerating the compilation artifacts for boot class path extensions and system_server.
\ No newline at end of file
diff --git a/odrefresh/TODO.md b/odrefresh/TODO.md
new file mode 100644
index 0000000..95c4aa1
--- /dev/null
+++ b/odrefresh/TODO.md
@@ -0,0 +1,22 @@
+# TODO Work Items
+
+## TODO (STOPSHIP until done)
+
+1. Add a log file that tracks status of recent compilation.
+2. Implement back off on trying compilation when previous attempt(s) failed.
+3. Free space calculation and only attempting compilation if sufficient space.
+4. Metrics for tracking issues:
+   - Successful compilation of all artifacts.
+   - Time limit exceeded (indicates a pathological issue, e.g. dex2oat bug, device driver bug, etc).
+   - Insufficient space for compilation.
+   - Compilation failure (boot extensions)
+   - Compilation failure (system server)
+   - Unexpected error (a setup or clean-up action failed).
+5. Metrics recording for subprocess timeouts.
+6. Decide and implement testing.
+
+## DONE
+
+1. <strike>Fix dexoptanalyzer so it can analyze boot extensions.</strike>
+2. <strike>Parse apex-info-list.xml into an apex_info (to make version and location available).</strike>
+3. <strike>Timeouts for pathological failures.</strike>
diff --git a/odrefresh/include/odrefresh/odrefresh.h b/odrefresh/include/odrefresh/odrefresh.h
new file mode 100644
index 0000000..8d1166a
--- /dev/null
+++ b/odrefresh/include/odrefresh/odrefresh.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
+#define ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
+
+#include <sysexits.h>
+
+namespace art {
+namespace odrefresh {
+
+static constexpr const char* kOdrefreshArtifactDirectory =
+    "/data/misc/apexdata/com.android.art/dalvik-cache";
+
+//
+// Exit codes from the odrefresh process (in addition to standard exit codes in sysexits.h).
+//
+// NB if odrefresh crashes, then the caller should not sign any artifacts and should remove any
+// unsigned artifacts under `kOdrefreshArtifactDirectory`.
+//
+enum ExitCode {
+  // No compilation required, all artifacts look good or there is insufficient space to compile.
+  // For ART APEX in the system image, there may be no artifacts present under
+  // `kOdrefreshArtifactDirectory`.
+  kOkay = EX_OK,
+
+  // Compilation required. Re-run program with --compile on the command-line to generate
+  // new artifacts under `kOdrefreshArtifactDirectory`.
+  kCompilationRequired = 1,
+
+  // Compilation failed. Artifacts under `kOdrefreshArtifactDirectory` will be valid. This may
+  // happen, for example, if compilation of boot extensions succeeds, but the compilation of the
+  // system_server jars fails due to lack of storage space.
+  kCompilationFailed = 2,
+};
+
+static_assert(EX_OK == 0);
+static_assert(ExitCode::kOkay < EX__BASE);
+static_assert(ExitCode::kCompilationFailed < EX__BASE);
+
+}  // namespace odrefresh
+}  // namespace art
+
+#endif  // ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
diff --git a/odrefresh/odr_artifacts.h b/odrefresh/odr_artifacts.h
new file mode 100644
index 0000000..66d76f0
--- /dev/null
+++ b/odrefresh/odr_artifacts.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_ODR_ARTIFACTS_H_
+#define ART_ODREFRESH_ODR_ARTIFACTS_H_
+
+#include <iosfwd>
+#include <string>
+
+#include <base/file_utils.h>
+
+namespace art {
+namespace odrefresh {
+
+// A grouping of odrefresh generated artifacts.
+class OdrArtifacts {
+ public:
+  static OdrArtifacts ForBootImageExtension(const std::string& image_path) {
+    return OdrArtifacts(image_path, "oat");
+  }
+
+  static OdrArtifacts ForSystemServer(const std::string& image_path) {
+    return OdrArtifacts(image_path, "odex");
+  }
+
+  const std::string& ImagePath() const { return image_path_; }
+  const std::string& OatPath() const { return oat_path_; }
+  const std::string& VdexPath() const { return vdex_path_; }
+
+ private:
+  OdrArtifacts(const std::string& image_path, const char* aot_extension)
+      : image_path_{image_path},
+        oat_path_{ReplaceFileExtension(image_path, aot_extension)},
+        vdex_path_{ReplaceFileExtension(image_path, "vdex")} {}
+
+  OdrArtifacts() = delete;
+  OdrArtifacts(const OdrArtifacts&) = delete;
+  OdrArtifacts& operator=(const OdrArtifacts&) = delete;
+
+  const std::string image_path_;
+  const std::string oat_path_;
+  const std::string vdex_path_;
+};
+
+}  // namespace odrefresh
+}  // namespace art
+
+#endif  // ART_ODREFRESH_ODR_ARTIFACTS_H_
diff --git a/odrefresh/odr_artifacts_test.cc b/odrefresh/odr_artifacts_test.cc
new file mode 100644
index 0000000..97f1fd7
--- /dev/null
+++ b/odrefresh/odr_artifacts_test.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odr_artifacts.h"
+
+#include "arch/instruction_set.h"
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+#include "base/string_view_cpp20.h"
+#include "odrefresh/odrefresh.h"
+
+namespace art {
+namespace odrefresh {
+
+TEST(OdrArtifactsTest, ForBootImageExtension) {
+  ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+
+  const std::string image_location = GetApexDataBootImage("/system/framework/framework.jar");
+  EXPECT_TRUE(StartsWith(image_location, GetArtApexData()));
+
+  const std::string image_filename =
+      GetSystemImageFilename(image_location.c_str(), InstructionSet::kArm64);
+
+  const auto artifacts = OdrArtifacts::ForBootImageExtension(image_filename);
+  CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.art",
+           artifacts.ImagePath());
+  CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.oat",
+           artifacts.OatPath());
+  CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.vdex",
+           artifacts.VdexPath());
+}
+
+TEST(OdrArtifactsTest, ForSystemServer) {
+  ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+
+  const std::string image_location = GetApexDataImage("/system/framework/services.jar");
+  EXPECT_TRUE(StartsWith(image_location, GetArtApexData()));
+
+  const std::string image_filename =
+      GetSystemImageFilename(image_location.c_str(), InstructionSet::kX86);
+  const auto artifacts = OdrArtifacts::ForSystemServer(image_filename);
+  CHECK_EQ(
+      std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.art",
+      artifacts.ImagePath());
+  CHECK_EQ(
+      std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.odex",
+      artifacts.OatPath());
+  CHECK_EQ(
+      std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.vdex",
+      artifacts.VdexPath());
+}
+
+}  // namespace odrefresh
+}  // namespace art
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
new file mode 100644
index 0000000..6b0f212
--- /dev/null
+++ b/odrefresh/odr_config.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_ODR_CONFIG_H_
+#define ART_ODREFRESH_ODR_CONFIG_H_
+
+#include <string>
+#include <vector>
+
+#include "android-base/file.h"
+#include "arch/instruction_set.h"
+#include "base/globals.h"
+#include "log/log.h"
+
+namespace art {
+namespace odrefresh {
+
+// An enumeration of the possible zygote configurations on Android.
+enum class ZygoteKind : uint8_t {
+  // 32-bit primary zygote, no secondary zygote.
+  kZygote32 = 0,
+  // 32-bit primary zygote, 64-bit secondary zygote.
+  kZygote32_64 = 1,
+  // 64-bit primary zygote, 32-bit secondary zygote.
+  kZygote64_32 = 2,
+  // 64-bit praimry zygote, no secondary zygote.
+  kZygote64 = 3
+};
+
+// Configuration class for odrefresh. Exists to enable abstracting environment variables and
+// system properties into a configuration class for development and testing purposes.
+class OdrConfig final {
+ private:
+  std::string apex_info_list_file_;
+  std::string art_bin_dir_;
+  std::string dex2oat_;
+  std::string dex2oat_boot_classpath_;
+  bool dry_run_;
+  InstructionSet isa_;
+  std::string program_name_;
+  std::string system_server_classpath_;
+  std::string updatable_bcp_packages_file_;
+  ZygoteKind zygote_kind_;
+
+ public:
+  explicit OdrConfig(const char* program_name)
+    : dry_run_(false),
+      isa_(InstructionSet::kNone),
+      program_name_(android::base::Basename(program_name)) {
+  }
+
+  const std::string& GetApexInfoListFile() const { return apex_info_list_file_; }
+
+  std::vector<InstructionSet> GetBootExtensionIsas() const {
+    const auto [isa32, isa64] = GetPotentialInstructionSets();
+    switch (zygote_kind_) {
+      case ZygoteKind::kZygote32:
+        return {isa32};
+      case ZygoteKind::kZygote32_64:
+      case ZygoteKind::kZygote64_32:
+        return {isa32, isa64};
+      case ZygoteKind::kZygote64:
+        return {isa64};
+    }
+  }
+
+  InstructionSet GetSystemServerIsa() const {
+    const auto [isa32, isa64] = GetPotentialInstructionSets();
+    switch (zygote_kind_) {
+      case ZygoteKind::kZygote32:
+      case ZygoteKind::kZygote32_64:
+        return isa32;
+      case ZygoteKind::kZygote64_32:
+      case ZygoteKind::kZygote64:
+        return isa64;
+    }
+  }
+
+  const std::string& GetDex2oatBootClasspath() const { return dex2oat_boot_classpath_; }
+
+  std::string GetDex2Oat() const {
+    const char* prefix = UseDebugBinaries() ? "dex2oatd" : "dex2oat";
+    const char* suffix = "";
+    if (kIsTargetBuild) {
+      switch (zygote_kind_) {
+        case ZygoteKind::kZygote32:
+          suffix = "32";
+          break;
+        case ZygoteKind::kZygote32_64:
+        case ZygoteKind::kZygote64_32:
+        case ZygoteKind::kZygote64:
+          suffix = "64";
+          break;
+      }
+    }
+    return art_bin_dir_ + '/' + prefix + suffix;
+  }
+
+  std::string GetDexOptAnalyzer() const {
+    const char* dexoptanalyzer{UseDebugBinaries() ? "dexoptanalyzerd" : "dexoptanalyzer"};
+    return art_bin_dir_ + '/' + dexoptanalyzer;
+  }
+
+  bool GetDryRun() const { return dry_run_; }
+  const std::string& GetSystemServerClasspath() const { return system_server_classpath_; }
+  const std::string& GetUpdatableBcpPackagesFile() const { return updatable_bcp_packages_file_; }
+
+  void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
+  void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
+
+  void SetDex2oatBootclasspath(const std::string& classpath) {
+    dex2oat_boot_classpath_ = classpath;
+  }
+
+  void SetDryRun() { dry_run_ = true; }
+  void SetIsa(const InstructionSet isa) { isa_ = isa; }
+
+  void SetSystemServerClasspath(const std::string& classpath) {
+    system_server_classpath_ = classpath;
+  }
+
+  void SetUpdatableBcpPackagesFile(const std::string& file) { updatable_bcp_packages_file_ = file; }
+  void SetZygoteKind(ZygoteKind zygote_kind) { zygote_kind_ = zygote_kind; }
+
+ 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
+  // architecture. The current `isa` is based on `kRuntimeISA` on target, odrefresh is compiled
+  // 32-bit by default so this method returns all options which are finessed based on the
+  // `ro.zygote` property.
+  std::pair<InstructionSet, InstructionSet> GetPotentialInstructionSets() const {
+    switch (isa_) {
+      case art::InstructionSet::kArm:
+      case art::InstructionSet::kArm64:
+        return std::make_pair(art::InstructionSet::kArm, art::InstructionSet::kArm64);
+      case art::InstructionSet::kX86:
+      case art::InstructionSet::kX86_64:
+        return std::make_pair(art::InstructionSet::kX86, art::InstructionSet::kX86_64);
+      case art::InstructionSet::kThumb2:
+      case art::InstructionSet::kNone:
+        LOG(FATAL) << "Invalid instruction set " << isa_;
+        return std::make_pair(art::InstructionSet::kNone, art::InstructionSet::kNone);
+    }
+  }
+
+  bool UseDebugBinaries() const { return program_name_ == "odrefreshd"; }
+
+  OdrConfig() = delete;
+  OdrConfig(const OdrConfig&) = delete;
+  OdrConfig& operator=(const OdrConfig&) = delete;
+};
+
+}  // namespace odrefresh
+}  // namespace art
+
+#endif  // ART_ODREFRESH_ODR_CONFIG_H_
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
new file mode 100644
index 0000000..c75a62e
--- /dev/null
+++ b/odrefresh/odrefresh.cc
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odrefresh/odrefresh.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <cstdarg>
+#include <cstdlib>
+#include <initializer_list>
+#include <iosfwd>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <queue>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android/log.h"
+#include "arch/instruction_set.h"
+#include "base/bit_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "base/string_view_cpp20.h"
+#include "base/unix_file/fd_file.h"
+#include "com_android_apex.h"
+#include "dexoptanalyzer.h"
+#include "exec_utils.h"
+#include "palette/palette.h"
+#include "palette/palette_types.h"
+
+#include "odr_artifacts.h"
+#include "odr_config.h"
+
+namespace art {
+namespace odrefresh {
+namespace {
+
+static void UsageErrorV(const char* fmt, va_list ap) {
+  std::string error;
+  android::base::StringAppendV(&error, fmt, ap);
+  if (isatty(fileno(stderr))) {
+    std::cerr << error << std::endl;
+  } else {
+    LOG(ERROR) << error;
+  }
+}
+
+static void UsageError(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+}
+
+NO_RETURN static void ArgumentError(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+  UsageError("Try '--help' for more information.");
+  exit(EX_USAGE);
+}
+
+NO_RETURN static void UsageHelp(const char* argv0) {
+  std::string name(android::base::Basename(argv0));
+  UsageError("Usage: %s ACTION", name.c_str());
+  UsageError("On-device refresh tool for boot class path extensions and system server");
+  UsageError("following an update of the ART APEX.");
+  UsageError("");
+  UsageError("Valid ACTION choices are:");
+  UsageError("");
+  UsageError("--check          Check compilation artifacts are up to date.");
+  UsageError("--compile        Compile boot class path extensions and system_server jars");
+  UsageError("                 when necessary).");
+  UsageError("--force-compile  Unconditionally compile the boot class path extensions and");
+  UsageError("                 system_server jars.");
+  UsageError("--help           Display this help information.");
+  exit(EX_USAGE);
+}
+
+static std::string Concatenate(std::initializer_list<std::string_view> args) {
+  std::stringstream ss;
+  for (auto arg : args) {
+    ss << arg;
+  }
+  return ss.str();
+}
+
+static std::string QuotePath(std::string_view path) {
+  return Concatenate({"'", path, "'"});
+}
+
+static void EraseFiles(const std::vector<std::unique_ptr<File>>& files) {
+  for (auto& file : files) {
+    file->Erase(/*unlink=*/true);
+  }
+}
+
+// Moves `files` to the directory `output_directory_path`.
+//
+// If any of the files cannot be moved, then all copies of the files are removed from both
+// the original location and the output location.
+//
+// Returns true if all files are moved, false otherwise.
+static bool MoveOrEraseFiles(const std::vector<std::unique_ptr<File>>& files,
+                             std::string_view output_directory_path) {
+  std::vector<std::unique_ptr<File>> output_files;
+  for (auto& file : files) {
+    const std::string file_basename(android::base::Basename(file->GetPath()));
+    const std::string output_file_path = Concatenate({output_directory_path, "/", file_basename});
+    const std::string input_file_path = file->GetPath();
+
+    output_files.emplace_back(OS::CreateEmptyFileWriteOnly(output_file_path.c_str()));
+    if (output_files.back() == nullptr) {
+      PLOG(ERROR) << "Failed to open " << QuotePath(output_file_path);
+      output_files.pop_back();
+      EraseFiles(output_files);
+      EraseFiles(files);
+      return false;
+    }
+
+    const size_t file_bytes = file->GetLength();
+    if (!output_files.back()->Copy(file.get(), /*offset=*/0, file_bytes)) {
+      PLOG(ERROR) << "Failed to copy " << QuotePath(file->GetPath())
+                  << " to " << QuotePath(output_file_path);
+      EraseFiles(output_files);
+      EraseFiles(files);
+      return false;
+    }
+
+    if (!file->Erase(/*unlink=*/true)) {
+      PLOG(ERROR) << "Failed to erase " << QuotePath(file->GetPath());
+      EraseFiles(output_files);
+      EraseFiles(files);
+      return false;
+    }
+
+    if (output_files.back()->FlushCloseOrErase() != 0) {
+      PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path);
+      EraseFiles(output_files);
+      EraseFiles(files);
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+bool ParseZygoteKind(const char* input, ZygoteKind* zygote_kind) {
+  std::string_view z(input);
+  if (z == "zygote32") {
+    *zygote_kind = ZygoteKind::kZygote32;
+    return true;
+  } else if (z == "zygote32_64") {
+    *zygote_kind = ZygoteKind::kZygote32_64;
+    return true;
+  } else if (z == "zygote64_32") {
+    *zygote_kind = ZygoteKind::kZygote64_32;
+    return true;
+  } else if (z == "zygote64") {
+    *zygote_kind = ZygoteKind::kZygote64;
+    return true;
+  }
+  return false;
+}
+
+class OnDeviceRefresh final {
+ private:
+  // Maximum execution time for odrefresh from start to end.
+  static constexpr time_t kMaximumExecutionSeconds = 300;
+
+  // Maximum execution time for any child process spawned.
+  static constexpr time_t kMaxChildProcessSeconds = 90;
+
+  const OdrConfig& config_;
+
+  std::vector<std::string> boot_extension_compilable_jars_;
+
+  std::string systemserver_output_dir_;
+  std::vector<std::string> systemserver_compilable_jars_;
+
+  const time_t start_time_;
+
+ public:
+  explicit OnDeviceRefresh(const OdrConfig& config) : config_(config), start_time_(time(nullptr)) {
+    const std::string art_apex_data = GetArtApexData();
+
+    for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
+      // Boot class path extensions are those not in the ART APEX. Updatable APEXes should not
+      // have DEX files in the DEX2OATBOOTCLASSPATH. At the time of writing i18n is a non-updatable
+      // APEX and so does appear in the DEX2OATBOOTCLASSPATH.
+      if (!LocationIsOnArtModule(jar)) {
+        boot_extension_compilable_jars_.emplace_back(jar);
+      }
+    }
+
+    for (const std::string& jar : android::base::Split(config_.GetSystemServerClasspath(), ":")) {
+      // Only consider DEX files on the SYSTEMSERVERCLASSPATH for compilation that do not reside
+      // in APEX modules. Otherwise, we'll recompile on boot any time one of these APEXes updates.
+      if (!LocationIsOnApex(jar)) {
+        systemserver_compilable_jars_.emplace_back(jar);
+      }
+    }
+  }
+
+  time_t GetExecutionTimeUsed() const { return time(nullptr) - start_time_; }
+
+  time_t GetExecutionTimeRemaining() const {
+    return kMaximumExecutionSeconds - GetExecutionTimeUsed();
+  }
+
+  time_t GetSubprocessTimeout() const {
+    return std::max(GetExecutionTimeRemaining(), kMaxChildProcessSeconds);
+  }
+
+  // Read apex_info_list.xml from input stream and determine if the ART APEX
+  // listed is the factory installed version.
+  static bool IsFactoryApex(const std::string& apex_info_list_xml_path) {
+    auto info_list = com::android::apex::readApexInfoList(apex_info_list_xml_path.c_str());
+    if (!info_list.has_value()) {
+      LOG(FATAL) << "Failed to process " << QuotePath(apex_info_list_xml_path);
+    }
+
+    for (const com::android::apex::ApexInfo& info : info_list->getApexInfo()) {
+      if (info.getIsActive() && info.getModuleName() == "com.android.art") {
+        return info.getIsFactory();
+      }
+    }
+
+    LOG(FATAL) << "Failed to find active com.android.art in " << QuotePath(apex_info_list_xml_path);
+    return false;
+  }
+
+  static void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>& args) {
+    args.emplace_back("--android-root=out/empty");
+    args.emplace_back("--abort-on-hard-verifier-error");
+    args.emplace_back("--compilation-reason=boot");
+    args.emplace_back("--image-format=lz4hc");
+    args.emplace_back("--resolve-startup-const-strings=true");
+  }
+
+  static void AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args) {
+    static constexpr std::pair<const char*, const char*> kPropertyArgPairs[] = {
+        std::make_pair("dalvik.vm.boot-dex2oat-cpu-set", "--cpu-set="),
+        std::make_pair("dalvik.vm.boot-dex2oat-threads", "-j"),
+    };
+    for (auto property_arg_pair : kPropertyArgPairs) {
+      auto [property, arg] = property_arg_pair;
+      std::string value = android::base::GetProperty(property, {});
+      if (!value.empty()) {
+        args.push_back(arg + value);
+      }
+    }
+  }
+
+  static void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& args) {
+    args.emplace_back("--generate-debug-info");
+    args.emplace_back("--generate-mini-debug-info");
+    args.emplace_back("--strip");
+  }
+
+  static void AddDex2OatInstructionSet(/*inout*/ std::vector<std::string> args,
+                                      InstructionSet isa) {
+    const char* isa_str = GetInstructionSetString(isa);
+    args.emplace_back(Concatenate({"--instruction-set=", isa_str}));
+  }
+
+  static void AddDex2OatProfileAndCompilerFilter(/*inout*/ std::vector<std::string>& args,
+                                                 const std::string& profile_file) {
+    if (OS::FileExists(profile_file.c_str(), /*check_file_type=*/true)) {
+      args.emplace_back(Concatenate({"--profile-file=", profile_file}));
+      args.emplace_back("--compiler-filter=speed-profile");
+    } else {
+      args.emplace_back("--compiler-filter=speed");
+    }
+  }
+
+  bool CheckSystemServerArtifactsAreUpToDate(bool on_system) const {
+    std::vector<std::string> classloader_context;
+    for (const std::string& jar_path : systemserver_compilable_jars_) {
+      std::vector<std::string> args;
+      args.emplace_back(config_.GetDexOptAnalyzer());
+      args.emplace_back("--dex-file=" + jar_path);
+
+      const std::string image_location = GetSystemServerImagePath(on_system, jar_path);
+
+      // odrefresh produces app-image files, but these are not guaranteed for those pre-installed
+      // on /system.
+      if (!on_system && !OS::FileExists(image_location.c_str(), true)) {
+        LOG(INFO) << "Missing image file: " << QuotePath(image_location);
+        return false;
+      }
+
+      // Generate set of artifacts that are output by compilation.
+      OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
+      if (!on_system) {
+        CHECK_EQ(artifacts.OatPath(),
+                 GetApexDataOdexFilename(jar_path, config_.GetSystemServerIsa()));
+        CHECK_EQ(artifacts.ImagePath(),
+                 GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "art"));
+        CHECK_EQ(artifacts.OatPath(),
+                 GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "odex"));
+        CHECK_EQ(artifacts.VdexPath(),
+                 GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "vdex"));
+      }
+
+      // Associate inputs and outputs with dexoptanalyzer arguments.
+      std::pair<const std::string, const char*> location_args[] = {
+          std::make_pair(artifacts.OatPath(), "--oat-fd="),
+          std::make_pair(artifacts.VdexPath(), "--vdex-fd="),
+          std::make_pair(jar_path, "--zip-fd=")
+      };
+
+      // Open file descriptors for dexoptanalyzer file inputs and add to the command-line.
+      std::vector<std::unique_ptr<File>> files;
+      for (const auto& location_arg : location_args) {
+        auto& [location, arg] = location_arg;
+        std::unique_ptr<File> file(OS::OpenFileForReading(location.c_str()));
+        if (file == nullptr) {
+          PLOG(ERROR) << "Failed to open \"" << location << "\"";
+          return false;
+        }
+        args.emplace_back(android::base::StringPrintf("%s%d", arg, file->Fd()));
+        files.emplace_back(file.release());
+      }
+
+      const std::string basename(android::base::Basename(jar_path));
+      const std::string root = GetAndroidRoot();
+      const std::string profile_file = Concatenate({root, "/framework/", basename, ".prof"});
+      if (OS::FileExists(profile_file.c_str())) {
+        args.emplace_back("--compiler-filter=speed-profile");
+      } else {
+        args.emplace_back("--compiler-filter=speed");
+      }
+
+      args.emplace_back(
+          Concatenate({"--image=", GetBootImage(), ":", GetBootImageExtensionImage(on_system)}));
+      args.emplace_back(
+          Concatenate({"--isa=", GetInstructionSetString(config_.GetSystemServerIsa())}));
+      args.emplace_back("--runtime-arg");
+      args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+      args.emplace_back(Concatenate(
+          {"--class-loader-context=PCL[", android::base::Join(classloader_context, ':'), "]"}));
+
+      classloader_context.emplace_back(jar_path);
+
+      LOG(INFO) << "Checking " << jar_path << ": " << android::base::Join(args, ' ');
+      std::string error_msg;
+      bool timed_out = false;
+      const time_t timeout = GetSubprocessTimeout();
+      const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+      if (dexoptanalyzer_result == -1) {
+        LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
+        if (timed_out) {
+          // TODO(oth): record metric for timeout.
+        }
+        return false;
+      }
+      LOG(INFO) << "dexoptanalyzer returned " << dexoptanalyzer_result;
+
+      bool unexpected_result = true;
+      switch (static_cast<dexoptanalyzer::ReturnCode>(dexoptanalyzer_result)) {
+        case art::dexoptanalyzer::ReturnCode::kNoDexOptNeeded:
+          unexpected_result = false;
+          break;
+
+        // Recompile needed
+        case art::dexoptanalyzer::ReturnCode::kDex2OatFromScratch:
+        case art::dexoptanalyzer::ReturnCode::kDex2OatForBootImageOat:
+        case art::dexoptanalyzer::ReturnCode::kDex2OatForFilterOat:
+        case art::dexoptanalyzer::ReturnCode::kDex2OatForBootImageOdex:
+        case art::dexoptanalyzer::ReturnCode::kDex2OatForFilterOdex:
+          return false;
+
+        // Unexpected issues (note no default-case here to catch missing enum values, but the
+        // return code from dexoptanalyzer may also be outside expected values, such as a
+        // process crash.
+        case art::dexoptanalyzer::ReturnCode::kFlattenClassLoaderContextSuccess:
+        case art::dexoptanalyzer::ReturnCode::kErrorInvalidArguments:
+        case art::dexoptanalyzer::ReturnCode::kErrorCannotCreateRuntime:
+        case art::dexoptanalyzer::ReturnCode::kErrorUnknownDexOptNeeded:
+          break;
+      }
+
+      if (unexpected_result) {
+        LOG(ERROR) << "Unexpected result from dexoptanalyzer: " << dexoptanalyzer_result;
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void RemoveSystemServerArtifactsFromData() const {
+    if (config_.GetDryRun()) {
+      LOG(INFO) << "Removal of system_server artifacts on /data skipped (dry-run).";
+      return;
+    }
+    for (const std::string& jar_path : systemserver_compilable_jars_) {
+      const std::string image_location =
+          GetSystemServerImagePath(/*on_system=*/false, jar_path);
+      const OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
+      LOG(INFO) << "Removing system_server artifacts on /data for " << QuotePath(jar_path);
+      RemoveArtifacts(artifacts);
+    }
+  }
+
+  // Check the validity of system server artifacts on both /system and /data.
+  // This method has the side-effect of removing system server artifacts on /data, if there are
+  // valid artifacts on /system, or if the artifacts on /data are not valid.
+  // Returns true if valid artifacts are found.
+  bool CheckSystemServerArtifactsAreUpToDate() const {
+    bool system_ok = CheckSystemServerArtifactsAreUpToDate(/*on_system=*/true);
+    LOG(INFO) << "system_server artifacts on /system are " << (system_ok ? "ok" : "stale");
+    bool data_ok = CheckSystemServerArtifactsAreUpToDate(/*on_system=*/false);
+    LOG(INFO) << "system_server artifacts on /data are " << (data_ok ? "ok" : "stale");
+    if (system_ok || !data_ok) {
+      // Artifacts on /system are usable or the ones on /data are not usable. Either way, remove
+      // the artifacts /data as they serve no purpose.
+      RemoveSystemServerArtifactsFromData();
+    }
+    return system_ok || data_ok;
+  }
+
+  // Check the validity of boot class path extension artifacts.
+  //
+  // Returns true if artifacts exist and are valid according to dexoptanalyzer.
+  bool CheckBootExtensionArtifactsAreUpToDate(const InstructionSet isa, bool on_system) const {
+    const std::string dex_file = boot_extension_compilable_jars_.front();
+    const std::string image_location = GetBootImageExtensionImage(on_system);
+
+    std::vector<std::string> args;
+    args.emplace_back(config_.GetDexOptAnalyzer());
+    args.emplace_back("--validate-bcp");
+    args.emplace_back(Concatenate({"--image=", GetBootImage(), ":", image_location}));
+    args.emplace_back(Concatenate({"--isa=", GetInstructionSetString(isa)}));
+    args.emplace_back("--runtime-arg");
+    args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+
+    LOG(INFO) << "Checking " << dex_file << ": " << android::base::Join(args, ' ');
+
+    std::string error_msg;
+    bool timed_out = false;
+    const time_t timeout = GetSubprocessTimeout();
+    const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+    if (dexoptanalyzer_result == -1) {
+      LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
+      if (timed_out) {
+        // TODO(oth): record metric for timeout.
+      }
+      return false;
+    }
+    auto rc = static_cast<dexoptanalyzer::ReturnCode>(dexoptanalyzer_result);
+    if (rc == dexoptanalyzer::ReturnCode::kNoDexOptNeeded) {
+      return true;
+    }
+    return false;
+  }
+
+  // Remove boot extension artifacts from /data.
+  void RemoveBootExtensionArtifactsFromData(InstructionSet isa) const {
+    if (config_.GetDryRun()) {
+      LOG(INFO) << "Removal of bcp extension artifacts on /data skipped (dry-run).";
+      return;
+    }
+    const std::string apexdata_image_location = GetBootImageExtensionImagePath(isa);
+    LOG(INFO) << "Removing boot class path artifacts on /data for "
+              << QuotePath(apexdata_image_location);
+    RemoveArtifacts(OdrArtifacts::ForBootImageExtension(apexdata_image_location));
+  }
+
+  // Check whether boot extension artifacts for `isa` are valid on system partition or in apexdata.
+  // This method has the side-effect of removing boot classpath extension artifacts on /data,
+  // if there are valid artifacts on /system, or if the artifacts on /data are not valid.
+  // Returns true if valid boot externsion artifacts are valid.
+  bool CheckBootExtensionArtifactsAreUpToDate(InstructionSet isa) const {
+    bool system_ok = CheckBootExtensionArtifactsAreUpToDate(isa, /*on_system=*/true);
+    LOG(INFO) << "Boot extension artifacts on /system are " << (system_ok ? "ok" : "stale");
+    bool data_ok = CheckBootExtensionArtifactsAreUpToDate(isa, /*on_system=*/false);
+    LOG(INFO) << "Boot extension artifacts on /data are " << (data_ok ? "ok" : "stale");
+    if (system_ok || !data_ok) {
+      // Artifacts on /system are usable or the ones on /data are not usable. Either way, remove
+      // the artifacts /data as they serve no purpose.
+      RemoveBootExtensionArtifactsFromData(isa);
+    }
+    return system_ok || data_ok;
+  }
+
+  static bool GetFreeSpace(const char* path, uint64_t* bytes) {
+    struct statvfs sv;
+    if (statvfs(path, &sv) != 0) {
+      PLOG(ERROR) << "statvfs '" << path << "'";
+      return false;
+    }
+    *bytes = sv.f_bfree * sv.f_bsize;
+    return true;
+  }
+
+  static bool GetUsedSpace(const char* path, uint64_t* bytes) {
+    *bytes = 0;
+
+    std::queue<std::string> unvisited;
+    unvisited.push(path);
+    while (!unvisited.empty()) {
+      std::string current = unvisited.front();
+      std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(current.c_str()), closedir);
+      for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+        if (entity->d_name[0] == '.') {
+          continue;
+        }
+        std::string entity_name = Concatenate({current, "/", entity->d_name});
+        if (entity->d_type == DT_DIR) {
+          unvisited.push(entity_name.c_str());
+        } else if (entity->d_type == DT_REG) {
+          // RoundUp file size to number of blocks.
+          *bytes += RoundUp(OS::GetFileSizeBytes(entity_name.c_str()), 512);
+        } else {
+          LOG(FATAL) << "Unsupported directory entry type: " << static_cast<int>(entity->d_type);
+        }
+      }
+      unvisited.pop();
+    }
+    return true;
+  }
+
+  static void ReportSpace() {
+    uint64_t bytes;
+    std::string data_dir = GetArtApexData();
+    if (GetUsedSpace(data_dir.c_str(), &bytes)) {
+      LOG(INFO) << "Used space " << bytes << " bytes.";
+    }
+    if (GetFreeSpace(data_dir.c_str(), &bytes)) {
+      LOG(INFO) << "Available space " << bytes << " bytes.";
+    }
+  }
+
+  // Checks all artifacts are up-to-date.
+  //
+  // Returns ExitCode::kOkay if artifacts are up-to-date, ExitCode::kCompilationRequired otherwise.
+  //
+  // NB This is the main function used by the --check command-line option. When invoked with
+  // --compile, we only recompile the out-of-date artifacts, not all (see `Odrefresh::Compile`).
+  ExitCode CheckArtifactsAreUpToDate() {
+    ExitCode exit_code = ExitCode::kOkay;
+    for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
+      if (!CheckBootExtensionArtifactsAreUpToDate(isa)) {
+        exit_code = ExitCode::kCompilationRequired;
+      }
+    }
+    if (!CheckSystemServerArtifactsAreUpToDate()) {
+      exit_code = ExitCode::kCompilationRequired;
+    }
+    return exit_code;
+  }
+
+  // Callback for use with nftw(3) to assist with clearing files and sub-directories.
+  // This method removes files and directories below the top-level directory passed to nftw().
+  static int NftwUnlinkRemoveCallback(const char* fpath,
+                                      const struct stat* sb ATTRIBUTE_UNUSED,
+                                      int typeflag,
+                                      struct FTW* ftwbuf) {
+    switch (typeflag) {
+      case FTW_F:
+      case FTW_SL:
+      case FTW_SLN:
+        if (unlink(fpath)) {
+          PLOG(FATAL) << "Failed unlink(\"" << fpath << "\")";
+        }
+        return 0;
+
+      case FTW_DP:
+        if (ftwbuf->level == 0) {
+          return 0;
+        }
+        if (rmdir(fpath) != 0) {
+          PLOG(FATAL) << "Failed rmdir(\"" << fpath << "\")";
+        }
+        return 0;
+
+      case FTW_DNR:
+        LOG(FATAL) << "Inaccessible directory \"" << fpath << "\"";
+        return -1;
+
+      case FTW_NS:
+        LOG(FATAL) << "Failed stat() \"" << fpath << "\"";
+        return -1;
+
+      default:
+        LOG(FATAL) << "Unexpected typeflag " << typeflag << "for \"" << fpath << "\"";
+        return -1;
+    }
+  }
+
+  void RemoveArtifactsOrDie() const {
+    // Remove everything under ArtApexDataDir
+    std::string data_dir = GetArtApexData();
+
+    // Perform depth first traversal removing artifacts.
+    nftw(data_dir.c_str(), NftwUnlinkRemoveCallback, 1, FTW_DEPTH | FTW_MOUNT);
+  }
+
+  void RemoveArtifacts(const OdrArtifacts& artifacts) const {
+    for (const auto& location :
+         {artifacts.ImagePath(), artifacts.OatPath(), artifacts.VdexPath()}) {
+      if (OS::FileExists(location.c_str()) && TEMP_FAILURE_RETRY(unlink(location.c_str())) != 0) {
+        PLOG(ERROR) << "Failed to remove: " << QuotePath(location);
+      }
+    }
+  }
+
+  void RemoveStagingFilesOrDie(const char* staging_dir) const {
+    if (OS::DirectoryExists(staging_dir)) {
+      nftw(staging_dir, NftwUnlinkRemoveCallback, 1, FTW_DEPTH | FTW_MOUNT);
+    }
+  }
+
+  // Create all directory and all required parents.
+  static void EnsureDirectoryExists(const std::string& absolute_path) {
+    CHECK(absolute_path.size() > 0 && absolute_path[0] == '/');
+    std::string path;
+    for (const std::string& directory : android::base::Split(absolute_path, "/")) {
+      path.append("/").append(directory);
+      if (!OS::DirectoryExists(path.c_str())) {
+        if (mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0) {
+          PLOG(FATAL) << "Could not create directory: " << path;
+        }
+      }
+    }
+  }
+
+  static std::string GetBootImage() {
+    // Typically "/apex/com.android.art/javalib/boot.art".
+    return GetArtRoot() + "/javalib/boot.art";
+  }
+
+  std::string GetBootImageExtensionImage(bool on_system) const {
+    CHECK(!boot_extension_compilable_jars_.empty());
+    const std::string leading_jar = boot_extension_compilable_jars_[0];
+    if (on_system) {
+      const std::string jar_name = android::base::Basename(leading_jar);
+      const std::string image_name = ReplaceFileExtension(jar_name, "art");
+      // Typically "/system/framework/boot-framework.art".
+      return Concatenate({GetAndroidRoot(), "/framework/boot-", image_name});
+    } else {
+      // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework.art".
+      return GetApexDataBootImage(leading_jar);
+    }
+  }
+
+  std::string GetBootImageExtensionImagePath(const InstructionSet isa) const {
+    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot-framework.art".
+    return GetSystemImageFilename(GetBootImageExtensionImage(/*on_system=*/false).c_str(), isa);
+  }
+
+  std::string GetSystemServerImagePath(bool on_system, const std::string& jar_path) const {
+    if (on_system) {
+      const std::string jar_name = android::base::Basename(jar_path);
+      const std::string image_name = ReplaceFileExtension(jar_name, "art");
+      const char* isa_str = GetInstructionSetString(config_.GetSystemServerIsa());
+      // Typically "/system/framework/oat/<isa>/services.art".
+      return Concatenate({GetAndroidRoot(), "/framework/oat/", isa_str, "/", image_name});
+    } else {
+      // Typically
+      // "/data/misc/apexdata/.../dalvik-cache/<isa>/system@framework@services.jar@classes.art".
+      const std::string image = GetApexDataImage(jar_path.c_str());
+      return GetSystemImageFilename(image.c_str(), config_.GetSystemServerIsa());
+    }
+  }
+
+  std::string GetStagingLocation(const std::string& staging_dir, const std::string& path) const {
+    return Concatenate({staging_dir, "/", android::base::Basename(path)});
+  }
+
+  bool CompileBootExtensionArtifacts(const InstructionSet isa,
+                                     const std::string& staging_dir,
+                                     std::string* error_msg) const {
+    std::vector<std::string> args;
+    args.push_back(config_.GetDex2Oat());
+
+    AddDex2OatCommonOptions(args);
+    AddDex2OatConcurrencyArguments(args);
+    AddDex2OatDebugInfo(args);
+    AddDex2OatInstructionSet(args, isa);
+    const std::string boot_profile_file(GetAndroidRoot() + "/etc/boot-image.prof");
+    AddDex2OatProfileAndCompilerFilter(args, boot_profile_file);
+
+    // Compile as a single image for fewer files and slightly less memory overhead.
+    args.emplace_back("--single-image");
+
+    // Set boot-image and expectation of compiling boot classpath extensions.
+    args.emplace_back("--boot-image=" + GetBootImage());
+
+    const std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects");
+    if (OS::FileExists(dirty_image_objects_file.c_str())) {
+      args.emplace_back(Concatenate({"--dirty-image-objects=", dirty_image_objects_file}));
+    } else {
+      LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file);
+    }
+
+    // Add boot extensions to compile.
+    for (const std::string& component : boot_extension_compilable_jars_) {
+      args.emplace_back("--dex-file=" + component);
+    }
+
+    args.emplace_back("--runtime-arg");
+    args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+
+    const std::string image_location = GetBootImageExtensionImagePath(isa);
+    const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(image_location);
+    CHECK_EQ(GetApexDataOatFilename(boot_extension_compilable_jars_.front().c_str(), isa),
+             artifacts.OatPath());
+
+    args.emplace_back("--oat-location=" + artifacts.OatPath());
+    const std::pair<const std::string, const char*> location_kind_pairs[] = {
+        std::make_pair(artifacts.ImagePath(), "image"),
+        std::make_pair(artifacts.OatPath(), "oat"),
+        std::make_pair(artifacts.VdexPath(), "output-vdex")
+    };
+
+    std::vector<std::unique_ptr<File>> staging_files;
+    for (const auto& location_kind_pair : location_kind_pairs) {
+      auto& [location, kind] = location_kind_pair;
+      const std::string staging_location = GetStagingLocation(staging_dir, location);
+      std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
+      if (staging_file == nullptr) {
+        PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+        EraseFiles(staging_files);
+        return false;
+      }
+      args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+      staging_files.emplace_back(std::move(staging_file));
+    }
+
+    const std::string install_location = android::base::Dirname(image_location);
+    EnsureDirectoryExists(install_location);
+
+    const time_t timeout = GetSubprocessTimeout();
+    const std::string cmd_line = android::base::Join(args, ' ');
+    LOG(INFO) << "Compiling boot extensions (" << isa << "): " << cmd_line
+              << " [timeout " << timeout << "s]";
+    if (config_.GetDryRun()) {
+      LOG(INFO) << "Compilation skipped (dry-run).";
+      return true;
+    }
+
+    bool timed_out = false;
+    if (ExecAndReturnCode(args, timeout, &timed_out, error_msg) != 0) {
+      if (timed_out) {
+        // TODO(oth): record timeout event for compiling boot extension
+      }
+      EraseFiles(staging_files);
+      return false;
+    }
+
+    if (!MoveOrEraseFiles(staging_files, install_location)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  bool CompileSystemServerArtifacts(const std::string& staging_dir, std::string* error_msg) const {
+    std::vector<std::string> classloader_context;
+
+    const std::string dex2oat = config_.GetDex2Oat();
+    const InstructionSet isa = config_.GetSystemServerIsa();
+    for (const std::string& jar : systemserver_compilable_jars_) {
+      std::vector<std::string> args;
+      args.emplace_back(dex2oat);
+      args.emplace_back("--dex-file=" + jar);
+
+      AddDex2OatCommonOptions(args);
+      AddDex2OatConcurrencyArguments(args);
+      AddDex2OatDebugInfo(args);
+      AddDex2OatInstructionSet(args, isa);
+      const std::string jar_name(android::base::Basename(jar));
+      const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"});
+      AddDex2OatProfileAndCompilerFilter(args, profile);
+
+      const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar);
+      const std::string install_location = android::base::Dirname(image_location);
+      if (classloader_context.empty()) {
+        // All images are in the same directory, we only need to check on the first iteration.
+        EnsureDirectoryExists(install_location);
+      }
+
+      OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
+      CHECK_EQ(artifacts.OatPath(), GetApexDataOdexFilename(jar.c_str(), isa));
+
+      const std::pair<const std::string, const char*> location_kind_pairs[] = {
+          std::make_pair(artifacts.ImagePath(), "app-image"),
+          std::make_pair(artifacts.OatPath(), "oat"),
+          std::make_pair(artifacts.VdexPath(), "output-vdex")
+      };
+
+      std::vector<std::unique_ptr<File>> staging_files;
+      for (const auto& location_kind_pair : location_kind_pairs) {
+        auto& [location, kind] = location_kind_pair;
+        const std::string staging_location = GetStagingLocation(staging_dir, location);
+        std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
+        if (staging_file == nullptr) {
+          PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+          EraseFiles(staging_files);
+          return false;
+        }
+        args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+        staging_files.emplace_back(std::move(staging_file));
+      }
+      args.emplace_back("--oat-location=" + artifacts.OatPath());
+
+      if (!config_.GetUpdatableBcpPackagesFile().empty()) {
+        args.emplace_back("--updatable-bcp-packages-file=" + config_.GetUpdatableBcpPackagesFile());
+      }
+
+      args.emplace_back("--runtime-arg");
+      args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+      const std::string context_path = android::base::Join(classloader_context, ':');
+      args.emplace_back(Concatenate({"--class-loader-context=PCL[", context_path, "]"}));
+      const std::string extension_image = GetBootImageExtensionImage(/*on_system=*/false);
+      args.emplace_back(Concatenate({"--boot-image=", GetBootImage(), ":", extension_image}));
+
+      const time_t timeout = GetSubprocessTimeout();
+      const std::string cmd_line = android::base::Join(args, ' ');
+      LOG(INFO) << "Compiling " << jar << ": " << cmd_line << " [timeout " << timeout << "s]";
+      if (config_.GetDryRun()) {
+        LOG(INFO) << "Compilation skipped (dry-run).";
+        return true;
+      }
+
+      bool timed_out = false;
+      if (!Exec(args, error_msg)) {
+        if (timed_out) {
+          // TODO(oth): record timeout event for compiling boot extension
+        }
+        EraseFiles(staging_files);
+        return false;
+      }
+
+      if (!MoveOrEraseFiles(staging_files, install_location)) {
+        return false;
+      }
+
+      classloader_context.emplace_back(jar);
+    }
+
+    return true;
+  }
+
+  ExitCode Compile(bool force_compile) const {
+    ReportSpace();  // TODO(oth): Factor available space into compilation logic.
+
+    // Clean-up existing files.
+    if (force_compile) {
+      RemoveArtifactsOrDie();
+    }
+
+    // Create staging area and assign label for generating compilation artifacts.
+    const char* staging_dir;
+    if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) {
+      return ExitCode::kCompilationFailed;
+    }
+
+    std::string error_msg;
+
+    for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
+      if (force_compile || !CheckBootExtensionArtifactsAreUpToDate(isa)) {
+        if (!CompileBootExtensionArtifacts(isa, staging_dir, &error_msg)) {
+          LOG(ERROR) << "BCP compilation failed: " << error_msg;
+          RemoveStagingFilesOrDie(staging_dir);
+          return ExitCode::kCompilationFailed;
+        }
+      }
+    }
+
+    if (force_compile || !CheckSystemServerArtifactsAreUpToDate()) {
+      if (!CompileSystemServerArtifacts(staging_dir, &error_msg)) {
+        LOG(ERROR) << "system_server compilation failed: " << error_msg;
+        RemoveStagingFilesOrDie(staging_dir);
+        return ExitCode::kCompilationFailed;
+      }
+    }
+
+    return ExitCode::kOkay;
+  }
+
+  static bool ArgumentMatches(std::string_view argument,
+                              std::string_view prefix,
+                              std::string* value) {
+    if (StartsWith(argument, prefix)) {
+      *value = std::string(argument.substr(prefix.size()));
+      return true;
+    }
+    return false;
+  }
+
+  static bool ArgumentEquals(std::string_view argument, std::string_view expected) {
+    return argument == expected;
+  }
+
+  static bool InitializeCommonConfig(std::string_view argument, OdrConfig* config) {
+    static constexpr std::string_view kDryRunArgument{"--dry-run"};
+    if (ArgumentEquals(argument, kDryRunArgument)) {
+      config->SetDryRun();
+      return true;
+    }
+    return false;
+  }
+
+  static int InitializeHostConfig(int argc, const char** argv, OdrConfig* config) {
+    __android_log_set_logger(__android_log_stderr_logger);
+
+    std::string current_binary;
+    if (argv[0][0] == '/') {
+      current_binary = argv[0];
+    } else {
+      std::vector<char> buf(PATH_MAX);
+      if (getcwd(buf.data(), buf.size()) == nullptr) {
+        PLOG(FATAL) << "Failed getwd()";
+      }
+      current_binary = Concatenate({buf.data(), "/", argv[0]});
+    }
+    config->SetArtBinDir(android::base::Dirname(current_binary));
+
+    int n = 1;
+    for (; n < argc - 1; ++n) {
+      const char* arg = argv[n];
+      std::string value;
+      if (ArgumentMatches(arg, "--android-root=", &value)) {
+        setenv("ANDROID_ROOT", value.c_str(), 1);
+      } else if (ArgumentMatches(arg, "--android-art-root=", &value)) {
+        setenv("ANDROID_ART_ROOT", value.c_str(), 1);
+      } else if (ArgumentMatches(arg, "--apex-info-list=", &value)) {
+        config->SetApexInfoListFile(value);
+      } else if (ArgumentMatches(arg, "--art-apex-data=", &value)) {
+        setenv("ART_APEX_DATA", value.c_str(), 1);
+      } else if (ArgumentMatches(arg, "--dex2oat-bootclasspath=", &value)) {
+        config->SetDex2oatBootclasspath(value);
+      } else if (ArgumentMatches(arg, "--isa=", &value)) {
+        config->SetIsa(GetInstructionSetFromString(value.c_str()));
+      } else if (ArgumentMatches(arg, "--system-server-classpath=", &value)) {
+        config->SetSystemServerClasspath(arg);
+      } else if (ArgumentMatches(arg, "--updatable-bcp-packages-file=", &value)) {
+        config->SetUpdatableBcpPackagesFile(value);
+      } else if (ArgumentMatches(arg, "--zygote-arch=", &value)) {
+        ZygoteKind zygote_kind;
+        if (!ParseZygoteKind(value.c_str(), &zygote_kind)) {
+          ArgumentError("Unrecognized zygote kind: '%s'", value.c_str());
+        }
+        config->SetZygoteKind(zygote_kind);
+      } else if (!InitializeCommonConfig(arg, config)) {
+        UsageError("Unrecognized argument: '%s'", arg);
+      }
+    }
+    return n;
+  }
+
+  static int InitializeTargetConfig(int argc, const char** argv, OdrConfig* config) {
+    config->SetApexInfoListFile("/apex/apex-info-list.xml");
+    config->SetArtBinDir(GetArtBinDir());
+    config->SetDex2oatBootclasspath(getenv("DEX2OATBOOTCLASSPATH"));
+    config->SetSystemServerClasspath(getenv("SYSTEMSERVERCLASSPATH"));
+    config->SetIsa(kRuntimeISA);
+
+    const std::string zygote = android::base::GetProperty("ro.zygote", {});
+    ZygoteKind zygote_kind;
+    if (!ParseZygoteKind(zygote.c_str(), &zygote_kind)) {
+      LOG(FATAL) << "Unknown zygote: " << QuotePath(zygote);
+    }
+    config->SetZygoteKind(zygote_kind);
+
+    const std::string updatable_packages =
+        android::base::GetProperty("dalvik.vm.dex2oat-updatable-bcp-packages-file", {});
+    config->SetUpdatableBcpPackagesFile(updatable_packages);
+
+    int n = 1;
+    for (; n < argc - 1; ++n) {
+      if (!InitializeCommonConfig(argv[n], config)) {
+        UsageError("Unrecognized argument: '%s'", argv[n]);
+      }
+    }
+    return n;
+  }
+
+  static int InitializeConfig(int argc, const char** argv, OdrConfig* config) {
+    if (kIsTargetBuild) {
+      return InitializeTargetConfig(argc, argv, config);
+    } else {
+      return InitializeHostConfig(argc, argv, config);
+    }
+  }
+
+  static int main(int argc, const char** argv) {
+    OdrConfig config(argv[0]);
+
+    int n = InitializeConfig(argc, argv, &config);
+    argv += n;
+    argc -= n;
+
+    if (argc != 1) {
+      UsageError("Expected 1 argument, but have %d.", argc);
+    }
+
+    OnDeviceRefresh odr(config);
+    for (int i = 0; i < argc; ++i) {
+      std::string_view action(argv[i]);
+      if (action == "--check") {
+        return odr.CheckArtifactsAreUpToDate();
+      } else if (action == "--compile") {
+        return odr.Compile(/*force_compile=*/false);
+      } else if (action == "--force-compile") {
+        return odr.Compile(/*force_compile=*/true);
+      } else if (action == "--help") {
+        UsageHelp(argv[0]);
+      } else {
+        UsageError("Unknown argument: ", argv[i]);
+      }
+    }
+    return ExitCode::kOkay;
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(OnDeviceRefresh);
+};
+
+}  // namespace odrefresh
+}  // namespace art
+
+int main(int argc, const char** argv) {
+  return art::odrefresh::OnDeviceRefresh::main(argc, argv);
+}
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
new file mode 100644
index 0000000..a09accb
--- /dev/null
+++ b/odrefresh/odrefresh_test.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odrefresh/odrefresh.h"
+
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace odrefresh {
+
+TEST(OdRefreshTest, OdrefreshArtifactDirectory) {
+    // odrefresh.h defines kOdrefreshArtifactDirectory for external callers of odrefresh. This is
+    // where compilation artifacts end up.
+    ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+    EXPECT_EQ(kOdrefreshArtifactDirectory, GetArtApexData() + "/dalvik-cache");
+}
+
+}  // namespace odrefresh
+}  // namespace art
diff --git a/tools/Android.bp b/tools/Android.bp
index 9dba79a..a233bbe 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -71,12 +71,6 @@
     },
 }
 
-sh_binary {
-    name: "compile_bcp.sh",
-    host_supported: false,
-    src: "compile_bcp.sh",
-}
-
 python_binary_host {
     name: "art-run-test-checker",
     srcs: [
diff --git a/tools/compile_bcp.sh b/tools/compile_bcp.sh
deleted file mode 100755
index 2fcee13..0000000
--- a/tools/compile_bcp.sh
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/system/bin/sh
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Script to recompile the non-APEX jars in the boot class path and
-# system server jars for on-device signing following an ART Module
-# update.
-#
-# For testing purposes, prepare Android device using:
-#
-#   $ adb root
-#   $ adb shell setenforce 0
-#
-# TODO: this is script is currently for proof-of-concept. It does not
-# respect many of the available dalvik.vm properties, e.g. affinity,
-# threads, etc nor dalvik.vm.dex2oat-resolve-startup-strings.
-#
-# TODO: some of the system server jars seem to be installed on device
-# for 32-bit and 64-bit, but services.jar artifacts are just one arch.
-# Not sure why both flavors are present. Does this script need to generate
-# both?
-#
-# TODO: Logging failure / error handling.
-
-# Output directory for generated files. Real location is TBD.
-output=$PWD/out
-
-function mkdir_clean() {
-  # mkdir_clean <dir_path>
-  local dir_path=$1
-  rm -rf "${dir_path}"
-  mkdir -p "${dir_path}"
-}
-
-# Determine candidate architectures for this device.
-case `getprop ro.product.cpu.abi` in
-  arm*)
-    arch32=arm
-    arch64=arm64
-    ;;
-  x86*)
-    arch32=x86
-    arch64=x86_64
-    ;;
-  *)
-    echo "Unknown abi"
-    exit -1
-esac
-
-# Determine architectures to use for Zygote. system_server runs under the primary
-# architecture. Prefer dex2oat64 if device supports 64-bits.
-case $(getprop ro.zygote) in
-  zygote32)
-    # The primary architecture is 32-bits.
-    archs="$arch32"
-    systemserver_arch="$arch32"
-    dex2oat=/apex/com.android.art/bin/dex2oat32
-    ;;
-  zygote32_64)
-    # The primary architecture is 32-bits and the secondary is 64-bits.
-    archs="$arch32 $arch64"
-    systemserver_arch="$arch32"
-    dex2oat=/apex/com.android.art/bin/dex2oat64
-    ;;
-  zygote64_32)
-    # The primary architecture is 64-bits and the secondary is 32-bits.
-    archs="$arch32 $arch64"
-    systemserver_arch="$arch64"
-    dex2oat=/apex/com.android.art/bin/dex2oat64
-    ;;
-  zygote64)
-    # Primary architecture is 64-bits.
-    archs="$arch64"
-    systemserver_arch="$arch64"
-    dex2oat=/apex/com.android.art/bin/dex2oat64
-    ;;
-  *)
-    echo "Unknown ro.zygote value"
-    exit -1
-    ;;
-esac
-
-# Determine which boot class path jars to compile.
-device_bcp_list=""
-device_bcp_dex_files=""
-for jar in ${DEX2OATBOOTCLASSPATH//:/ }; do
-  if [[ ${jar} = *com.android.art* ]]; then
-    continue
-  fi
-  device_bcp_list="${device_bcp_list}${device_bcp_list:+:}${jar}"
-  device_bcp_dex_files="$device_bcp_dex_files --dex-file=${jar}"
-done
-
-# Compile the boot class path elements that are present on device.
-for arch in ${archs}; do
-  arch_output="${output}/$arch"
-  mkdir_clean "${arch_output}"
-
-  invocation_dir="${output}/$arch"
-  mkdir_clean "${invocation_dir}"
-
-  echo "Compiling ${device_bcp_list} ($arch)"
-  ${dex2oat} --avoid-storing-invocation \
-    --compiler-filter=speed-profile \
-    --profile-file=/system/etc/boot-image.prof \
-    --dirty-image-objects=/system/etc/dirty-image-objects \
-    --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
-    --boot-image=/apex/com.android.art/javalib/boot.art \
-    ${device_bcp_dex_files} \
-    --generate-debug-info \
-    --image-format=lz4hc \
-    --strip \
-    --oat-file=${arch_output}/boot.oat \
-    --image=${arch_output}/boot.art \
-    --android-root=out/empty \
-    --abort-on-hard-verifier-error \
-    --instruction-set=$arch \
-    --generate-mini-debug-info
-done
-
-# Compile system_server and related jars.
-classloader_context=""
-for jar in ${SYSTEMSERVERCLASSPATH//:/ }; do
-  # Skip class path components in APEXes
-  if [[ ${jar} = "/apex"* ]]; then
-    continue
-  fi
-
-  # Add profile if it exists. Only services.jar has a profile in AOSP.
-  stem=$(basename $jar .jar)
-  profile_file=/system/framework/${stem}.jar.prof
-  if [ -f "${profile_file}" ] ; then
-    profile_arg=--profile-file=${profile_file}
-    filter=speed-profile
-  else
-    profile_arg=""
-    filter=speed
-  fi
-
-  # Add updatable boot class path packages file if there is a property for it.
-  updatable_bcp_file=$(getprop dalvik.vm.dex2oat-updatable-bcp-packages-file)
-  if [ "${updatable_bcp_file}" -a -f "${updatable_bcp_file}" ] ; then
-    updatable_bcp_file_arg="--updatable-bcp-packages-file=${updatable_bcp_file}"
-  fi
-
-  echo "Compiling ${jar} (${systemserver_arch} ${filter} PCL[${classloader_context}])"
-  $dex2oat --avoid-storing-invocation \
-           --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
-           --class-loader-context=PCL[${classloader_context}] \
-           --boot-image=/apex/com.android.art/javalib/boot.art:${output}/boot-framework.art \
-           --dex-file=${jar} \
-           --oat-file=${arch_output}/${stem}.odex \
-           --app-image-file=${arch_output}/${stem}.art \
-           --android-root=out/empty \
-           --instruction-set=${systemserver_arch} \
-           --abort-on-hard-verifier-error \
-           --compiler-filter=$filter \
-           --generate-mini-debug-info \
-           --compilation-reason=prebuilt \
-           --image-format=lz4 \
-           --resolve-startup-const-strings=true \
-           ${profile_arg} \
-           ${updatable_bcp_file_arg}
-
-  classloader_context=${classloader_context}${classloader_context:+:}${jar}
-done