Walk up the super chain to find an IMT to share. am: fa26b96f2f am: e661dadf16 am: 4064189043

Original change: https://android-review.googlesource.com/c/platform/art/+/2407273

Change-Id: I369542026cd87e5654c8c1a3ba40b6d71d4a8d73
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 86a8888..d01c739 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1309,6 +1309,9 @@
       ]
     },
     {
+      "name": "art_standalone_dex2oat_tests[com.google.android.art.apex]"
+    },
+    {
       "name": "art_standalone_dexdump_tests[com.google.android.art.apex]"
     },
     {
@@ -1318,6 +1321,9 @@
       "name": "art_standalone_dexoptanalyzer_tests[com.google.android.art.apex]"
     },
     {
+      "name": "art_standalone_libartbase_tests[com.google.android.art.apex]"
+    },
+    {
       "name": "art_standalone_libartpalette_tests[com.google.android.art.apex]"
     },
     {
@@ -2676,6 +2682,9 @@
       "name": "art_standalone_compiler_tests"
     },
     {
+      "name": "art_standalone_dex2oat_tests"
+    },
+    {
       "name": "art_standalone_dexdump_tests"
     },
     {
@@ -2685,6 +2694,9 @@
       "name": "art_standalone_dexoptanalyzer_tests"
     },
     {
+      "name": "art_standalone_libartbase_tests"
+    },
+    {
       "name": "art_standalone_libartpalette_tests"
     },
     {
diff --git a/artd/Android.bp b/artd/Android.bp
index c0ffd0a..e0674f0 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -27,14 +27,21 @@
     defaults: ["art_defaults"],
     srcs: [
         "artd.cc",
+        "file_utils.cc",
+        "path_utils.cc",
+    ],
+    header_libs: [
+        "profman_headers",
     ],
     shared_libs: [
         "libarttools",
         "libbase",
         "libbinder_ndk",
+        "libselinux",
     ],
     static_libs: [
         "artd-aidl-ndk",
+        "libc++fs",
     ],
 }
 
@@ -45,6 +52,7 @@
         "artd_main.cc",
     ],
     shared_libs: [
+        "libart",
         "libartbase",
     ],
     apex_available: [
@@ -63,8 +71,17 @@
 art_cc_defaults {
     name: "art_artd_tests_defaults",
     defaults: ["artd_defaults"],
+    static_libs: [
+        "libgmock",
+    ],
     srcs: [
         "artd_test.cc",
+        "file_utils_test.cc",
+        "path_utils_test.cc",
+    ],
+    data: [
+        ":art-gtest-jars-Main",
+        ":art-gtest-jars-Nested",
     ],
 }
 
@@ -90,4 +107,5 @@
         "art_standalone_gtest_defaults",
         "art_artd_tests_defaults",
     ],
+    test_config_template: "art_standalone_artd_tests.xml",
 }
diff --git a/artd/art_standalone_artd_tests.xml b/artd/art_standalone_artd_tests.xml
new file mode 100644
index 0000000..9125046
--- /dev/null
+++ b/artd/art_standalone_artd_tests.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Main.jar" />
+        <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Nested.jar" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+    </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/artd/artd.cc b/artd/artd.cc
index 27a609d..ef161c0 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -16,16 +16,56 @@
 
 #include "artd.h"
 
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
+#include <climits>
+#include <csignal>
+#include <cstdint>
+#include <cstring>
+#include <filesystem>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <ostream>
 #include <string>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+#include <vector>
 
 #include "aidl/com/android/server/art/BnArtd.h"
+#include "aidl/com/android/server/art/DexoptTrigger.h"
+#include "aidl/com/android/server/art/IArtdCancellationSignal.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
 #include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
 #include "android/binder_manager.h"
 #include "android/binder_process.h"
+#include "base/compiler_filter.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/os.h"
+#include "exec_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+#include "oat_file_assistant_context.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "selinux/android.h"
+#include "tools/cmdline_builder.h"
 #include "tools/tools.h"
 
 namespace art {
@@ -33,19 +73,942 @@
 
 namespace {
 
+using ::aidl::com::android::server::art::ArtdDexoptResult;
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::DexoptTrigger;
+using ::aidl::com::android::server::art::FileVisibility;
+using ::aidl::com::android::server::art::FsPermission;
+using ::aidl::com::android::server::art::GetDexoptNeededResult;
+using ::aidl::com::android::server::art::GetDexoptStatusResult;
+using ::aidl::com::android::server::art::IArtdCancellationSignal;
+using ::aidl::com::android::server::art::MergeProfileOptions;
+using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
+using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Dirname;
 using ::android::base::Error;
+using ::android::base::Join;
+using ::android::base::make_scope_guard;
+using ::android::base::ReadFileToString;
 using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringReplace;
+using ::android::base::WriteStringToFd;
+using ::art::tools::CmdlineBuilder;
 using ::ndk::ScopedAStatus;
 
+using ::fmt::literals::operator""_format;  // NOLINT
+
+using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
 constexpr const char* kServiceName = "artd";
+constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal";
+
+// Timeout for short operations, such as merging profiles.
+constexpr int kShortTimeoutSec = 60;  // 1 minute.
+
+// Timeout for long operations, such as compilation. We set it to be smaller than the Package
+// Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation
+// is called from the Package Manager's thread handler, it will be aborted before that watchdog
+// would take down the system server.
+constexpr int kLongTimeoutSec = 570;  // 9.5 minutes.
+
+std::optional<int64_t> GetSize(std::string_view path) {
+  std::error_code ec;
+  int64_t size = std::filesystem::file_size(path, ec);
+  if (ec) {
+    // It is okay if the file does not exist. We don't have to log it.
+    if (ec.value() != ENOENT) {
+      LOG(ERROR) << "Failed to get the file size of '{}': {}"_format(path, ec.message());
+    }
+    return std::nullopt;
+  }
+  return size;
+}
+
+// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
+// error occurs.
+int64_t GetSizeAndDeleteFile(const std::string& path) {
+  std::optional<int64_t> size = GetSize(path);
+  if (!size.has_value()) {
+    return 0;
+  }
+
+  std::error_code ec;
+  if (!std::filesystem::remove(path, ec)) {
+    LOG(ERROR) << "Failed to remove '{}': {}"_format(path, ec.message());
+    return 0;
+  }
+
+  return size.value();
+}
+
+std::string EscapeErrorMessage(const std::string& message) {
+  return StringReplace(message, std::string("\0", /*n=*/1), "\\0", /*all=*/true);
+}
+
+// Indicates an error that should never happen (e.g., illegal arguments passed by service-art
+// internally). System server should crash if this kind of error happens.
+ScopedAStatus Fatal(const std::string& message) {
+  return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
+                                                     EscapeErrorMessage(message).c_str());
+}
+
+// Indicates an error that service-art should handle (e.g., I/O errors, sub-process crashes).
+// The scope of the error depends on the function that throws it, so service-art should catch the
+// error at every call site and take different actions.
+// Ideally, this should be a checked exception or an additional return value that forces service-art
+// to handle it, but `ServiceSpecificException` (a separate runtime exception type) is the best
+// approximate we have given the limitation of Java and Binder.
+ScopedAStatus NonFatal(const std::string& message) {
+  constexpr int32_t kArtdNonFatalErrorCode = 1;
+  return ScopedAStatus::fromServiceSpecificErrorWithMessage(kArtdNonFatalErrorCode,
+                                                            EscapeErrorMessage(message).c_str());
+}
+
+Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) {
+  CompilerFilter::Filter compiler_filter;
+  if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) {
+    return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str);
+  }
+  return compiler_filter;
+}
+
+OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int32_t aidl_value) {
+  OatFileAssistant::DexOptTrigger trigger{};
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) {
+    trigger.targetFilterIsBetter = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) {
+    trigger.targetFilterIsSame = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) {
+    trigger.targetFilterIsWorse = true;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) {
+    trigger.primaryBootImageBecomesUsable = true;
+  }
+  return trigger;
+}
+
+ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) {
+  switch (location) {
+    case OatFileAssistant::Location::kLocationNoneOrError:
+      return ArtifactsLocation::NONE_OR_ERROR;
+    case OatFileAssistant::Location::kLocationOat:
+      return ArtifactsLocation::DALVIK_CACHE;
+    case OatFileAssistant::Location::kLocationOdex:
+      return ArtifactsLocation::NEXT_TO_DEX;
+    case OatFileAssistant::Location::kLocationDm:
+      return ArtifactsLocation::DM;
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected Location " << location;
+}
+
+Result<void> PrepareArtifactsDir(const std::string& path, const FsPermission& fs_permission) {
+  std::error_code ec;
+  bool created = std::filesystem::create_directory(path, ec);
+  if (ec) {
+    return Errorf("Failed to create directory '{}': {}", path, ec.message());
+  }
+
+  auto cleanup = make_scope_guard([&] {
+    if (created) {
+      std::filesystem::remove(path, ec);
+    }
+  });
+
+  if (chmod(path.c_str(), DirFsPermissionToMode(fs_permission)) != 0) {
+    return ErrnoErrorf("Failed to chmod directory '{}'", path);
+  }
+  OR_RETURN(Chown(path, fs_permission));
+
+  cleanup.Disable();
+  return {};
+}
+
+Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts,
+                                  /*out*/ std::string* oat_dir_path) {
+  if (output_artifacts.artifactsPath.isInDalvikCache) {
+    return {};
+  }
+
+  std::filesystem::path oat_path(OR_RETURN(BuildOatPath(output_artifacts.artifactsPath)));
+  std::filesystem::path isa_dir = oat_path.parent_path();
+  std::filesystem::path oat_dir = isa_dir.parent_path();
+  DCHECK_EQ(oat_dir.filename(), "oat");
+
+  OR_RETURN(PrepareArtifactsDir(oat_dir, output_artifacts.permissionSettings.dirFsPermission));
+  OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission));
+  *oat_dir_path = oat_dir;
+  return {};
+}
+
+Result<void> Restorecon(
+    const std::string& path,
+    const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context) {
+  if (!kIsTargetAndroid) {
+    return {};
+  }
+
+  int res = 0;
+  if (se_context.has_value()) {
+    res = selinux_android_restorecon_pkgdir(path.c_str(),
+                                            se_context->seInfo.c_str(),
+                                            se_context->uid,
+                                            SELINUX_ANDROID_RESTORECON_RECURSE);
+  } else {
+    res = selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
+  }
+  if (res != 0) {
+    return ErrnoErrorf("Failed to restorecon directory '{}'", path);
+  }
+  return {};
+}
+
+Result<FileVisibility> GetFileVisibility(const std::string& file) {
+  std::error_code ec;
+  std::filesystem::file_status status = std::filesystem::status(file, ec);
+  if (!std::filesystem::status_known(status)) {
+    return Errorf("Failed to get status of '{}': {}", file, ec.message());
+  }
+  if (!std::filesystem::exists(status)) {
+    return FileVisibility::NOT_FOUND;
+  }
+
+  return (status.permissions() & std::filesystem::perms::others_read) !=
+                 std::filesystem::perms::none ?
+             FileVisibility::OTHER_READABLE :
+             FileVisibility::NOT_OTHER_READABLE;
+}
+
+Result<ArtdCancellationSignal*> ToArtdCancellationSignal(IArtdCancellationSignal* input) {
+  if (input == nullptr) {
+    return Error() << "Cancellation signal must not be nullptr";
+  }
+  // We cannot use `dynamic_cast` because ART code is compiled with `-fno-rtti`, so we have to check
+  // the magic number.
+  int64_t type;
+  if (!input->getType(&type).isOk() ||
+      type != reinterpret_cast<intptr_t>(kArtdCancellationSignalType)) {
+    // The cancellation signal must be created by `Artd::createCancellationSignal`.
+    return Error() << "Invalid cancellation signal type";
+  }
+  return static_cast<ArtdCancellationSignal*>(input);
+}
+
+Result<void> CopyFile(const std::string& src_path, const NewFile& dst_file) {
+  std::string content;
+  if (!ReadFileToString(src_path, &content)) {
+    return Errorf("Failed to read file '{}': {}", src_path, strerror(errno));
+  }
+  if (!WriteStringToFd(content, dst_file.Fd())) {
+    return Errorf("Failed to write file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  if (fsync(dst_file.Fd()) != 0) {
+    return Errorf("Failed to flush file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  if (lseek(dst_file.Fd(), /*offset=*/0, SEEK_SET) != 0) {
+    return Errorf(
+        "Failed to reset the offset for file '{}': {}", dst_file.TempPath(), strerror(errno));
+  }
+  return {};
+}
+
+class FdLogger {
+ public:
+  void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); }
+  void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); }
+
+  std::string GetFds() {
+    std::vector<int> fds;
+    fds.reserve(fd_mapping_.size());
+    for (const auto& [fd, path] : fd_mapping_) {
+      fds.push_back(fd);
+    }
+    return Join(fds, ':');
+  }
+
+ private:
+  std::vector<std::pair<int, std::string>> fd_mapping_;
+
+  friend std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger);
+};
+
+std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger) {
+  for (const auto& [fd, path] : fd_logger.fd_mapping_) {
+    os << fd << ":" << path << ' ';
+  }
+  return os;
+}
 
 }  // namespace
 
+#define OR_RETURN_ERROR(func, expr)         \
+  ({                                        \
+    decltype(expr)&& tmp = (expr);          \
+    if (!tmp.ok()) {                        \
+      return (func)(tmp.error().message()); \
+    }                                       \
+    std::move(tmp).value();                 \
+  })
+
+#define OR_RETURN_FATAL(expr)     OR_RETURN_ERROR(Fatal, expr)
+#define OR_RETURN_NON_FATAL(expr) OR_RETURN_ERROR(NonFatal, expr)
+
 ScopedAStatus Artd::isAlive(bool* _aidl_return) {
   *_aidl_return = true;
   return ScopedAStatus::ok();
 }
 
+ScopedAStatus Artd::deleteArtifacts(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) {
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+
+  *_aidl_return = 0;
+  *_aidl_return += GetSizeAndDeleteFile(oat_path);
+  *_aidl_return += GetSizeAndDeleteFile(OatPathToVdexPath(oat_path));
+  *_aidl_return += GetSizeAndDeleteFile(OatPathToArtPath(oat_path));
+
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getDexoptStatus(const std::string& in_dexFile,
+                                    const std::string& in_instructionSet,
+                                    const std::string& in_classLoaderContext,
+                                    GetDexoptStatusResult* _aidl_return) {
+  Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+  if (!ofa_context.ok()) {
+    return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
+  }
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  auto oat_file_assistant = OatFileAssistant::Create(in_dexFile.c_str(),
+                                                     in_instructionSet.c_str(),
+                                                     in_classLoaderContext.c_str(),
+                                                     /*load_executable=*/false,
+                                                     /*only_load_trusted_executable=*/true,
+                                                     ofa_context.value(),
+                                                     &context,
+                                                     &error_msg);
+  if (oat_file_assistant == nullptr) {
+    return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+  }
+
+  std::string ignored_odex_status;
+  oat_file_assistant->GetOptimizationStatus(&_aidl_return->locationDebugString,
+                                            &_aidl_return->compilerFilter,
+                                            &_aidl_return->compilationReason,
+                                            &ignored_odex_status);
+
+  // We ignore odex_status because it is not meaningful. It can only be either "up-to-date",
+  // "apk-more-recent", or "io-error-no-oat", which means it doesn't give us information in addition
+  // to what we can learn from compiler_filter because compiler_filter will be the actual compiler
+  // filter, "run-from-apk-fallback", and "run-from-apk" in those three cases respectively.
+  DCHECK(ignored_odex_status == "up-to-date" || ignored_odex_status == "apk-more-recent" ||
+         ignored_odex_status == "io-error-no-oat");
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile,
+                                         const std::string& in_dexFile,
+                                         bool* _aidl_return) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman()));
+
+  Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path);
+  if (!profile.ok()) {
+    if (profile.error().code() == ENOENT) {
+      *_aidl_return = false;
+      return ScopedAStatus::ok();
+    }
+    return NonFatal(
+        "Failed to open profile '{}': {}"_format(profile_path, profile.error().message()));
+  }
+  args.Add("--reference-profile-file-fd=%d", profile.value()->Fd());
+  fd_logger.Add(*profile.value());
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--apk-fd=%d", dex_file->Fd());
+  fd_logger.Add(*dex_file);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() != ProfmanResult::kSkipCompilationSmallDelta &&
+      result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta;
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
+                                               OutputProfile* in_dst,
+                                               const std::string& in_dexFile,
+                                               bool* _aidl_return) {
+  std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+  std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath));
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman())).Add("--copy-and-update-profile-key");
+
+  Result<std::unique_ptr<File>> src = OpenFileForReading(src_path);
+  if (!src.ok()) {
+    if (src.error().code() == ENOENT) {
+      *_aidl_return = false;
+      return ScopedAStatus::ok();
+    }
+    return NonFatal("Failed to open src profile '{}': {}"_format(src_path, src.error().message()));
+  }
+  args.Add("--profile-file-fd=%d", src.value()->Fd());
+  fd_logger.Add(*src.value());
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--apk-fd=%d", dex_file->Fd());
+  fd_logger.Add(*dex_file);
+
+  std::unique_ptr<NewFile> dst =
+      OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission));
+  args.Add("--reference-profile-file-fd=%d", dst->Fd());
+  fd_logger.Add(*dst);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch) {
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  OR_RETURN_NON_FATAL(dst->Keep());
+  *_aidl_return = true;
+  in_dst->profilePath.id = dst->TempId();
+  in_dst->profilePath.tmpPath = dst->TempPath();
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
+  std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile));
+  std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile));
+
+  std::error_code ec;
+  std::filesystem::rename(tmp_profile_path, ref_profile_path, ec);
+  if (ec) {
+    return NonFatal(
+        "Failed to move '{}' to '{}': {}"_format(tmp_profile_path, ref_profile_path, ec.message()));
+  }
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+
+  std::error_code ec;
+  if (!std::filesystem::remove(profile_path, ec) && ec.value() != ENOENT) {
+    LOG(ERROR) << "Failed to remove '{}': {}"_format(profile_path, ec.message());
+  }
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile,
+                                              FileVisibility* _aidl_return) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath,
+                                                FileVisibility* _aidl_return) {
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile,
+                                              FileVisibility* _aidl_return) {
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile,
+                                             FileVisibility* _aidl_return) {
+  std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path));
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles,
+                                       const std::optional<ProfilePath>& in_referenceProfile,
+                                       OutputProfile* in_outputProfile,
+                                       const std::vector<std::string>& in_dexFiles,
+                                       const MergeProfileOptions& in_options,
+                                       bool* _aidl_return) {
+  std::vector<std::string> profile_paths;
+  for (const ProfilePath& profile : in_profiles) {
+    std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(profile));
+    if (profile.getTag() == ProfilePath::dexMetadataPath) {
+      return Fatal("Does not support DM file, got '{}'"_format(profile_path));
+    }
+    profile_paths.push_back(std::move(profile_path));
+  }
+  std::string output_profile_path =
+      OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath));
+  for (const std::string& dex_file : in_dexFiles) {
+    OR_RETURN_FATAL(ValidateDexPath(dex_file));
+  }
+  if (in_options.forceMerge + in_options.dumpOnly + in_options.dumpClassesAndMethods > 1) {
+    return Fatal("Only one of 'forceMerge', 'dumpOnly', and 'dumpClassesAndMethods' can be set");
+  }
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetProfman()));
+
+  std::vector<std::unique_ptr<File>> profile_files;
+  for (const std::string& profile_path : profile_paths) {
+    Result<std::unique_ptr<File>> profile_file = OpenFileForReading(profile_path);
+    if (!profile_file.ok()) {
+      if (profile_file.error().code() == ENOENT) {
+        // Skip non-existing file.
+        continue;
+      }
+      return NonFatal(
+          "Failed to open profile '{}': {}"_format(profile_path, profile_file.error().message()));
+    }
+    args.Add("--profile-file-fd=%d", profile_file.value()->Fd());
+    fd_logger.Add(*profile_file.value());
+    profile_files.push_back(std::move(profile_file.value()));
+  }
+
+  if (profile_files.empty()) {
+    LOG(INFO) << "Merge skipped because there are no existing profiles";
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  std::unique_ptr<NewFile> output_profile_file =
+      OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission));
+
+  if (in_referenceProfile.has_value()) {
+    if (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+      return Fatal(
+          "Reference profile must not be set when 'forceMerge', 'dumpOnly', or "
+          "'dumpClassesAndMethods' is set");
+    }
+    std::string reference_profile_path =
+        OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile));
+    if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) {
+      return Fatal("Does not support DM file, got '{}'"_format(reference_profile_path));
+    }
+    OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file));
+  }
+
+  if (in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+    args.Add("--dump-output-to-fd=%d", output_profile_file->Fd());
+  } else {
+    // profman is ok with this being an empty file when in_referenceProfile isn't set.
+    args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd());
+  }
+  fd_logger.Add(*output_profile_file);
+
+  std::vector<std::unique_ptr<File>> dex_files;
+  for (const std::string& dex_path : in_dexFiles) {
+    std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path));
+    args.Add("--apk-fd=%d", dex_file->Fd());
+    fd_logger.Add(*dex_file);
+    dex_files.push_back(std::move(dex_file));
+  }
+
+  if (in_options.dumpOnly || in_options.dumpClassesAndMethods) {
+    args.Add(in_options.dumpOnly ? "--dump-only" : "--dump-classes-and-methods");
+  } else {
+    args.AddIfNonEmpty("--min-new-classes-percent-change=%s",
+                       props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent"))
+        .AddIfNonEmpty("--min-new-methods-percent-change=%s",
+                       props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent"))
+        .AddIf(in_options.forceMerge, "--force-merge")
+        .AddIf(in_options.forBootImage, "--boot-image-merge");
+  }
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec);
+  if (!result.ok()) {
+    return NonFatal("Failed to run profman: " + result.error().message());
+  }
+
+  LOG(INFO) << "profman returned code {}"_format(result.value());
+
+  if (result.value() == ProfmanResult::kSkipCompilationSmallDelta ||
+      result.value() == ProfmanResult::kSkipCompilationEmptyProfiles) {
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  ProfmanResult::ProcessingResult expected_result =
+      (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) ?
+          ProfmanResult::kSuccess :
+          ProfmanResult::kCompile;
+  if (result.value() != expected_result) {
+    return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+  }
+
+  OR_RETURN_NON_FATAL(output_profile_file->Keep());
+  *_aidl_return = true;
+  in_outputProfile->profilePath.id = output_profile_file->TempId();
+  in_outputProfile->profilePath.tmpPath = output_profile_file->TempPath();
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile,
+                                         const std::string& in_instructionSet,
+                                         const std::optional<std::string>& in_classLoaderContext,
+                                         const std::string& in_compilerFilter,
+                                         int32_t in_dexoptTrigger,
+                                         GetDexoptNeededResult* _aidl_return) {
+  Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+  if (!ofa_context.ok()) {
+    return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
+  }
+
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string error_msg;
+  auto oat_file_assistant = OatFileAssistant::Create(in_dexFile,
+                                                     in_instructionSet,
+                                                     in_classLoaderContext,
+                                                     /*load_executable=*/false,
+                                                     /*only_load_trusted_executable=*/true,
+                                                     ofa_context.value(),
+                                                     &context,
+                                                     &error_msg);
+  if (oat_file_assistant == nullptr) {
+    return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+  }
+
+  OatFileAssistant::DexOptStatus status;
+  _aidl_return->isDexoptNeeded =
+      oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)),
+                                          DexOptTriggerFromAidl(in_dexoptTrigger),
+                                          &status);
+  _aidl_return->isVdexUsable = status.IsVdexUsable();
+  _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation());
+
+  return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::dexopt(
+    const OutputArtifacts& in_outputArtifacts,
+    const std::string& in_dexFile,
+    const std::string& in_instructionSet,
+    const std::optional<std::string>& in_classLoaderContext,
+    const std::string& in_compilerFilter,
+    const std::optional<ProfilePath>& in_profile,
+    const std::optional<VdexPath>& in_inputVdex,
+    const std::optional<DexMetadataPath>& in_dmFile,
+    PriorityClass in_priorityClass,
+    const DexoptOptions& in_dexoptOptions,
+    const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal,
+    ArtdDexoptResult* _aidl_return) {
+  _aidl_return->cancelled = false;
+
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath));
+  std::string vdex_path = OatPathToVdexPath(oat_path);
+  std::string art_path = OatPathToArtPath(oat_path);
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  std::optional<std::string> profile_path =
+      in_profile.has_value() ?
+          std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) :
+          std::nullopt;
+  ArtdCancellationSignal* cancellation_signal =
+      OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get()));
+
+  std::unique_ptr<ClassLoaderContext> context = nullptr;
+  if (in_classLoaderContext.has_value()) {
+    context = ClassLoaderContext::Create(in_classLoaderContext->c_str());
+    if (context == nullptr) {
+      return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext.value()));
+    }
+  }
+
+  std::string oat_dir_path;  // For restorecon, can be empty if the artifacts are in dalvik-cache.
+  OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts, &oat_dir_path));
+
+  // First-round restorecon. artd doesn't have the permission to create files with the
+  // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will
+  // inherit `dalvikcache_data_file` rather than `apk_data_file`.
+  if (!in_outputArtifacts.artifactsPath.isInDalvikCache) {
+    OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext));
+  }
+
+  FdLogger fd_logger;
+
+  CmdlineBuilder art_exec_args;
+  art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+  CmdlineBuilder args;
+  args.Add(OR_RETURN_FATAL(GetDex2Oat()));
+
+  const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission;
+
+  std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+  args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile);
+  fd_logger.Add(*dex_file);
+  struct stat dex_st = OR_RETURN_NON_FATAL(Fstat(*dex_file));
+  if ((dex_st.st_mode & S_IROTH) == 0) {
+    if (fs_permission.isOtherReadable) {
+      return NonFatal(
+          "Outputs cannot be other-readable because the dex file '{}' is not other-readable"_format(
+              dex_file->GetPath()));
+    }
+    // Negative numbers mean no `chown`. 0 means root.
+    // Note: this check is more strict than it needs to be. For example, it doesn't allow the
+    // outputs to belong to a group that is a subset of the dex file's group. This is for
+    // simplicity, and it's okay as we don't have to handle such complicated cases in practice.
+    if ((fs_permission.uid > 0 && static_cast<uid_t>(fs_permission.uid) != dex_st.st_uid) ||
+        (fs_permission.gid > 0 && static_cast<gid_t>(fs_permission.gid) != dex_st.st_uid &&
+         static_cast<gid_t>(fs_permission.gid) != dex_st.st_gid)) {
+      return NonFatal(
+          "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})"_format(
+              dex_file->GetPath(),
+              fs_permission.uid,
+              fs_permission.gid,
+              dex_st.st_uid,
+              dex_st.st_gid));
+    }
+  }
+
+  std::unique_ptr<NewFile> oat_file = OR_RETURN_NON_FATAL(NewFile::Create(oat_path, fs_permission));
+  args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", oat_path);
+  fd_logger.Add(*oat_file);
+
+  std::unique_ptr<NewFile> vdex_file =
+      OR_RETURN_NON_FATAL(NewFile::Create(vdex_path, fs_permission));
+  args.Add("--output-vdex-fd=%d", vdex_file->Fd());
+  fd_logger.Add(*vdex_file);
+
+  std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()};
+  std::vector<std::string_view> files_to_delete;
+
+  std::unique_ptr<NewFile> art_file = nullptr;
+  if (in_dexoptOptions.generateAppImage) {
+    art_file = OR_RETURN_NON_FATAL(NewFile::Create(art_path, fs_permission));
+    args.Add("--app-image-fd=%d", art_file->Fd());
+    args.AddIfNonEmpty("--image-format=%s", props_->GetOrEmpty("dalvik.vm.appimageformat"));
+    fd_logger.Add(*art_file);
+    files_to_commit.push_back(art_file.get());
+  } else {
+    files_to_delete.push_back(art_path);
+  }
+
+  std::unique_ptr<NewFile> swap_file = nullptr;
+  if (ShouldCreateSwapFileForDexopt()) {
+    swap_file = OR_RETURN_NON_FATAL(
+        NewFile::Create("{}.swap"_format(oat_path), FsPermission{.uid = -1, .gid = -1}));
+    args.Add("--swap-fd=%d", swap_file->Fd());
+    fd_logger.Add(*swap_file);
+  }
+
+  std::vector<std::unique_ptr<File>> context_files;
+  if (context != nullptr) {
+    std::vector<std::string> flattened_context = context->FlattenDexPaths();
+    std::string dex_dir = Dirname(in_dexFile.c_str());
+    std::vector<int> context_fds;
+    for (const std::string& context_element : flattened_context) {
+      std::string context_path = std::filesystem::path(dex_dir).append(context_element);
+      OR_RETURN_FATAL(ValidateDexPath(context_path));
+      std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path));
+      context_fds.push_back(context_file->Fd());
+      fd_logger.Add(*context_file);
+      context_files.push_back(std::move(context_file));
+    }
+    args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':'))
+        .Add("--class-loader-context=%s", in_classLoaderContext.value())
+        .Add("--classpath-dir=%s", dex_dir);
+  }
+
+  std::unique_ptr<File> input_vdex_file = nullptr;
+  if (in_inputVdex.has_value()) {
+    std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value()));
+    input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
+    args.Add("--input-vdex-fd=%d", input_vdex_file->Fd());
+    fd_logger.Add(*input_vdex_file);
+  }
+
+  std::unique_ptr<File> dm_file = nullptr;
+  if (in_dmFile.has_value()) {
+    std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value()));
+    dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path));
+    args.Add("--dm-fd=%d", dm_file->Fd());
+    fd_logger.Add(*dm_file);
+  }
+
+  std::unique_ptr<File> profile_file = nullptr;
+  if (profile_path.has_value()) {
+    profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value()));
+    args.Add("--profile-file-fd=%d", profile_file->Fd());
+    fd_logger.Add(*profile_file);
+    struct stat profile_st = OR_RETURN_NON_FATAL(Fstat(*profile_file));
+    if (fs_permission.isOtherReadable && (profile_st.st_mode & S_IROTH) == 0) {
+      return NonFatal(
+          "Outputs cannot be other-readable because the profile '{}' is not other-readable"_format(
+              profile_file->GetPath()));
+    }
+    // TODO(b/260228411): Check uid and gid.
+  }
+
+  // Second-round restorecon. Restorecon recursively after the output files are created, so that the
+  // SELinux context is applied to all of them. The SELinux context of a file is mostly inherited
+  // from the parent directory upon creation, but the MLS label is not inherited, so we need to
+  // restorecon every file so that they have the right MLS label. If the files are in dalvik-cache,
+  // there's no need to restorecon because they inherits the SELinux context of the dalvik-cache
+  // directory and they don't need to have MLS labels.
+  if (!in_outputArtifacts.artifactsPath.isInDalvikCache) {
+    OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext));
+  }
+
+  AddBootImageFlags(args);
+  AddCompilerConfigFlags(
+      in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args);
+  AddPerfConfigFlags(in_priorityClass, art_exec_args, args);
+
+  art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args));
+
+  LOG(INFO) << "Running dex2oat: " << Join(art_exec_args.Get(), /*separator=*/" ")
+            << "\nOpened FDs: " << fd_logger;
+
+  ExecCallbacks callbacks{
+      .on_start =
+          [&](pid_t pid) {
+            std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+            cancellation_signal->pids_.insert(pid);
+            // Handle cancellation signals sent before the process starts.
+            if (cancellation_signal->is_cancelled_) {
+              int res = kill_(pid, SIGKILL);
+              DCHECK_EQ(res, 0);
+            }
+          },
+      .on_end =
+          [&](pid_t pid) {
+            std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+            // The pid should no longer receive kill signals sent by `cancellation_signal`.
+            cancellation_signal->pids_.erase(pid);
+          },
+  };
+
+  ProcessStat stat;
+  Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kLongTimeoutSec, callbacks, &stat);
+  _aidl_return->wallTimeMs = stat.wall_time_ms;
+  _aidl_return->cpuTimeMs = stat.cpu_time_ms;
+  if (!result.ok()) {
+    {
+      std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+      if (cancellation_signal->is_cancelled_) {
+        _aidl_return->cancelled = true;
+        return ScopedAStatus::ok();
+      }
+    }
+    return NonFatal("Failed to run dex2oat: " + result.error().message());
+  }
+
+  LOG(INFO) << "dex2oat returned code {}"_format(result.value());
+
+  if (result.value() != 0) {
+    return NonFatal("dex2oat returned an unexpected code: {}"_format(result.value()));
+  }
+
+  int64_t size_bytes = 0;
+  int64_t size_before_bytes = 0;
+  for (const NewFile* file : files_to_commit) {
+    size_bytes += GetSize(file->TempPath()).value_or(0);
+    size_before_bytes += GetSize(file->FinalPath()).value_or(0);
+  }
+  for (std::string_view path : files_to_delete) {
+    size_before_bytes += GetSize(path).value_or(0);
+  }
+  OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete));
+
+  _aidl_return->sizeBytes = size_bytes;
+  _aidl_return->sizeBeforeBytes = size_before_bytes;
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::cancel() {
+  std::lock_guard<std::mutex> lock(mu_);
+  is_cancelled_ = true;
+  for (pid_t pid : pids_) {
+    int res = kill_(pid, SIGKILL);
+    DCHECK_EQ(res, 0);
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) {
+  *_aidl_return = reinterpret_cast<intptr_t>(kArtdCancellationSignalType);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::createCancellationSignal(
+    std::shared_ptr<IArtdCancellationSignal>* _aidl_return) {
+  *_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_);
+  return ScopedAStatus::ok();
+}
+
 Result<void> Artd::Start() {
   ScopedAStatus status = ScopedAStatus::fromStatus(
       AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
@@ -58,5 +1021,232 @@
   return {};
 }
 
+Result<OatFileAssistantContext*> Artd::GetOatFileAssistantContext() {
+  std::lock_guard<std::mutex> lock(ofa_context_mu_);
+
+  if (ofa_context_ == nullptr) {
+    ofa_context_ = std::make_unique<OatFileAssistantContext>(
+        std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+            OatFileAssistantContext::RuntimeOptions{
+                .image_locations = *OR_RETURN(GetBootImageLocations()),
+                .boot_class_path = *OR_RETURN(GetBootClassPath()),
+                .boot_class_path_locations = *OR_RETURN(GetBootClassPath()),
+                .deny_art_apex_data_files = DenyArtApexDataFiles(),
+            }));
+    std::string error_msg;
+    if (!ofa_context_->FetchAll(&error_msg)) {
+      return Error() << error_msg;
+    }
+  }
+
+  return ofa_context_.get();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootImageLocations() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+
+  if (!cached_boot_image_locations_.has_value()) {
+    std::string location_str;
+
+    if (UseJitZygoteLocked()) {
+      location_str = GetJitZygoteBootImageLocation();
+    } else if (std::string value = GetUserDefinedBootImageLocationsLocked(); !value.empty()) {
+      location_str = std::move(value);
+    } else {
+      std::string error_msg;
+      std::string android_root = GetAndroidRootSafe(&error_msg);
+      if (!error_msg.empty()) {
+        return Errorf("Failed to get ANDROID_ROOT: {}", error_msg);
+      }
+      location_str = GetDefaultBootImageLocation(android_root, DenyArtApexDataFilesLocked());
+    }
+
+    cached_boot_image_locations_ = Split(location_str, ":");
+  }
+
+  return &cached_boot_image_locations_.value();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootClassPath() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+
+  if (!cached_boot_class_path_.has_value()) {
+    const char* env_value = getenv("BOOTCLASSPATH");
+    if (env_value == nullptr || strlen(env_value) == 0) {
+      return Errorf("Failed to get environment variable 'BOOTCLASSPATH'");
+    }
+    cached_boot_class_path_ = Split(env_value, ":");
+  }
+
+  return &cached_boot_class_path_.value();
+}
+
+bool Artd::UseJitZygote() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return UseJitZygoteLocked();
+}
+
+bool Artd::UseJitZygoteLocked() {
+  if (!cached_use_jit_zygote_.has_value()) {
+    cached_use_jit_zygote_ =
+        props_->GetBool("persist.device_config.runtime_native_boot.profilebootclasspath",
+                        "dalvik.vm.profilebootclasspath",
+                        /*default_value=*/false);
+  }
+
+  return cached_use_jit_zygote_.value();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocations() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return GetUserDefinedBootImageLocationsLocked();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocationsLocked() {
+  if (!cached_user_defined_boot_image_locations_.has_value()) {
+    cached_user_defined_boot_image_locations_ = props_->GetOrEmpty("dalvik.vm.boot-image");
+  }
+
+  return cached_user_defined_boot_image_locations_.value();
+}
+
+bool Artd::DenyArtApexDataFiles() {
+  std::lock_guard<std::mutex> lock(cache_mu_);
+  return DenyArtApexDataFilesLocked();
+}
+
+bool Artd::DenyArtApexDataFilesLocked() {
+  if (!cached_deny_art_apex_data_files_.has_value()) {
+    cached_deny_art_apex_data_files_ =
+        !props_->GetBool("odsign.verification.success", /*default_value=*/false);
+  }
+
+  return cached_deny_art_apex_data_files_.value();
+}
+
+Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); }
+
+Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); }
+
+bool Artd::ShouldUseDex2Oat64() {
+  return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() &&
+         props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false);
+}
+
+Result<std::string> Artd::GetDex2Oat() {
+  std::string binary_name = ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32";
+  // TODO(b/234351700): Should we use the "d" variant?
+  return BuildArtBinPath(binary_name);
+}
+
+bool Artd::ShouldCreateSwapFileForDexopt() {
+  // Create a swap file by default. Dex2oat will decide whether to use it or not.
+  return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true);
+}
+
+void Artd::AddBootImageFlags(/*out*/ CmdlineBuilder& args) {
+  if (UseJitZygote()) {
+    args.Add("--force-jit-zygote");
+  } else {
+    args.AddIfNonEmpty("--boot-image=%s", GetUserDefinedBootImageLocations());
+  }
+}
+
+void Artd::AddCompilerConfigFlags(const std::string& instruction_set,
+                                  const std::string& compiler_filter,
+                                  PriorityClass priority_class,
+                                  const DexoptOptions& dexopt_options,
+                                  /*out*/ CmdlineBuilder& args) {
+  args.Add("--instruction-set=%s", instruction_set);
+  std::string features_prop = "dalvik.vm.isa.{}.features"_format(instruction_set);
+  args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop));
+  std::string variant_prop = "dalvik.vm.isa.{}.variant"_format(instruction_set);
+  args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop));
+
+  args.Add("--compiler-filter=%s", compiler_filter)
+      .Add("--compilation-reason=%s", dexopt_options.compilationReason);
+
+  args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none");
+
+  args.AddIfNonEmpty("--max-image-block-size=%s",
+                     props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size"))
+      .AddIfNonEmpty("--very-large-app-threshold=%s",
+                     props_->GetOrEmpty("dalvik.vm.dex2oat-very-large"))
+      .AddIfNonEmpty(
+          "--resolve-startup-const-strings=%s",
+          props_->GetOrEmpty("persist.device_config.runtime.dex2oat_resolve_startup_strings",
+                             "dalvik.vm.dex2oat-resolve-startup-strings"));
+
+  args.AddIf(dexopt_options.debuggable, "--debuggable")
+      .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false),
+             "--generate-debug-info")
+      .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false),
+             "--generate-mini-debug-info");
+
+  args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files")
+      .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion)
+      .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled");
+}
+
+void Artd::AddPerfConfigFlags(PriorityClass priority_class,
+                              /*out*/ CmdlineBuilder& art_exec_args,
+                              /*out*/ CmdlineBuilder& dex2oat_args) {
+  // CPU set and number of threads.
+  std::string default_cpu_set_prop = "dalvik.vm.dex2oat-cpu-set";
+  std::string default_threads_prop = "dalvik.vm.dex2oat-threads";
+  std::string cpu_set;
+  std::string threads;
+  if (priority_class >= PriorityClass::BOOT) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set");
+    threads = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-threads");
+  } else if (priority_class >= PriorityClass::INTERACTIVE_FAST) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-cpu-set", default_cpu_set_prop);
+    threads = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-threads", default_threads_prop);
+  } else if (priority_class <= PriorityClass::BACKGROUND) {
+    cpu_set = props_->GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", default_cpu_set_prop);
+    threads = props_->GetOrEmpty("dalvik.vm.background-dex2oat-threads", default_threads_prop);
+  } else {
+    cpu_set = props_->GetOrEmpty(default_cpu_set_prop);
+    threads = props_->GetOrEmpty(default_threads_prop);
+  }
+  dex2oat_args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads);
+
+  if (priority_class < PriorityClass::BOOT) {
+    art_exec_args
+        .Add(priority_class <= PriorityClass::BACKGROUND ? "--set-task-profile=Dex2OatBackground" :
+                                                           "--set-task-profile=Dex2OatBootComplete")
+        .Add("--set-priority=background");
+  }
+
+  dex2oat_args.AddRuntimeIfNonEmpty("-Xms%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xms"))
+      .AddRuntimeIfNonEmpty("-Xmx%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xmx"));
+
+  // Enable compiling dex files in isolation on low ram devices.
+  // It takes longer but reduces the memory footprint.
+  dex2oat_args.AddIf(props_->GetBool("ro.config.low_ram", /*default_value=*/false),
+                     "--compile-individually");
+}
+
+Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args,
+                                    int timeout_sec,
+                                    const ExecCallbacks& callbacks,
+                                    ProcessStat* stat) const {
+  std::string error_msg;
+  ExecResult result =
+      exec_utils_->ExecAndReturnResult(args, timeout_sec, callbacks, stat, &error_msg);
+  if (result.status != ExecResult::kExited) {
+    return Error() << error_msg;
+  }
+  return result.exit_code;
+}
+
+Result<struct stat> Artd::Fstat(const File& file) const {
+  struct stat st;
+  if (fstat_(file.Fd(), &st) != 0) {
+    return Errorf("Unable to fstat file '{}'", file.GetPath());
+  }
+  return st;
+}
+
 }  // namespace artd
 }  // namespace art
diff --git a/artd/artd.h b/artd/artd.h
index f01d9a8..fbaaed0 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -17,18 +17,210 @@
 #ifndef ART_ARTD_ARTD_H_
 #define ART_ARTD_ARTD_H_
 
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <csignal>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
 #include "aidl/com/android/server/art/BnArtd.h"
+#include "aidl/com/android/server/art/BnArtdCancellationSignal.h"
 #include "android-base/result.h"
+#include "android-base/thread_annotations.h"
 #include "android/binder_auto_utils.h"
+#include "base/os.h"
+#include "exec_utils.h"
+#include "oat_file_assistant_context.h"
+#include "tools/cmdline_builder.h"
+#include "tools/system_properties.h"
 
 namespace art {
 namespace artd {
 
+class ArtdCancellationSignal : public aidl::com::android::server::art::BnArtdCancellationSignal {
+ public:
+  explicit ArtdCancellationSignal(std::function<int(pid_t, int)> kill_func)
+      : kill_(std::move(kill_func)) {}
+
+  ndk::ScopedAStatus cancel() override;
+
+  ndk::ScopedAStatus getType(int64_t* _aidl_return) override;
+
+ private:
+  std::mutex mu_;
+  // True if cancellation has been signaled.
+  bool is_cancelled_ GUARDED_BY(mu_) = false;
+  // The pids of currently running child processes that are bound to this signal.
+  std::unordered_set<pid_t> pids_ GUARDED_BY(mu_);
+
+  std::function<int(pid_t, int)> kill_;
+
+  friend class Artd;
+};
+
 class Artd : public aidl::com::android::server::art::BnArtd {
  public:
+  explicit Artd(std::unique_ptr<art::tools::SystemProperties> props =
+                    std::make_unique<art::tools::SystemProperties>(),
+                std::unique_ptr<ExecUtils> exec_utils = std::make_unique<ExecUtils>(),
+                std::function<int(pid_t, int)> kill_func = kill,
+                std::function<int(int, struct stat*)> fstat_func = fstat)
+      : props_(std::move(props)),
+        exec_utils_(std::move(exec_utils)),
+        kill_(std::move(kill_func)),
+        fstat_(std::move(fstat_func)) {}
+
   ndk::ScopedAStatus isAlive(bool* _aidl_return) override;
 
+  ndk::ScopedAStatus deleteArtifacts(
+      const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexoptStatus(
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::string& in_classLoaderContext,
+      aidl::com::android::server::art::GetDexoptStatusResult* _aidl_return) override;
+
+  ndk::ScopedAStatus isProfileUsable(const aidl::com::android::server::art::ProfilePath& in_profile,
+                                     const std::string& in_dexFile,
+                                     bool* _aidl_return) override;
+
+  ndk::ScopedAStatus copyAndRewriteProfile(
+      const aidl::com::android::server::art::ProfilePath& in_src,
+      aidl::com::android::server::art::OutputProfile* in_dst,
+      const std::string& in_dexFile,
+      bool* _aidl_return) override;
+
+  ndk::ScopedAStatus commitTmpProfile(
+      const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override;
+
+  ndk::ScopedAStatus deleteProfile(
+      const aidl::com::android::server::art::ProfilePath& in_profile) override;
+
+  ndk::ScopedAStatus getProfileVisibility(
+      const aidl::com::android::server::art::ProfilePath& in_profile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus mergeProfiles(
+      const std::vector<aidl::com::android::server::art::ProfilePath>& in_profiles,
+      const std::optional<aidl::com::android::server::art::ProfilePath>& in_referenceProfile,
+      aidl::com::android::server::art::OutputProfile* in_outputProfile,
+      const std::vector<std::string>& in_dexFiles,
+      const aidl::com::android::server::art::MergeProfileOptions& in_options,
+      bool* _aidl_return) override;
+
+  ndk::ScopedAStatus getArtifactsVisibility(
+      const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexFileVisibility(
+      const std::string& in_dexFile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDmFileVisibility(
+      const aidl::com::android::server::art::DexMetadataPath& in_dmFile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
+  ndk::ScopedAStatus getDexoptNeeded(
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::optional<std::string>& in_classLoaderContext,
+      const std::string& in_compilerFilter,
+      int32_t in_dexoptTrigger,
+      aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override;
+
+  ndk::ScopedAStatus dexopt(
+      const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts,
+      const std::string& in_dexFile,
+      const std::string& in_instructionSet,
+      const std::optional<std::string>& in_classLoaderContext,
+      const std::string& in_compilerFilter,
+      const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile,
+      const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex,
+      const std::optional<aidl::com::android::server::art::DexMetadataPath>& in_dmFile,
+      aidl::com::android::server::art::PriorityClass in_priorityClass,
+      const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions,
+      const std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>&
+          in_cancellationSignal,
+      aidl::com::android::server::art::ArtdDexoptResult* _aidl_return) override;
+
+  ndk::ScopedAStatus createCancellationSignal(
+      std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>* _aidl_return)
+      override;
+
   android::base::Result<void> Start();
+
+ private:
+  android::base::Result<OatFileAssistantContext*> GetOatFileAssistantContext()
+      EXCLUDES(ofa_context_mu_);
+
+  android::base::Result<const std::vector<std::string>*> GetBootImageLocations()
+      EXCLUDES(cache_mu_);
+
+  android::base::Result<const std::vector<std::string>*> GetBootClassPath() EXCLUDES(cache_mu_);
+
+  bool UseJitZygote() EXCLUDES(cache_mu_);
+  bool UseJitZygoteLocked() REQUIRES(cache_mu_);
+
+  const std::string& GetUserDefinedBootImageLocations() EXCLUDES(cache_mu_);
+  const std::string& GetUserDefinedBootImageLocationsLocked() REQUIRES(cache_mu_);
+
+  bool DenyArtApexDataFiles() EXCLUDES(cache_mu_);
+  bool DenyArtApexDataFilesLocked() REQUIRES(cache_mu_);
+
+  android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+                                               int timeout_sec,
+                                               const ExecCallbacks& callbacks = ExecCallbacks(),
+                                               ProcessStat* stat = nullptr) const;
+
+  android::base::Result<std::string> GetProfman();
+
+  android::base::Result<std::string> GetArtExec();
+
+  bool ShouldUseDex2Oat64();
+
+  android::base::Result<std::string> GetDex2Oat();
+
+  bool ShouldCreateSwapFileForDexopt();
+
+  void AddBootImageFlags(/*out*/ art::tools::CmdlineBuilder& args);
+
+  void AddCompilerConfigFlags(const std::string& instruction_set,
+                              const std::string& compiler_filter,
+                              aidl::com::android::server::art::PriorityClass priority_class,
+                              const aidl::com::android::server::art::DexoptOptions& dexopt_options,
+                              /*out*/ art::tools::CmdlineBuilder& args);
+
+  void AddPerfConfigFlags(aidl::com::android::server::art::PriorityClass priority_class,
+                          /*out*/ art::tools::CmdlineBuilder& art_exec_args,
+                          /*out*/ art::tools::CmdlineBuilder& args);
+
+  android::base::Result<struct stat> Fstat(const art::File& file) const;
+
+  std::mutex cache_mu_;
+  std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_);
+  std::optional<std::vector<std::string>> cached_boot_class_path_ GUARDED_BY(cache_mu_);
+  std::optional<bool> cached_use_jit_zygote_ GUARDED_BY(cache_mu_);
+  std::optional<std::string> cached_user_defined_boot_image_locations_ GUARDED_BY(cache_mu_);
+  std::optional<bool> cached_deny_art_apex_data_files_ GUARDED_BY(cache_mu_);
+
+  std::mutex ofa_context_mu_;
+  std::unique_ptr<OatFileAssistantContext> ofa_context_ GUARDED_BY(ofa_context_mu_);
+
+  const std::unique_ptr<art::tools::SystemProperties> props_;
+  const std::unique_ptr<ExecUtils> exec_utils_;
+  const std::function<int(pid_t, int)> kill_;
+  const std::function<int(int, struct stat*)> fstat_;
 };
 
 }  // namespace artd
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 14bccc2..8d03536 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -16,26 +16,471 @@
 
 #include "artd.h"
 
-#include <memory>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
-#include "android/binder_interface_utils.h"
+#include <algorithm>
+#include <chrono>
+#include <condition_variable>
+#include <csignal>
+#include <filesystem>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/collections.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_status.h"
+#include "base/array_ref.h"
 #include "base/common_art_test.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "testing.h"
+#include "tools/system_properties.h"
 
 namespace art {
 namespace artd {
 namespace {
 
+using ::aidl::com::android::server::art::ArtdDexoptResult;
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::FileVisibility;
+using ::aidl::com::android::server::art::FsPermission;
+using ::aidl::com::android::server::art::IArtdCancellationSignal;
+using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
+using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Append;
+using ::android::base::Error;
+using ::android::base::make_scope_guard;
+using ::android::base::ParseInt;
+using ::android::base::ReadFdToString;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::ScopeGuard;
+using ::android::base::Split;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::AnyOf;
+using ::testing::Contains;
+using ::testing::ContainsRegex;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
+using ::testing::Matcher;
+using ::testing::MockFunction;
+using ::testing::Not;
+using ::testing::Property;
+using ::testing::ResultOf;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::UnorderedElementsAreArray;
+using ::testing::WithArg;
+
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
+  android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger));
+  return make_scope_guard([old_logger = std::move(old_logger)]() mutable {
+    android::base::SetLogger(std::move(old_logger));
+  });
+}
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+  std::string actual_content;
+  ASSERT_TRUE(ReadFileToString(path, &actual_content));
+  EXPECT_EQ(actual_content, expected_content);
+}
+
+void CheckOtherReadable(const std::string& path, bool expected_value) {
+  EXPECT_EQ((std::filesystem::status(path).permissions() & std::filesystem::perms::others_read) !=
+                std::filesystem::perms::none,
+            expected_value);
+}
+
+Result<std::vector<std::string>> GetFlagValues(ArrayRef<const std::string> args,
+                                               std::string_view flag) {
+  std::vector<std::string> values;
+  for (const std::string& arg : args) {
+    std::string_view value(arg);
+    if (android::base::ConsumePrefix(&value, flag)) {
+      values.emplace_back(value);
+    }
+  }
+  if (values.empty()) {
+    return Errorf("Flag '{}' not found", flag);
+  }
+  return values;
+}
+
+Result<std::string> GetFlagValue(ArrayRef<const std::string> args, std::string_view flag) {
+  std::vector<std::string> flag_values = OR_RETURN(GetFlagValues(args, flag));
+  if (flag_values.size() > 1) {
+    return Errorf("Duplicate flag '{}'", flag);
+  }
+  return flag_values[0];
+}
+
+void WriteToFdFlagImpl(const std::vector<std::string>& args,
+                       std::string_view flag,
+                       std::string_view content,
+                       bool assume_empty) {
+  std::string value = OR_FAIL(GetFlagValue(ArrayRef<const std::string>(args), flag));
+  ASSERT_NE(value, "");
+  int fd;
+  ASSERT_TRUE(ParseInt(value, &fd));
+  if (assume_empty) {
+    ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_CUR), 0);
+  } else {
+    ASSERT_EQ(ftruncate(fd, /*length=*/0), 0);
+    ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_SET), 0);
+  }
+  ASSERT_TRUE(WriteStringToFd(content, fd));
+}
+
+// Writes `content` to the FD specified by the `flag`.
+ACTION_P(WriteToFdFlag, flag, content) {
+  WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/true);
+}
+
+// Clears any existing content and writes `content` to the FD specified by the `flag`.
+ACTION_P(ClearAndWriteToFdFlag, flag, content) {
+  WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/false);
+}
+
+// Matches a flag that starts with `flag` and whose value matches `matcher`.
+MATCHER_P2(Flag, flag, matcher, "") {
+  std::string_view value(arg);
+  if (!android::base::ConsumePrefix(&value, flag)) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, std::string(value), result_listener);
+}
+
+// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches
+// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument.
+MATCHER_P2(ListFlag, flag, matcher, "") {
+  return ExplainMatchResult(
+      Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)),
+      arg,
+      result_listener);
+}
+
+// Matches an FD of a file whose path matches `matcher`.
+MATCHER_P(FdOf, matcher, "") {
+  std::string proc_path = "/proc/self/fd/{}"_format(arg);
+  char path[PATH_MAX];
+  ssize_t len = readlink(proc_path.c_str(), path, sizeof(path));
+  if (len < 0) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener);
+}
+
+// Matches an FD of a file whose content matches `matcher`.
+MATCHER_P(FdHasContent, matcher, "") {
+  int fd;
+  if (!ParseInt(arg, &fd)) {
+    return false;
+  }
+  std::string actual_content;
+  if (!ReadFdToString(fd, &actual_content)) {
+    return false;
+  }
+  return ExplainMatchResult(matcher, actual_content, result_listener);
+}
+
+template <typename T, typename U>
+Result<std::pair<ArrayRef<const T>, ArrayRef<const T>>> SplitBy(const std::vector<T>& list,
+                                                                const U& separator) {
+  auto it = std::find(list.begin(), list.end(), separator);
+  if (it == list.end()) {
+    return Errorf("'{}' not found", separator);
+  }
+  size_t pos = it - list.begin();
+  return std::make_pair(ArrayRef<const T>(list).SubArray(0, pos),
+                        ArrayRef<const T>(list).SubArray(pos + 1));
+}
+
+// Matches a container that, when split by `separator`, the first part matches `head_matcher`, and
+// the second part matches `tail_matcher`.
+MATCHER_P3(WhenSplitBy, separator, head_matcher, tail_matcher, "") {
+  auto [head, tail] = OR_MISMATCH(SplitBy(arg, separator));
+  return ExplainMatchResult(head_matcher, head, result_listener) &&
+         ExplainMatchResult(tail_matcher, tail, result_listener);
+}
+
+MATCHER_P(HasKeepFdsForImpl, fd_flags, "") {
+  auto [head, tail] = OR_MISMATCH(SplitBy(arg, "--"));
+  std::string keep_fds_value = OR_MISMATCH(GetFlagValue(head, "--keep-fds="));
+  std::vector<std::string> keep_fds = Split(keep_fds_value, ":");
+  std::vector<std::string> fd_flag_values;
+  for (std::string_view fd_flag : fd_flags) {
+    for (const std::string& fd_flag_value : OR_MISMATCH(GetFlagValues(tail, fd_flag))) {
+      for (std::string& fd : Split(fd_flag_value, ":")) {
+        fd_flag_values.push_back(std::move(fd));
+      }
+    }
+  }
+  return ExplainMatchResult(UnorderedElementsAreArray(fd_flag_values), keep_fds, result_listener);
+}
+
+// Matches an argument list that has the "--keep-fds=" flag before "--", whose value is a
+// semicolon-separated list that contains exactly the values of the given flags after "--".
+//
+// E.g., if the flags after "--" are "--foo=1", "--bar=2:3", "--baz=4", "--baz=5", and the matcher
+// is `HasKeepFdsFor("--foo=", "--bar=", "--baz=")`, then it requires the "--keep-fds=" flag before
+// "--" to contain exactly 1, 2, 3, 4, and 5.
+template <typename... Args>
+auto HasKeepFdsFor(Args&&... args) {
+  std::vector<std::string_view> fd_flags;
+  Append(fd_flags, std::forward<Args>(args)...);
+  return HasKeepFdsForImpl(fd_flags);
+}
+
+class MockSystemProperties : public tools::SystemProperties {
+ public:
+  MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class MockExecUtils : public ExecUtils {
+ 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).
+  ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+                                 int,
+                                 const ExecCallbacks& callbacks,
+                                 ProcessStat* stat,
+                                 std::string*) const override {
+    Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat);
+    if (code.ok()) {
+      return {.status = ExecResult::kExited, .exit_code = code.value()};
+    }
+    return {.status = ExecResult::kUnknown};
+  }
+
+  MOCK_METHOD(Result<int>,
+              DoExecAndReturnCode,
+              (const std::vector<std::string>& arg_vector,
+               const ExecCallbacks& callbacks,
+               ProcessStat* stat),
+              (const));
+};
+
 class ArtdTest : public CommonArtTest {
  protected:
   void SetUp() override {
     CommonArtTest::SetUp();
-    artd_ = ndk::SharedRefBase::make<Artd>();
+    auto mock_props = std::make_unique<MockSystemProperties>();
+    mock_props_ = mock_props.get();
+    EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return(""));
+    auto mock_exec_utils = std::make_unique<MockExecUtils>();
+    mock_exec_utils_ = mock_exec_utils.get();
+    artd_ = ndk::SharedRefBase::make<Artd>(std::move(mock_props),
+                                           std::move(mock_exec_utils),
+                                           mock_kill_.AsStdFunction(),
+                                           mock_fstat_.AsStdFunction());
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    scratch_path_ = scratch_dir_->GetPath();
+    // Remove the trailing '/';
+    scratch_path_.resize(scratch_path_.length() - 1);
+
+    ON_CALL(mock_fstat_, Call).WillByDefault(fstat);
+
+    // Use an arbitrary existing directory as ART root.
+    art_root_ = scratch_path_ + "/com.android.art";
+    std::filesystem::create_directories(art_root_);
+    setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1);
+
+    // Use an arbitrary existing directory as Android data.
+    android_data_ = scratch_path_ + "/data";
+    std::filesystem::create_directories(android_data_);
+    setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1);
+
+    dex_file_ = scratch_path_ + "/a/b.apk";
+    isa_ = "arm64";
+    artifacts_path_ = ArtifactsPath{
+        .dexPath = dex_file_,
+        .isa = isa_,
+        .isInDalvikCache = false,
+    };
+    struct stat st;
+    ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0);
+    output_artifacts_ = OutputArtifacts{
+        .artifactsPath = artifacts_path_,
+        .permissionSettings =
+            OutputArtifacts::PermissionSettings{
+                .dirFsPermission =
+                    FsPermission{
+                        .uid = static_cast<int32_t>(st.st_uid),
+                        .gid = static_cast<int32_t>(st.st_gid),
+                        .isOtherReadable = true,
+                        .isOtherExecutable = true,
+                    },
+                .fileFsPermission =
+                    FsPermission{
+                        .uid = static_cast<int32_t>(st.st_uid),
+                        .gid = static_cast<int32_t>(st.st_gid),
+                        .isOtherReadable = true,
+                    },
+            },
+    };
+    clc_1_ = GetTestDexFileName("Main");
+    clc_2_ = GetTestDexFileName("Nested");
+    class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
+    compiler_filter_ = "speed";
+    TmpProfilePath tmp_profile_path{
+        .finalPath =
+            PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+        .id = "12345"};
+    profile_path_ = tmp_profile_path;
+    vdex_path_ = artifacts_path_;
+    dm_path_ = DexMetadataPath{.dexPath = dex_file_};
+    std::filesystem::create_directories(
+        std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
   }
 
-  void TearDown() override { CommonArtTest::TearDown(); }
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  void RunDexopt(binder_exception_t expected_status = EX_NONE,
+                 Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
+                                                                       false),
+                 std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+    RunDexopt(Property(&ndk::ScopedAStatus::getExceptionCode, expected_status),
+              std::move(aidl_return_matcher),
+              cancellation_signal);
+  }
+
+  void RunDexopt(Matcher<ndk::ScopedAStatus> status_matcher,
+                 Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
+                                                                       false),
+                 std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+    InitFilesBeforeDexopt();
+    if (cancellation_signal == nullptr) {
+      ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+    }
+    ArtdDexoptResult aidl_return;
+    ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_,
+                                              dex_file_,
+                                              isa_,
+                                              class_loader_context_,
+                                              compiler_filter_,
+                                              profile_path_,
+                                              vdex_path_,
+                                              dm_path_,
+                                              priority_class_,
+                                              dexopt_options_,
+                                              cancellation_signal,
+                                              &aidl_return);
+    ASSERT_THAT(status, std::move(status_matcher)) << status.getMessage();
+    if (status.isOk()) {
+      ASSERT_THAT(aidl_return, std::move(aidl_return_matcher));
+    }
+  }
+
+  void CreateFile(const std::string& filename, const std::string& content = "") {
+    std::filesystem::path path(filename);
+    std::filesystem::create_directories(path.parent_path());
+    ASSERT_TRUE(WriteStringToFile(content, filename));
+  }
 
   std::shared_ptr<Artd> artd_;
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string scratch_path_;
+  std::string art_root_;
+  std::string android_data_;
+  MockFunction<android::base::LogFunction> mock_logger_;
+  ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+  ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
+  MockSystemProperties* mock_props_;
+  MockExecUtils* mock_exec_utils_;
+  MockFunction<int(pid_t, int)> mock_kill_;
+  MockFunction<int(int, struct stat*)> mock_fstat_;
+
+  std::string dex_file_;
+  std::string isa_;
+  ArtifactsPath artifacts_path_;
+  OutputArtifacts output_artifacts_;
+  std::string clc_1_;
+  std::string clc_2_;
+  std::optional<std::string> class_loader_context_;
+  std::string compiler_filter_;
+  std::optional<VdexPath> vdex_path_;
+  std::optional<DexMetadataPath> dm_path_;
+  PriorityClass priority_class_ = PriorityClass::BACKGROUND;
+  DexoptOptions dexopt_options_;
+  std::optional<ProfilePath> profile_path_;
+  bool dex_file_other_readable_ = true;
+  bool profile_other_readable_ = true;
+
+ private:
+  void InitFilesBeforeDexopt() {
+    // Required files.
+    CreateFile(dex_file_);
+    std::filesystem::permissions(dex_file_,
+                                 std::filesystem::perms::others_read,
+                                 dex_file_other_readable_ ? std::filesystem::perm_options::add :
+                                                            std::filesystem::perm_options::remove);
+
+    // Optional files.
+    if (vdex_path_.has_value()) {
+      CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex");
+    }
+    if (dm_path_.has_value()) {
+      CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+    }
+    if (profile_path_.has_value()) {
+      std::string path = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+      CreateFile(path);
+      std::filesystem::permissions(path,
+                                   std::filesystem::perms::others_read,
+                                   profile_other_readable_ ? std::filesystem::perm_options::add :
+                                                             std::filesystem::perm_options::remove);
+    }
+
+    // Files to be replaced.
+    std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_));
+    CreateFile(oat_path, "old_oat");
+    CreateFile(OatPathToVdexPath(oat_path), "old_vdex");
+    CreateFile(OatPathToArtPath(oat_path), "old_art");
+  }
 };
 
 TEST_F(ArtdTest, isAlive) {
@@ -44,6 +489,1334 @@
   EXPECT_TRUE(result);
 }
 
+TEST_F(ArtdTest, deleteArtifacts) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex"));    // 2 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 4 + 2 + 1);
+
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.vdex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
+}
+
+TEST_F(ArtdTest, deleteArtifactsMissingFile) {
+  // Missing VDEX file.
+  std::string oat_dir = android_data_ + "/dalvik-cache/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art"));     // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_
+                  ->deleteArtifacts(
+                      ArtifactsPath{
+                          .dexPath = "/a/b.apk",
+                          .isa = "arm64",
+                          .isInDalvikCache = true,
+                      },
+                      &result)
+                  .isOk());
+  EXPECT_EQ(result, 4 + 1);
+
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.dex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.art"));
+}
+
+TEST_F(ArtdTest, deleteArtifactsNoFile) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsPermissionDenied) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex"));    // 2 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(3);
+
+  auto scoped_inaccessible = ScopedInaccessible(oat_dir);
+  auto scoped_unroot = ScopedUnroot();
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsFileIsDir) {
+  // VDEX file is a directory.
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  std::filesystem::create_directories(oat_dir);
+  std::filesystem::create_directories(oat_dir + "/b.vdex");
+  ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex"));  // 4 bytes.
+  ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art"));      // 1 byte.
+
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_,
+              Call(_, _, _, _, _, ContainsRegex(R"re(Failed to get the file size.*b\.vdex)re")))
+      .Times(1);
+
+  int64_t result = -1;
+  EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
+  EXPECT_EQ(result, 4 + 1);
+
+  // The directory is kept because getting the file size failed.
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
+  EXPECT_TRUE(std::filesystem::exists(oat_dir + "/b.vdex"));
+  EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
+}
+
+TEST_F(ArtdTest, dexopt) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/dex2oat32"),
+                          Contains(Flag("--zip-fd=", FdOf(dex_file_))),
+                          Contains(Flag("--zip-location=", dex_file_)),
+                          Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
+                          Contains(Flag("--instruction-set=", "arm64")),
+                          Contains(Flag("--compiler-filter=", "speed")),
+                          Contains(Flag(
+                              "--profile-file-fd=",
+                              FdOf(android_data_ +
+                                   "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))),
+                          Contains(Flag("--input-vdex-fd=",
+                                        FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))),
+                          Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))))),
+                HasKeepFdsFor("--zip-fd=",
+                              "--profile-file-fd=",
+                              "--input-vdex-fd=",
+                              "--dm-fd=",
+                              "--oat-fd=",
+                              "--output-vdex-fd=",
+                              "--app-image-fd=",
+                              "--class-loader-context-fds=",
+                              "--swap-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
+                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")),
+                      SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
+                      Return(0)));
+  RunDexopt(
+      EX_NONE,
+      AllOf(Field(&ArtdDexoptResult::cancelled, false),
+            Field(&ArtdDexoptResult::wallTimeMs, 100),
+            Field(&ArtdDexoptResult::cpuTimeMs, 400),
+            Field(&ArtdDexoptResult::sizeBytes, strlen("art") + strlen("oat") + strlen("vdex")),
+            Field(&ArtdDexoptResult::sizeBeforeBytes,
+                  strlen("old_art") + strlen("old_oat") + strlen("old_vdex"))));
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art");
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", true);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", true);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.art", true);
+}
+
+TEST_F(ArtdTest, dexoptClassLoaderContext) {
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      _,
+                      AllOf(Contains(ListFlag("--class-loader-context-fds=",
+                                              ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))),
+                            Contains(Flag("--class-loader-context=", class_loader_context_)),
+                            Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptClassLoaderContextNull) {
+  class_loader_context_ = std::nullopt;
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Not(Contains(Flag("--class-loader-context-fds=", _))),
+                                            Not(Contains(Flag("--class-loader-context=", _))),
+                                            Not(Contains(Flag("--classpath-dir=", _))))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptNoOptionalInputFiles) {
+  profile_path_ = std::nullopt;
+  vdex_path_ = std::nullopt;
+  dm_path_ = std::nullopt;
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              _,
+                                              AllOf(Not(Contains(Flag("--profile-file-fd=", _))),
+                                                    Not(Contains(Flag("--input-vdex-fd=", _))),
+                                                    Not(Contains(Flag("--dm-fd=", _))))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassBoot) {
+  priority_class_ = PriorityClass::BOOT;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              AllOf(Not(Contains(Flag("--set-task-profile=", _))),
+                                                    Not(Contains(Flag("--set-priority=", _)))),
+                                              Contains(Flag("--compact-dex-level=", "none"))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassInteractive) {
+  priority_class_ = PriorityClass::INTERACTIVE;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassInteractiveFast) {
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptPriorityClassBackground) {
+  priority_class_ = PriorityClass::BACKGROUND;
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBackground")),
+                                    Contains(Flag("--set-priority=", "background"))),
+                              Not(Contains(Flag("--compact-dex-level=", _)))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDexoptOptions) {
+  dexopt_options_ = DexoptOptions{
+      .compilationReason = "install",
+      .targetSdkVersion = 123,
+      .debuggable = false,
+      .generateAppImage = false,
+      .hiddenApiPolicyEnabled = false,
+  };
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Contains(Flag("--compilation-reason=", "install")),
+                                            Contains(Flag("-Xtarget-sdk-version:", "123")),
+                                            Not(Contains("--debuggable")),
+                                            Not(Contains(Flag("--app-image-fd=", _))),
+                                            Not(Contains(Flag("-Xhidden-api-policy:", _))))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+
+  // `sizeBeforeBytes` should include the size of the old ART file even if no new ART file is
+  // generated.
+  RunDexopt(EX_NONE,
+            Field(&ArtdDexoptResult::sizeBeforeBytes,
+                  strlen("old_art") + strlen("old_oat") + strlen("old_vdex")));
+}
+
+TEST_F(ArtdTest, dexoptDexoptOptions2) {
+  dexopt_options_ = DexoptOptions{
+      .compilationReason = "bg-dexopt",
+      .targetSdkVersion = 456,
+      .debuggable = true,
+      .generateAppImage = true,
+      .hiddenApiPolicyEnabled = true,
+  };
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(WhenSplitBy("--",
+                                      _,
+                                      AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")),
+                                            Contains(Flag("-Xtarget-sdk-version:", "456")),
+                                            Contains("--debuggable"),
+                                            Contains(Flag("--app-image-fd=", _)),
+                                            Contains(Flag("-Xhidden-api-policy:", "enabled")))),
+                          _,
+                          _))
+      .WillOnce(Return(0));
+
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDefaultFlagsWhenNoSystemProps) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Contains(Flag("--swap-fd=", FdOf(_))),
+                                    Not(Contains(Flag("--instruction-set-features=", _))),
+                                    Not(Contains(Flag("--instruction-set-variant=", _))),
+                                    Not(Contains(Flag("--max-image-block-size=", _))),
+                                    Not(Contains(Flag("--very-large-app-threshold=", _))),
+                                    Not(Contains(Flag("--resolve-startup-const-strings=", _))),
+                                    Not(Contains("--generate-debug-info")),
+                                    Not(Contains("--generate-mini-debug-info")),
+                                    Contains("-Xdeny-art-apex-data-files"),
+                                    Not(Contains(Flag("--cpu-set=", _))),
+                                    Not(Contains(Flag("-j", _))),
+                                    Not(Contains(Flag("-Xms", _))),
+                                    Not(Contains(Flag("-Xmx", _))),
+                                    Not(Contains("--compile-individually")),
+                                    Not(Contains(Flag("--image-format=", _))),
+                                    Not(Contains("--force-jit-zygote")),
+                                    Not(Contains(Flag("--boot-image=", _))))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFlagsFromSystemProps) {
+  dexopt_options_.generateAppImage = true;
+
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-swap")).WillOnce(Return("0"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.features"))
+      .WillOnce(Return("features"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.variant")).WillOnce(Return("variant"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-max-image-block-size"))
+      .WillOnce(Return("size"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-very-large"))
+      .WillOnce(Return("threshold"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-resolve-startup-strings"))
+      .WillOnce(Return("strings"));
+  EXPECT_CALL(*mock_props_, GetProperty("debug.generate-debug-info")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-minidebuginfo")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("odsign.verification.success")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xms")).WillOnce(Return("xms"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xmx")).WillOnce(Return("xmx"));
+  EXPECT_CALL(*mock_props_, GetProperty("ro.config.low_ram")).WillOnce(Return("1"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.appimageformat")).WillOnce(Return("imgfmt"));
+  EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillOnce(Return("boot-image"));
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Not(Contains(Flag("--swap-fd=", _))),
+                                    Contains(Flag("--instruction-set-features=", "features")),
+                                    Contains(Flag("--instruction-set-variant=", "variant")),
+                                    Contains(Flag("--max-image-block-size=", "size")),
+                                    Contains(Flag("--very-large-app-threshold=", "threshold")),
+                                    Contains(Flag("--resolve-startup-const-strings=", "strings")),
+                                    Contains("--generate-debug-info"),
+                                    Contains("--generate-mini-debug-info"),
+                                    Not(Contains("-Xdeny-art-apex-data-files")),
+                                    Contains(Flag("-Xms", "xms")),
+                                    Contains(Flag("-Xmx", "xmx")),
+                                    Contains("--compile-individually"),
+                                    Contains(Flag("--image-format=", "imgfmt")),
+                                    Not(Contains("--force-jit-zygote")),
+                                    Contains(Flag("--boot-image=", "boot-image")))),
+                  _,
+                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFlagsForceJitZygote) {
+  EXPECT_CALL(*mock_props_,
+              GetProperty("persist.device_config.runtime_native_boot.profilebootclasspath"))
+      .WillOnce(Return("true"));
+  ON_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillByDefault(Return("boot-image"));
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(WhenSplitBy("--",
+                                              _,
+                                              AllOf(Contains("--force-jit-zygote"),
+                                                    Not(Contains(Flag("--boot-image=", _))))),
+                                  _,
+                                  _))
+      .WillOnce(Return(0));
+  RunDexopt();
+}
+
+static void SetDefaultResourceControlProps(MockSystemProperties* mock_props) {
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
+}
+
+TEST_F(ArtdTest, dexoptDefaultResourceControlBoot) {
+  SetDefaultResourceControlProps(mock_props_);
+
+  // The default resource control properties don't apply to BOOT.
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BOOT;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDefaultResourceControlOther) {
+  SetDefaultResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _,
+          _))
+      .Times(3)
+      .WillRepeatedly(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  RunDexopt();
+  priority_class_ = PriorityClass::INTERACTIVE;
+  RunDexopt();
+  priority_class_ = PriorityClass::BACKGROUND;
+  RunDexopt();
+}
+
+static void SetAllResourceControlProps(MockSystemProperties* mock_props) {
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0,1,2,3"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-threads"))
+      .WillRepeatedly(Return("8"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0,2,3"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-threads"))
+      .WillRepeatedly(Return("6"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-cpu-set"))
+      .WillRepeatedly(Return("0"));
+  EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-threads"))
+      .WillRepeatedly(Return("2"));
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlBoot) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BOOT;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlInteractiveFast) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE_FAST;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlInteractive) {
+  SetAllResourceControlProps(mock_props_);
+
+  // INTERACTIVE always uses the default resource control properties.
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::INTERACTIVE;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptAllResourceControlBackground) {
+  SetAllResourceControlProps(mock_props_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))),
+          _,
+          _))
+      .WillOnce(Return(0));
+  priority_class_ = PriorityClass::BACKGROUND;
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptFailed) {
+  dexopt_options_.generateAppImage = true;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")),
+                      Return(1)));
+  RunDexopt(EX_SERVICE_SPECIFIC);
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptFailedToCommit) {
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_inaccessible;
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_unroot;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, auto, auto) {
+                        scoped_inaccessible = std::make_unique<ScopeGuard<std::function<void()>>>(
+                            ScopedInaccessible(scratch_path_ + "/a/oat/arm64"));
+                        scoped_unroot =
+                            std::make_unique<ScopeGuard<std::function<void()>>>(ScopedUnroot());
+                        return 0;
+                      }));
+
+  RunDexopt(
+      EX_SERVICE_SPECIFIC,
+      AllOf(Field(&ArtdDexoptResult::sizeBytes, 0), Field(&ArtdDexoptResult::sizeBeforeBytes, 0)));
+}
+
+TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
+        callbacks.on_start(kPid);
+        callbacks.on_end(kPid);
+        return Error();
+      });
+  EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL));
+
+  cancellation_signal->cancel();
+
+  RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal);
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptCancelledDuringDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+  constexpr std::chrono::duration<int> kTimeout = std::chrono::seconds(1);
+
+  std::condition_variable process_started_cv, process_killed_cv;
+  std::mutex mu;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
+        std::unique_lock<std::mutex> lock(mu);
+        // Step 2.
+        callbacks.on_start(kPid);
+        process_started_cv.notify_one();
+        EXPECT_EQ(process_killed_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+        // Step 5.
+        callbacks.on_end(kPid);
+        return Error();
+      });
+
+  EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)).WillOnce([&](auto, auto) {
+    // Step 4.
+    process_killed_cv.notify_one();
+    return 0;
+  });
+
+  std::thread t;
+  {
+    std::unique_lock<std::mutex> lock(mu);
+    // Step 1.
+    t = std::thread([&] {
+      RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal);
+    });
+    EXPECT_EQ(process_started_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+    // Step 3.
+    cancellation_signal->cancel();
+  }
+
+  t.join();
+
+  // Step 6.
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
+}
+
+TEST_F(ArtdTest, dexoptCancelledAfterDex2oat) {
+  std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+  ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+
+  constexpr pid_t kPid = 123;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, const ExecCallbacks& callbacks, auto) {
+                        callbacks.on_start(kPid);
+                        callbacks.on_end(kPid);
+                        return 0;
+                      }));
+  EXPECT_CALL(mock_kill_, Call).Times(0);
+
+  RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, false), cancellation_signal);
+
+  // This signal should be ignored.
+  cancellation_signal->cancel();
+
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "new_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "new_vdex");
+  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
+}
+
+TEST_F(ArtdTest, dexoptDexFileNotOtherReadable) {
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs cannot be other-readable because the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptProfileNotOtherReadable) {
+  profile_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs cannot be other-readable because the profile"))));
+}
+
+TEST_F(ArtdTest, dexoptOutputNotOtherReadable) {
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  profile_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
+  RunDexopt();
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", false);
+  CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", false);
+}
+
+TEST_F(ArtdTest, dexoptUidMismatch) {
+  output_artifacts_.permissionSettings.fileFsPermission.uid = 12345;
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs' owner doesn't match the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptGidMismatch) {
+  output_artifacts_.permissionSettings.fileFsPermission.gid = 12345;
+  output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
+  RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                  Property(&ndk::ScopedAStatus::getMessage,
+                           HasSubstr("Outputs' owner doesn't match the dex file"))));
+}
+
+TEST_F(ArtdTest, dexoptGidMatchesUid) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 123, .gid = 123, .isOtherReadable = false};
+  EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat);  // For profile.
+  EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
+      .WillOnce(DoAll(SetArgPointee<1>((struct stat){
+                          .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
+                      Return(0)));
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptGidMatchesGid) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 123, .gid = 456, .isOtherReadable = false};
+  EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat);  // For profile.
+  EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
+      .WillOnce(DoAll(SetArgPointee<1>((struct stat){
+                          .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
+                      Return(0)));
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptUidGidChangeOk) {
+  // The dex file is other-readable, so we don't check uid and gid.
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = 12345, .gid = 12345, .isOtherReadable = false};
+  ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
+  // It's okay to fail on chown. This happens when the test is not run as root.
+  RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
+                  AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
+                        Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
+}
+
+TEST_F(ArtdTest, dexoptNoUidGidChange) {
+  output_artifacts_.permissionSettings.fileFsPermission = {
+      .uid = -1, .gid = -1, .isOtherReadable = false};
+  dex_file_other_readable_ = false;
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
+  RunDexopt();
+}
+
+TEST_F(ArtdTest, isProfileUsable) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+                HasKeepFdsFor("--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta));
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_TRUE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFalse) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles));
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableNotFound) {
+  CreateFile(dex_file_);
+
+  bool result;
+  EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+}
+
+TEST_F(ArtdTest, isProfileUsableFailed) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
+
+  bool result;
+  ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfile) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Contains("--copy-and-update-profile-key"),
+                          Contains(Flag("--profile-file-fd=", FdOf(src_file))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+                HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
+                      Return(ProfmanResult::kCopyAndUpdateSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+  std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath));
+  EXPECT_EQ(dst.profilePath.tmpPath, real_path);
+  CheckContent(real_path, "def");
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch));
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
+  CreateFile(dex_file_);
+
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  bool result;
+  EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+  CreateFile(src_file, "abc");
+  OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  dst.profilePath.id = "";
+  dst.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
+
+  bool result;
+  ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+  EXPECT_THAT(dst.profilePath.id, IsEmpty());
+  EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, commitTmpProfile) {
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path));
+  CreateFile(tmp_profile_file);
+
+  EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
+
+  EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
+  EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, commitTmpProfileFailed) {
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
+
+  EXPECT_FALSE(status.isOk());
+  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+  EXPECT_THAT(
+      status.getMessage(),
+      ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
+
+  EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, deleteProfile) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  CreateFile(profile_file);
+
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+
+  EXPECT_FALSE(std::filesystem::exists(profile_file));
+}
+
+TEST_F(ArtdTest, deleteProfileDoesNotExist) {
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+TEST_F(ArtdTest, deleteProfileFailed) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(
+      mock_logger_,
+      Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re")));
+
+  EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+class ArtdGetVisibilityTest : public ArtdTest {
+ protected:
+  template <typename PathType>
+  using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*);
+
+  template <typename PathType>
+  void TestGetVisibilityOtherReadable(Method<PathType> method,
+                                      const PathType& input,
+                                      const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotOtherReadable(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) {
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityPermissionDenied(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+
+    auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path());
+    auto scoped_unroot = ScopedUnroot();
+
+    FileVisibility result;
+    ndk::ScopedAStatus status = ((*artd_).*method)(input, &result);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of"));
+  }
+};
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getProfileVisibility,
+                                 profile_path_.value(),
+                                 OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility,
+                                 dm_path_.value(),
+                                 OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdTest, mergeProfiles) {
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
+  CreateFile(reference_profile_file, "abc");
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+
+  PrimaryCurProfilePath profile_1_path{
+      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
+  CreateFile(profile_1_file, "def");
+
+  OutputProfile output_profile{.profilePath = reference_profile_path,
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  std::string dex_file_1 = scratch_path_ + "/a/b.apk";
+  std::string dex_file_2 = scratch_path_ + "/a/c.apk";
+  CreateFile(dex_file_1);
+  CreateFile(dex_file_2);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          AllOf(WhenSplitBy(
+                    "--",
+                    AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                    AllOf(Contains(art_root_ + "/bin/profman"),
+                          Not(Contains(Flag("--profile-file-fd=", FdOf(profile_0_file)))),
+                          Contains(Flag("--profile-file-fd=", FdOf(profile_1_file))),
+                          Contains(Flag("--reference-profile-file-fd=", FdHasContent("abc"))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_1))),
+                          Contains(Flag("--apk-fd=", FdOf(dex_file_2))),
+                          Not(Contains("--force-merge")),
+                          Not(Contains("--boot-image-merge")))),
+                HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(ClearAndWriteToFdFlag("--reference-profile-file-fd=", "merged")),
+                      Return(ProfmanResult::kCompile)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path, profile_1_path},
+                                  reference_profile_path,
+                                  &output_profile,
+                                  {dex_file_1, dex_file_2},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  std::string real_path = OR_FATAL(BuildTmpProfilePath(output_profile.profilePath));
+  EXPECT_EQ(output_profile.profilePath.tmpPath, real_path);
+  CheckContent(real_path, "merged");
+}
+
+TEST_F(ArtdTest, mergeProfilesEmptyReferenceProfile) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                      AllOf(Contains(art_root_ + "/bin/profman"),
+                            Contains(Flag("--profile-file-fd=", FdOf(profile_0_file))),
+                            Contains(Flag("--reference-profile-file-fd=", FdHasContent(""))),
+                            Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+          _,
+          _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "merged")),
+                      Return(ProfmanResult::kCompile)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
+}
+
+TEST_F(ArtdTest, mergeProfilesProfilesDontExist) {
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
+  CreateFile(reference_profile_file, "abc");
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+
+  // Doesn't exist.
+  PrimaryCurProfilePath profile_1_path{
+      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
+
+  OutputProfile output_profile{.profilePath = reference_profile_path,
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode).Times(0);
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  /*in_options=*/{},
+                                  &result)
+                  .isOk());
+  EXPECT_FALSE(result);
+  EXPECT_THAT(output_profile.profilePath.id, IsEmpty());
+  EXPECT_THAT(output_profile.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsForceMerge) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--", _, AllOf(Contains("--force-merge"), Contains("--boot-image-merge"))),
+          _,
+          _))
+      .WillOnce(Return(ProfmanResult::kSuccess));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.forceMerge = true, .forBootImage = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsDumpOnly) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  AllOf(WhenSplitBy("--",
+                                    _,
+                                    AllOf(Contains("--dump-only"),
+                                          Not(Contains(Flag("--reference-profile-file-fd=", _))))),
+                        HasKeepFdsFor("--profile-file-fd=", "--apk-fd=", "--dump-output-to-fd=")),
+                  _,
+                  _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")),
+                      Return(ProfmanResult::kSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.dumpOnly = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  CheckContent(output_profile.profilePath.tmpPath, "dump");
+}
+
+TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) {
+  PrimaryCurProfilePath profile_0_path{
+      .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+  CreateFile(profile_0_file, "def");
+
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
+                               .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+  output_profile.profilePath.id = "";
+  output_profile.profilePath.tmpPath = "";
+
+  CreateFile(dex_file_);
+
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Contains("--dump-classes-and-methods"),
+                                    Not(Contains(Flag("--reference-profile-file-fd=", _))))),
+                  _,
+                  _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")),
+                      Return(ProfmanResult::kSuccess)));
+
+  bool result;
+  EXPECT_TRUE(artd_
+                  ->mergeProfiles({profile_0_path},
+                                  std::nullopt,
+                                  &output_profile,
+                                  {dex_file_},
+                                  {.dumpClassesAndMethods = true},
+                                  &result)
+                  .isOk());
+  EXPECT_TRUE(result);
+  EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
+  CheckContent(output_profile.profilePath.tmpPath, "dump");
+}
+
 }  // namespace
 }  // namespace artd
 }  // namespace art
diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp
index ad8474f..b6fd5b8 100644
--- a/artd/binder/Android.bp
+++ b/artd/binder/Android.bp
@@ -31,6 +31,10 @@
     backend: {
         java: {
             enabled: true,
+            apex_available: [
+                "com.android.art",
+                "com.android.art.debug",
+            ],
         },
         cpp: {
             enabled: false,
@@ -40,9 +44,7 @@
             apex_available: [
                 "com.android.art",
                 "com.android.art.debug",
-                "com.android.compos",
             ],
-            min_sdk_version: "31",
         },
     },
     unstable: true,
@@ -50,4 +52,5 @@
         "//system/tools/aidl/build",
         "//art:__subpackages__",
     ],
+    min_sdk_version: "31",
 }
diff --git a/artd/binder/com/android/server/art/ArtdDexoptResult.aidl b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl
new file mode 100644
index 0000000..6f031f2
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.dexopt}.
+ *
+ * @hide
+ */
+parcelable ArtdDexoptResult {
+    /** True if the operation is cancelled. */
+    boolean cancelled;
+    /**
+     * The wall time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long wallTimeMs;
+    /**
+     * The CPU time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long cpuTimeMs;
+    /**
+     * The total size, in bytes, of the dexopt artifacts, or 0 if dex2oat fails, is cancelled, or
+     * is not run.
+     */
+    long sizeBytes;
+    /**
+     * The total size, in bytes, of the previous dexopt artifacts that have been replaced, or
+     * 0 if there were no previous dexopt artifacts or dex2oat fails, is cancelled, or is not
+     * run.
+     */
+    long sizeBeforeBytes;
+}
diff --git a/artd/binder/com/android/server/art/ArtifactsPath.aidl b/artd/binder/com/android/server/art/ArtifactsPath.aidl
new file mode 100644
index 0000000..3122f0f
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtifactsPath.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to the dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files).
+ *
+ * @hide
+ */
+parcelable ArtifactsPath {
+    /** The absolute path starting with '/' to the dex file (i.e., APK or JAR file). */
+    @utf8InCpp String dexPath;
+    /** The instruction set of the dexopt artifacts. */
+    @utf8InCpp String isa;
+    /** Whether the dexopt artifacts are in the dalvik-cache folder. */
+    boolean isInDalvikCache;
+}
diff --git a/artd/binder/com/android/server/art/DexMetadataPath.aidl b/artd/binder/com/android/server/art/DexMetadataPath.aidl
new file mode 100644
index 0000000..5f9ab81
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexMetadataPath.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a dex metadata file.
+ *
+ * @hide
+ */
+parcelable DexMetadataPath {
+    /**
+     * The absolute path starting with '/' to the dex file that the dex metadata file is next to.
+     */
+    @utf8InCpp String dexPath;
+}
diff --git a/artd/binder/com/android/server/art/DexoptOptions.aidl b/artd/binder/com/android/server/art/DexoptOptions.aidl
new file mode 100644
index 0000000..351c079
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptOptions.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Miscellaneous options for performing dexopt. Every field corresponds to a dex2oat command line
+ * flag.
+ *
+ * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes
+ * static flags, and flags that only depend on system properties or other passed parameters, such as
+ * the priority class.
+ *
+ * All fields are required.
+ *
+ * @hide
+ */
+parcelable DexoptOptions {
+    /** --compilation-reason */
+    @utf8InCpp String compilationReason;
+    /** -Xtarget-sdk-version */
+    int targetSdkVersion;
+    /** --debuggable */
+    boolean debuggable;
+    /** --app-image-fd */
+    boolean generateAppImage;
+    /** -Xhidden-api-policy:enabled */
+    boolean hiddenApiPolicyEnabled;
+}
diff --git a/artd/binder/com/android/server/art/DexoptTrigger.aidl b/artd/binder/com/android/server/art/DexoptTrigger.aidl
new file mode 100644
index 0000000..58a9ec8
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the conditions where dexopt should be performed.
+ * See `OatFileAssistant::DexOptTrigger`.
+ *
+ * This is actually used as a bit field, but is declared as an enum because AIDL doesn't support bit
+ * fields.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum DexoptTrigger {
+    COMPILER_FILTER_IS_BETTER = 1 << 0,
+    COMPILER_FILTER_IS_SAME = 1 << 1,
+    COMPILER_FILTER_IS_WORSE = 1 << 2,
+    PRIMARY_BOOT_IMAGE_BECOMES_USABLE = 1 << 3,
+}
diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl
new file mode 100644
index 0000000..ceaa818
--- /dev/null
+++ b/artd/binder/com/android/server/art/FileVisibility.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Indicates the visibility of a file. I.e., whether the file has the "read" bit for "others"
+ * (S_IROTH).
+ *
+ * Theoretically, even if the value is {@code OTHER_READABLE}, others' access can still be denied
+ * due to the lack of the "exec" bit on parent directories. However, for compilation artifacts, all
+ * parent directories do have the "exec" bit for "others" in practice.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum FileVisibility {
+    NOT_FOUND = 0,
+    OTHER_READABLE = 1,
+    NOT_OTHER_READABLE = 2,
+}
diff --git a/artd/binder/com/android/server/art/FsPermission.aidl b/artd/binder/com/android/server/art/FsPermission.aidl
new file mode 100644
index 0000000..9c2ddb9
--- /dev/null
+++ b/artd/binder/com/android/server/art/FsPermission.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the Linux filesystem permission of a file or a directory.
+ *
+ * If both `uid` and `gid` are negative, no `chown` will be performed.
+ *
+ * If none of the booleans are set, the default permission bits are `rw-r-----` for a file, and
+ * `rwxr-x---` for a directory.
+ *
+ * @hide
+ */
+parcelable FsPermission {
+    int uid;
+    int gid;
+    /**
+     * Whether the file/directory should have the "read" bit for "others" (S_IROTH).
+     */
+    boolean isOtherReadable;
+    /**
+     * Whether the file/directory should have the "execute" bit for "others" (S_IXOTH).
+     */
+    boolean isOtherExecutable;
+}
diff --git a/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
new file mode 100644
index 0000000..99c4951
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getDexoptNeeded}.
+ *
+ * @hide
+ */
+parcelable GetDexoptNeededResult {
+    /** Whether dexopt is needed. */
+    boolean isDexoptNeeded;
+    /** Whether there is a usable VDEX file. Note that this can be true even if dexopt is needed. */
+    boolean isVdexUsable;
+    /** The location of the best usable artifacts. */
+    ArtifactsLocation artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+
+    enum ArtifactsLocation {
+        /** No usable artifacts. */
+        NONE_OR_ERROR = 0,
+        /** In the global "dalvik-cache" folder. */
+        DALVIK_CACHE = 1,
+        /** In the "oat" folder next to the dex file. */
+        NEXT_TO_DEX = 2,
+        /** In the dex metadata file. This means the only usable artifact is the VDEX file. */
+        DM = 3,
+    }
+}
diff --git a/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl
new file mode 100644
index 0000000..08786ca
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getDexoptStatus}. Each field corresponds to a field in
+ * {@code com.android.server.art.model.DexoptStatus.DexFileDexoptStatus}.
+ *
+ * @hide
+ */
+parcelable GetDexoptStatusResult {
+    @utf8InCpp String compilerFilter;
+    @utf8InCpp String compilationReason;
+    @utf8InCpp String locationDebugString;
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 58b2aae..5121063 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -16,8 +16,143 @@
 
 package com.android.server.art;
 
-/** {@hide} */
+/** @hide */
 interface IArtd {
     // Test to see if the artd service is available.
     boolean isAlive();
+
+    /**
+     * Deletes artifacts and returns the released space, in bytes.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long deleteArtifacts(in com.android.server.art.ArtifactsPath artifactsPath);
+
+    /**
+     * Returns the dexopt status of a dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.GetDexoptStatusResult getDexoptStatus(
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @utf8InCpp String classLoaderContext);
+
+    /**
+     * Returns true if the profile exists and contains entries for the given dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean isProfileUsable(in com.android.server.art.ProfilePath profile,
+            @utf8InCpp String dexFile);
+
+    /**
+     * Copies the profile and rewrites it for the given dex file. Returns true and fills
+     * `dst.profilePath.id` if the operation succeeds and `src` exists and contains entries that
+     * match the given dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean copyAndRewriteProfile(in com.android.server.art.ProfilePath src,
+            inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
+
+    /**
+     * Moves the temporary profile to the permanent location.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    void commitTmpProfile(in com.android.server.art.ProfilePath.TmpProfilePath profile);
+
+    /**
+     * Deletes the profile. Does nothing of the profile doesn't exist.
+     *
+     * Operates on the whole DM file if given one.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    void deleteProfile(in com.android.server.art.ProfilePath profile);
+
+    /**
+     * Returns the visibility of the profile.
+     *
+     * Operates on the whole DM file if given one.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getProfileVisibility(
+            in com.android.server.art.ProfilePath profile);
+
+    /**
+     * Merges profiles. Both `profiles` and `referenceProfile` are inputs, while the difference is
+     * that `referenceProfile` is also used as the reference to calculate the diff. `profiles` that
+     * don't exist are skipped, while `referenceProfile`, if provided, must exist. Returns true,
+     * writes the merge result to `outputProfile` and fills `outputProfile.profilePath.id` and
+     * `outputProfile.profilePath.tmpPath` if a merge has been performed.
+     *
+     * When `options.forceMerge`, `options.dumpOnly`, or `options.dumpClassesAndMethods` is set,
+     * `referenceProfile` must not be set. I.e., all inputs must be provided by `profiles`. This is
+     * because the merge will always happen, and hence no reference profile is needed to calculate
+     * the diff.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    boolean mergeProfiles(in List<com.android.server.art.ProfilePath> profiles,
+            in @nullable com.android.server.art.ProfilePath referenceProfile,
+            inout com.android.server.art.OutputProfile outputProfile,
+            in @utf8InCpp List<String> dexFiles,
+            in com.android.server.art.MergeProfileOptions options);
+
+    /**
+     * Returns the visibility of the artifacts.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getArtifactsVisibility(
+            in com.android.server.art.ArtifactsPath artifactsPath);
+
+    /**
+     * Returns the visibility of the dex file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getDexFileVisibility(@utf8InCpp String dexFile);
+
+    /**
+     * Returns the visibility of the DM file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getDmFileVisibility(
+            in com.android.server.art.DexMetadataPath dmFile);
+
+    /**
+     * Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values
+     * defined in `com.android.server.art.DexoptTrigger`.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.GetDexoptNeededResult getDexoptNeeded(
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+            int dexoptTrigger);
+
+    /**
+     * Dexopts a dex file for the given instruction set.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.ArtdDexoptResult dexopt(
+            in com.android.server.art.OutputArtifacts outputArtifacts,
+            @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+            @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+            in @nullable com.android.server.art.ProfilePath profile,
+            in @nullable com.android.server.art.VdexPath inputVdex,
+            in @nullable com.android.server.art.DexMetadataPath dmFile,
+            com.android.server.art.PriorityClass priorityClass,
+            in com.android.server.art.DexoptOptions dexoptOptions,
+            in com.android.server.art.IArtdCancellationSignal cancellationSignal);
+
+    /**
+     * Returns a cancellation signal which can be used to cancel {@code dexopt} calls.
+     */
+    com.android.server.art.IArtdCancellationSignal createCancellationSignal();
 }
diff --git a/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
new file mode 100644
index 0000000..fb15e64
--- /dev/null
+++ b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Similar to `android.os.CancellationSignal` but for artd. Must be created by
+ * `IArtd.createCancellationSignal`.
+ *
+ * @hide
+ */
+interface IArtdCancellationSignal {
+    oneway void cancel();
+
+    /** For artd internal type-checking. DO NOT USE. */
+    long getType();
+}
diff --git a/artd/binder/com/android/server/art/MergeProfileOptions.aidl b/artd/binder/com/android/server/art/MergeProfileOptions.aidl
new file mode 100644
index 0000000..2d007f9
--- /dev/null
+++ b/artd/binder/com/android/server/art/MergeProfileOptions.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Miscellaneous options for merging profiles. Every field corresponds to a profman command line
+ * flag.
+ *
+ * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes
+ * static flags, and flags that only depend on system properties or other passed parameters.
+ *
+ * All fields are required.
+ *
+ * @hide
+ */
+parcelable MergeProfileOptions {
+    /** --force-merge */
+    boolean forceMerge;
+    /** --boot-image-merge */
+    boolean forBootImage;
+    /** --dump-only */
+    boolean dumpOnly;
+    /** --dump-classes-and-methods */
+    boolean dumpClassesAndMethods;
+}
diff --git a/artd/binder/com/android/server/art/OutputArtifacts.aidl b/artd/binder/com/android/server/art/OutputArtifacts.aidl
new file mode 100644
index 0000000..9a53965
--- /dev/null
+++ b/artd/binder/com/android/server/art/OutputArtifacts.aidl
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents output dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files).
+ *
+ * @hide
+ */
+parcelable OutputArtifacts {
+    /** The path to the output. */
+    com.android.server.art.ArtifactsPath artifactsPath;
+
+    parcelable PermissionSettings {
+        /**
+         * The permission of the directories that contain the artifacts. Has no effect if
+         * `artifactsPath.isInDalvikCache` is true.
+         */
+        com.android.server.art.FsPermission dirFsPermission;
+
+        /** The permission of the files. */
+        com.android.server.art.FsPermission fileFsPermission;
+
+        /** The tuple used for looking up for the SELinux context. */
+        parcelable SeContext {
+            /** The seinfo tag in SELinux policy. */
+            @utf8InCpp String seInfo;
+
+            /** The uid that represents the combination of the user id and the app id. */
+            int uid;
+        }
+
+        /**
+         * Determines the SELinux context of the directories and the files. If empty, the default
+         * context based on the file path will be used. Has no effect if
+         * `artifactsPath.isInDalvikCache` is true.
+         */
+        @nullable SeContext seContext;
+    }
+
+    PermissionSettings permissionSettings;
+}
diff --git a/artd/binder/com/android/server/art/OutputProfile.aidl b/artd/binder/com/android/server/art/OutputProfile.aidl
new file mode 100644
index 0000000..50efda2
--- /dev/null
+++ b/artd/binder/com/android/server/art/OutputProfile.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents output profile file.
+ *
+ * @hide
+ */
+parcelable OutputProfile {
+    /**
+     * The path to the output.
+     *
+     * Only outputing to a temporary file is supported to avoid race condition.
+     */
+    com.android.server.art.ProfilePath.TmpProfilePath profilePath;
+
+    /** The permission of the file. */
+    com.android.server.art.FsPermission fsPermission;
+}
diff --git a/artd/binder/com/android/server/art/PriorityClass.aidl b/artd/binder/com/android/server/art/PriorityClass.aidl
new file mode 100644
index 0000000..abea3f3
--- /dev/null
+++ b/artd/binder/com/android/server/art/PriorityClass.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Keep in sync with {@link ArtFlags.PriorityClassApi}.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum PriorityClass {
+    BOOT = 100,
+    INTERACTIVE_FAST = 80,
+    INTERACTIVE = 60,
+    BACKGROUND = 40,
+}
diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl
new file mode 100644
index 0000000..43df531
--- /dev/null
+++ b/artd/binder/com/android/server/art/ProfilePath.aidl
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a profile file.
+ *
+ * @hide
+ */
+union ProfilePath {
+    PrimaryRefProfilePath primaryRefProfilePath;
+    PrebuiltProfilePath prebuiltProfilePath;
+    PrimaryCurProfilePath primaryCurProfilePath;
+    SecondaryRefProfilePath secondaryRefProfilePath;
+    SecondaryCurProfilePath secondaryCurProfilePath;
+    TmpProfilePath tmpProfilePath;
+
+    /** Represents a profile in the dex metadata file. */
+    com.android.server.art.DexMetadataPath dexMetadataPath;
+
+    /** Represents a reference profile. */
+    parcelable PrimaryRefProfilePath {
+        /** The name of the package. */
+        @utf8InCpp String packageName;
+        /** The stem of the profile file */
+        @utf8InCpp String profileName;
+    }
+
+    /**
+     * Represents a profile next to a dex file. This is usually a prebuilt profile in the system
+     * image, but it can also be a profile that package manager can potentially put along with the
+     * APK during installation. The latter one is not officially supported by package manager, but
+     * OEMs can customize package manager to support that.
+     */
+    parcelable PrebuiltProfilePath {
+        /** The path to the dex file that the profile is next to. */
+        @utf8InCpp String dexPath;
+    }
+
+    /** Represents a current profile. */
+    parcelable PrimaryCurProfilePath {
+        /** The user ID of the user that owns the profile. */
+        int userId;
+        /** The name of the package. */
+        @utf8InCpp String packageName;
+        /** The stem of the profile file */
+        @utf8InCpp String profileName;
+    }
+
+    /** Represents a reference profile of a secondary dex file. */
+    parcelable SecondaryRefProfilePath {
+        /**
+         * The path to the dex file that the profile is next to.
+         *
+         * Currently, possible paths are in the format of
+         * `{/data,/mnt/expand/<volume-uuid>}/{user,user_de}/<user-id>/<package-name>/...`.
+         */
+        @utf8InCpp String dexPath;
+    }
+
+    /** Represents a current profile of a secondary dex file. */
+    parcelable SecondaryCurProfilePath {
+        /** The path to the dex file that the profile is next to. */
+        @utf8InCpp String dexPath;
+    }
+
+    /** All types of profile paths that artd can write to. */
+    union WritableProfilePath {
+        PrimaryRefProfilePath forPrimary;
+        SecondaryRefProfilePath forSecondary;
+    }
+
+    /** Represents a temporary profile. */
+    parcelable TmpProfilePath {
+        /** The path that this temporary file will eventually be committed to. */
+        WritableProfilePath finalPath;
+        /** A unique identifier to distinguish this temporary file from others. Filled by artd. */
+        @utf8InCpp String id;
+        /** The path to the temporary file. Filled by artd. */
+        @utf8InCpp String tmpPath;
+    }
+}
diff --git a/artd/binder/com/android/server/art/VdexPath.aidl b/artd/binder/com/android/server/art/VdexPath.aidl
new file mode 100644
index 0000000..6112e7a
--- /dev/null
+++ b/artd/binder/com/android/server/art/VdexPath.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a VDEX file.
+ *
+ * @hide
+ */
+union VdexPath {
+    /** Represents a VDEX file as part of the artifacts. */
+    com.android.server.art.ArtifactsPath artifactsPath;
+}
diff --git a/artd/file_utils.cc b/artd/file_utils.cc
new file mode 100644
index 0000000..53fe9f9
--- /dev/null
+++ b/artd/file_utils.cc
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2022 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 "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+#include "fmt/format.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::make_scope_guard;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+void UnlinkIfExists(const std::string& path) {
+  std::error_code ec;
+  if (!std::filesystem::remove(path, ec)) {
+    if (ec.value() != ENOENT) {
+      LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message());
+    }
+  }
+}
+
+}  // namespace
+
+Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
+                                                 const FsPermission& fs_permission) {
+  std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
+  OR_RETURN(output_file->Init());
+  return output_file;
+}
+
+NewFile::~NewFile() { Cleanup(); }
+
+Result<void> NewFile::Keep() {
+  if (close(std::exchange(fd_, -1)) != 0) {
+    return ErrnoErrorf("Failed to close file '{}'", temp_path_);
+  }
+  return {};
+}
+
+Result<void> NewFile::CommitOrAbandon() {
+  auto cleanup = make_scope_guard([this] { Unlink(); });
+  OR_RETURN(Keep());
+  std::error_code ec;
+  std::filesystem::rename(temp_path_, final_path_, ec);
+  if (ec) {
+    return Errorf(
+        "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message());
+  }
+  cleanup.Disable();
+  committed_ = true;
+  return {};
+}
+
+void NewFile::Cleanup() {
+  if (fd_ >= 0) {
+    Unlink();
+    if (close(std::exchange(fd_, -1)) != 0) {
+      // Nothing we can do. If the file is already unlinked, it will go away when the process exits.
+      PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'";
+    }
+  }
+}
+
+Result<void> NewFile::Init() {
+  mode_t mode = FileFsPermissionToMode(fs_permission_);
+  // "<path_>.XXXXXX.tmp".
+  temp_path_ = BuildTempPath(final_path_, "XXXXXX");
+  fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4);
+  if (fd_ < 0) {
+    return ErrnoErrorf("Failed to create temp file for '{}'", final_path_);
+  }
+  temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6);
+  if (fchmod(fd_, mode) != 0) {
+    return ErrnoErrorf("Failed to chmod file '{}'", temp_path_);
+  }
+  OR_RETURN(Chown(temp_path_, fs_permission_));
+  return {};
+}
+
+void NewFile::Unlink() {
+  // This should never fail. We were able to create the file, so we should be able to remove it.
+  UnlinkIfExists(temp_path_);
+}
+
+Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit,
+                                         const std::vector<std::string_view>& files_to_remove) {
+  std::vector<std::pair<std::string_view, std::string>> moved_files;
+
+  auto cleanup = make_scope_guard([&]() {
+    // Clean up new files.
+    for (NewFile* new_file : files_to_commit) {
+      if (new_file->committed_) {
+        UnlinkIfExists(new_file->FinalPath());
+      } else {
+        new_file->Cleanup();
+      }
+    }
+
+    // Move old files back.
+    for (const auto& [original_path, temp_path] : moved_files) {
+      std::error_code ec;
+      std::filesystem::rename(temp_path, original_path, ec);
+      if (ec) {
+        // This should never happen. We were able to move the file from `original_path` to
+        // `temp_path`. We should be able to move it back.
+        LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format(
+            original_path, temp_path, ec.message());
+      }
+    }
+  });
+
+  // Move old files to temporary locations.
+  std::vector<std::string_view> all_files_to_remove;
+  all_files_to_remove.reserve(files_to_commit.size() + files_to_remove.size());
+  for (NewFile* file : files_to_commit) {
+    all_files_to_remove.push_back(file->FinalPath());
+  }
+  all_files_to_remove.insert(
+      all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end());
+
+  for (std::string_view original_path : all_files_to_remove) {
+    std::error_code ec;
+    std::filesystem::file_status status = std::filesystem::status(original_path, ec);
+    if (!std::filesystem::status_known(status)) {
+      return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message());
+    }
+    if (std::filesystem::is_directory(status)) {
+      return ErrnoErrorf("Old file '{}' is a directory", original_path);
+    }
+    if (std::filesystem::exists(status)) {
+      std::string temp_path = BuildTempPath(original_path, "XXXXXX");
+      int fd = mkstemps(temp_path.data(), /*suffixlen=*/4);
+      if (fd < 0) {
+        return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path);
+      }
+      close(fd);
+
+      std::filesystem::rename(original_path, temp_path, ec);
+      if (ec) {
+        UnlinkIfExists(temp_path);
+        return Errorf("Failed to move old file '{}' to temporary path '{}': {}",
+                      original_path,
+                      temp_path,
+                      ec.message());
+      }
+
+      moved_files.push_back({original_path, std::move(temp_path)});
+    }
+  }
+
+  // Commit new files.
+  for (NewFile* file : files_to_commit) {
+    OR_RETURN(file->CommitOrAbandon());
+  }
+
+  cleanup.Disable();
+
+  // Clean up old files.
+  for (const auto& [original_path, temp_path] : moved_files) {
+    // This should never fail.  We were able to move the file to `temp_path`. We should be able to
+    // remove it.
+    UnlinkIfExists(temp_path);
+  }
+
+  return {};
+}
+
+std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) {
+  return "{}.{}.tmp"_format(final_path, id);
+}
+
+Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
+  std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str()));
+  if (file == nullptr) {
+    return ErrnoErrorf("Failed to open file '{}'", path);
+  }
+  return file;
+}
+
+mode_t FileFsPermissionToMode(const FsPermission& fs_permission) {
+  return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) |
+         (fs_permission.isOtherExecutable ? S_IXOTH : 0);
+}
+
+mode_t DirFsPermissionToMode(const FsPermission& fs_permission) {
+  return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP;
+}
+
+Result<void> Chown(const std::string& path, const FsPermission& fs_permission) {
+  if (fs_permission.uid < 0 && fs_permission.gid < 0) {
+    // Keep the default owner.
+  } else if (fs_permission.uid < 0 || fs_permission.gid < 0) {
+    return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.",
+                  fs_permission.uid,
+                  fs_permission.gid);
+  }
+  if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) {
+    return ErrnoErrorf("Failed to chown '{}'", path);
+  }
+  return {};
+}
+
+}  // namespace artd
+}  // namespace art
diff --git a/artd/file_utils.h b/artd/file_utils.h
new file mode 100644
index 0000000..b5fd170
--- /dev/null
+++ b/artd/file_utils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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_ARTD_FILE_UTILS_H_
+#define ART_ARTD_FILE_UTILS_H_
+
+#include <sys/types.h>
+
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/result.h"
+#include "base/os.h"
+
+namespace art {
+namespace artd {
+
+// A class that creates a new file that will eventually be committed to the given path. The new file
+// is created at a temporary location. It will not overwrite the file at the given path until
+// `CommitOrAbandon` has been called and will be automatically cleaned up on object destruction
+// unless `CommitOrAbandon` has been called.
+// The new file is opened without O_CLOEXEC so that it can be passed to subprocesses.
+class NewFile {
+ public:
+  // Creates a new file at the given path with the given permission.
+  static android::base::Result<std::unique_ptr<NewFile>> Create(
+      const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+  NewFile(const NewFile&) = delete;
+  NewFile& operator=(const NewFile&) = delete;
+  NewFile(NewFile&& other) noexcept
+      : fd_(std::exchange(other.fd_, -1)),
+        final_path_(std::move(other.final_path_)),
+        temp_path_(std::move(other.temp_path_)),
+        temp_id_(std::move(other.temp_id_)),
+        fs_permission_(other.fs_permission_) {}
+
+  // Deletes the file if it is not committed.
+  virtual ~NewFile();
+
+  int Fd() const { return fd_; }
+
+  // The path that the file will eventually be committed to.
+  const std::string& FinalPath() const { return final_path_; }
+
+  // The path to the new file.
+  const std::string& TempPath() const { return temp_path_; }
+
+  // The unique ID of the new file. Can be used by `BuildTempPath` for reconstructing the path to
+  // the file.
+  const std::string& TempId() const { return temp_id_; }
+
+  // Closes the new file, keeps it, moves the file to the final path, and overwrites any existing
+  // file at that path, or abandons the file on failure. The fd will be invalid after this function
+  // is called.
+  android::base::Result<void> CommitOrAbandon();
+
+  // Closes the new file and keeps it at the temporary location. The file will not be automatically
+  // cleaned up on object destruction. The file can be found at `TempPath()` (i.e.,
+  // `BuildTempPath(FinalPath(), TempId())`). The fd will be invalid after this function is called.
+  virtual android::base::Result<void> Keep();
+
+  // Unlinks and closes the new file if it is not committed. The fd will be invalid after this
+  // function is called.
+  void Cleanup();
+
+  // Commits all new files, replacing old files, and removes given files in addition. Or abandons
+  // new files and restores old files at best effort if any error occurs. The fds will be invalid
+  // after this function is called.
+  //
+  // Note: This function is NOT thread-safe. It is intended to be used in single-threaded code or in
+  // cases where some race condition is acceptable.
+  //
+  // Usage:
+  //
+  // Commit `file_1` and `file_2`, and remove the file at "path_3":
+  //   CommitAllOrAbandon({file_1, file_2}, {"path_3"});
+  static android::base::Result<void> CommitAllOrAbandon(
+      const std::vector<NewFile*>& files_to_commit,
+      const std::vector<std::string_view>& files_to_remove = {});
+
+  // Returns the path to a temporary file. See `Keep`.
+  static std::string BuildTempPath(std::string_view final_path, const std::string& id);
+
+ private:
+  NewFile(const std::string& path,
+          const aidl::com::android::server::art::FsPermission& fs_permission)
+      : final_path_(path), fs_permission_(fs_permission) {}
+
+  android::base::Result<void> Init();
+
+  // Unlinks the new file. The fd will still be valid after this function is called.
+  void Unlink();
+
+  int fd_ = -1;
+  std::string final_path_;
+  std::string temp_path_;
+  std::string temp_id_;
+  aidl::com::android::server::art::FsPermission fs_permission_;
+  bool committed_ = false;
+};
+
+// Opens a file for reading.
+android::base::Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path);
+
+// Converts FsPermission to Linux access mode for a file.
+mode_t FileFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Converts FsPermission to Linux access mode for a directory.
+mode_t DirFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Changes the owner based on FsPermission.
+android::base::Result<void> Chown(
+    const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+}  // namespace artd
+}  // namespace art
+
+#endif  // ART_ARTD_FILE_UTILS_H_
diff --git a/artd/file_utils_test.cc b/artd/file_utils_test.cc
new file mode 100644
index 0000000..8f79d5d
--- /dev/null
+++ b/artd/file_utils_test.cc
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2022 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 "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/result-gmock.h"
+#include "android-base/result.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::Error;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::Ok;
+using ::android::base::testing::WithMessage;
+using ::testing::ContainsRegex;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+  std::string actual_content;
+  ASSERT_TRUE(ReadFileToString(path, &actual_content));
+  EXPECT_EQ(actual_content, expected_content);
+}
+
+// A file that will always fail on `Commit`.
+class UncommittableFile : public NewFile {
+ public:
+  static Result<std::unique_ptr<UncommittableFile>> Create(const std::string& path,
+                                                           const FsPermission& fs_permission) {
+    std::unique_ptr<NewFile> new_file = OR_RETURN(NewFile::Create(path, fs_permission));
+    return std::unique_ptr<UncommittableFile>(new UncommittableFile(std::move(*new_file)));
+  }
+
+  Result<void> Keep() override { return Error() << "Uncommittable file"; }
+
+ private:
+  explicit UncommittableFile(NewFile&& other) : NewFile(std::move(other)) {}
+};
+
+class FileUtilsTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    struct stat st;
+    ASSERT_EQ(stat(scratch_dir_->GetPath().c_str(), &st), 0);
+    fs_permission_ = FsPermission{.uid = static_cast<int32_t>(st.st_uid),
+                                  .gid = static_cast<int32_t>(st.st_gid)};
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  FsPermission fs_permission_;
+  std::unique_ptr<ScratchDir> scratch_dir_;
+};
+
+TEST_F(FileUtilsTest, NewFileCreate) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+
+  Result<std::unique_ptr<NewFile>> new_file = NewFile::Create(path, fs_permission_);
+  ASSERT_THAT(new_file, HasValue(NotNull()));
+  EXPECT_GE((*new_file)->Fd(), 0);
+  EXPECT_EQ((*new_file)->FinalPath(), path);
+  EXPECT_THAT((*new_file)->TempPath(), Not(IsEmpty()));
+  EXPECT_THAT((*new_file)->TempId(), Not(IsEmpty()));
+
+  EXPECT_FALSE(std::filesystem::exists((*new_file)->FinalPath()));
+  EXPECT_TRUE(std::filesystem::exists((*new_file)->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCreateNonExistentDir) {
+  std::string path = scratch_dir_->GetPath() + "/non_existent_dir/file.tmp";
+
+  EXPECT_THAT(NewFile::Create(path, fs_permission_),
+              HasError(WithMessage(
+                  ContainsRegex("Failed to create temp file for .*/non_existent_dir/file.tmp"))));
+}
+
+TEST_F(FileUtilsTest, NewFileExplicitCleanup) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+  new_file->Cleanup();
+
+  EXPECT_FALSE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(new_file->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileImplicitCleanup) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::string temp_path;
+
+  // Cleanup on object destruction.
+  {
+    std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+    temp_path = new_file->TempPath();
+  }
+
+  EXPECT_FALSE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommit) {
+  std::string path = scratch_dir_->GetPath() + "/file.tmp";
+  std::string temp_path;
+
+  {
+    std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+    temp_path = new_file->TempPath();
+    new_file->CommitOrAbandon();
+  }
+
+  EXPECT_TRUE(std::filesystem::exists(path));
+  EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllNoOldFile) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesLessOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));  // No old_file_2.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesMoreOldFiles) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              Ok());
+
+  // New files are committed.
+  CheckContent(file_1_path, "new_file_1");
+  CheckContent(file_2_path, "new_file_2");
+  EXPECT_FALSE(std::filesystem::exists(file_3_path));  // Extra file removed.
+
+  // New files are no longer at the temporary paths.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToCommit) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  // Uncommittable file.
+  std::unique_ptr<NewFile> new_file_2 =
+      OR_FATAL(UncommittableFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              HasError(WithMessage("Uncommittable file")));
+
+  // Old files are fine.
+  CheckContent(file_1_path, "old_file_1");
+  CheckContent(file_2_path, "old_file_2");
+  CheckContent(file_3_path, "old_file_3");
+
+  // New files are abandoned.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToMoveOldFile) {
+  std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+  std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+  std::filesystem::create_directory(file_2_path);
+  std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+  ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+  ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path));  // Extra file.
+
+  std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+  std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+  ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+  ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+  // file_2 is not movable because it is a directory.
+  EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+              HasError(WithMessage(ContainsRegex("Old file '.*/file_2' is a directory"))));
+
+  // Old files are fine.
+  CheckContent(file_1_path, "old_file_1");
+  EXPECT_TRUE(std::filesystem::is_directory(file_2_path));
+  CheckContent(file_3_path, "old_file_3");
+
+  // New files are abandoned.
+  EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+  EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, BuildTempPath) {
+  EXPECT_EQ(NewFile::BuildTempPath("/a/b/original_path", "123456"),
+            "/a/b/original_path.123456.tmp");
+}
+
+TEST_F(FileUtilsTest, OpenFileForReading) {
+  std::string path = scratch_dir_->GetPath() + "/foo";
+  ASSERT_TRUE(WriteStringToFile("foo", path));
+
+  EXPECT_THAT(OpenFileForReading(path), HasValue(NotNull()));
+}
+
+TEST_F(FileUtilsTest, OpenFileForReadingFailed) {
+  std::string path = scratch_dir_->GetPath() + "/foo";
+
+  EXPECT_THAT(OpenFileForReading(path),
+              HasError(WithMessage(ContainsRegex("Failed to open file .*/foo"))));
+}
+
+TEST_F(FileUtilsTest, FileFsPermissionToMode) {
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP);
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+  EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH);
+  EXPECT_EQ(
+      FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH);
+}
+
+TEST_F(FileUtilsTest, DirFsPermissionToMode) {
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH);
+  EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+            S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+}
+
+}  // namespace
+}  // namespace artd
+}  // namespace art
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
new file mode 100644
index 0000000..295b023
--- /dev/null
+++ b/artd/path_utils.cc
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2022 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 "path_utils.h"
+
+#include <filesystem>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/errors.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
+#include "base/file_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::EndsWith;
+using ::android::base::Error;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+using WritableProfilePath = ProfilePath::WritableProfilePath;
+
+Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
+  if (path_str.empty()) {
+    return Errorf("Path is empty");
+  }
+  if (path_str.find('\0') != std::string::npos) {
+    return Errorf("Path '{}' has invalid character '\\0'", path_str);
+  }
+  std::filesystem::path path(path_str);
+  if (!path.is_absolute()) {
+    return Errorf("Path '{}' is not an absolute path", path_str);
+  }
+  if (path.lexically_normal() != path_str) {
+    return Errorf("Path '{}' is not in normal form", path_str);
+  }
+  return {};
+}
+
+Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+                                          const std::string& name) {
+  if (path_element_substring.empty()) {
+    return Errorf("{} is empty", name);
+  }
+  if (path_element_substring.find('/') != std::string::npos) {
+    return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
+  }
+  if (path_element_substring.find('\0') != std::string::npos) {
+    return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
+  }
+  return {};
+}
+
+Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
+  OR_RETURN(ValidatePathElementSubstring(path_element, name));
+  if (path_element == "." || path_element == "..") {
+    return Errorf("Invalid {} '{}'", name, path_element);
+  }
+  return {};
+}
+
+Result<std::string> GetAndroidDataOrError() {
+  std::string error_msg;
+  std::string result = GetAndroidDataSafe(&error_msg);
+  if (!error_msg.empty()) {
+    return Error() << error_msg;
+  }
+  return result;
+}
+
+Result<std::string> GetArtRootOrError() {
+  std::string error_msg;
+  std::string result = GetArtRootSafe(&error_msg);
+  if (!error_msg.empty()) {
+    return Error() << error_msg;
+  }
+  return result;
+}
+
+}  // namespace
+
+Result<void> ValidateDexPath(const std::string& dex_path) {
+  OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+  if (!EndsWith(dex_path, ".apk") && !EndsWith(dex_path, ".jar")) {
+    return Errorf("Dex path '{}' has an invalid extension", dex_path);
+  }
+  return {};
+}
+
+Result<std::string> BuildArtBinPath(const std::string& binary_name) {
+  return "{}/bin/{}"_format(OR_RETURN(GetArtRootOrError()), binary_name);
+}
+
+Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) {
+  OR_RETURN(ValidateDexPath(artifacts_path.dexPath));
+
+  InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str());
+  if (isa == InstructionSet::kNone) {
+    return Errorf("Instruction set '{}' is invalid", artifacts_path.isa);
+  }
+
+  std::string error_msg;
+  std::string path;
+  if (artifacts_path.isInDalvikCache) {
+    // Apps' OAT files are never in ART APEX data.
+    if (!OatFileAssistant::DexLocationToOatFilename(
+            artifacts_path.dexPath, isa, /*deny_art_apex_data_files=*/true, &path, &error_msg)) {
+      return Error() << error_msg;
+    }
+    return path;
+  } else {
+    if (!OatFileAssistant::DexLocationToOdexFilename(
+            artifacts_path.dexPath, isa, &path, &error_msg)) {
+      return Error() << error_msg;
+    }
+    return path;
+  }
+}
+
+Result<std::string> BuildPrimaryRefProfilePath(
+    const PrimaryRefProfilePath& primary_ref_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_ref_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_ref_profile_path.profileName, "profileName"));
+  return "{}/misc/profiles/ref/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+                                                  primary_ref_profile_path.packageName,
+                                                  primary_ref_profile_path.profileName);
+}
+
+Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) {
+  OR_RETURN(ValidateDexPath(prebuilt_profile_path.dexPath));
+  return prebuilt_profile_path.dexPath + ".prof";
+}
+
+Result<std::string> BuildPrimaryCurProfilePath(
+    const PrimaryCurProfilePath& primary_cur_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_cur_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_cur_profile_path.profileName, "profileName"));
+  return "{}/misc/profiles/cur/{}/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+                                                     primary_cur_profile_path.userId,
+                                                     primary_cur_profile_path.packageName,
+                                                     primary_cur_profile_path.profileName);
+}
+
+Result<std::string> BuildSecondaryRefProfilePath(
+    const SecondaryRefProfilePath& secondary_ref_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_ref_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_ref_profile_path.dexPath);
+  return "{}/oat/{}.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildSecondaryCurProfilePath(
+    const SecondaryCurProfilePath& secondary_cur_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_cur_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_cur_profile_path.dexPath);
+  return "{}/oat/{}.cur.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) {
+  const WritableProfilePath& final_path = tmp_profile_path.finalPath;
+  switch (final_path.getTag()) {
+    case WritableProfilePath::forPrimary:
+      return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>());
+    case WritableProfilePath::forSecondary:
+      return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>());
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected writable profile path type {}"_format(final_path.getTag());
+}
+
+Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) {
+  OR_RETURN(ValidatePathElementSubstring(tmp_profile_path.id, "id"));
+  return NewFile::BuildTempPath(OR_RETURN(BuildFinalProfilePath(tmp_profile_path)),
+                                tmp_profile_path.id);
+}
+
+Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) {
+  OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath));
+  return ReplaceFileExtension(dex_metadata_path.dexPath, "dm");
+}
+
+Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
+  switch (profile_path.getTag()) {
+    case ProfilePath::primaryRefProfilePath:
+      return BuildPrimaryRefProfilePath(profile_path.get<ProfilePath::primaryRefProfilePath>());
+    case ProfilePath::prebuiltProfilePath:
+      return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>());
+    case ProfilePath::primaryCurProfilePath:
+      return BuildPrimaryCurProfilePath(profile_path.get<ProfilePath::primaryCurProfilePath>());
+    case ProfilePath::secondaryRefProfilePath:
+      return BuildSecondaryRefProfilePath(profile_path.get<ProfilePath::secondaryRefProfilePath>());
+    case ProfilePath::secondaryCurProfilePath:
+      return BuildSecondaryCurProfilePath(profile_path.get<ProfilePath::secondaryCurProfilePath>());
+    case ProfilePath::tmpProfilePath:
+      return BuildTmpProfilePath(profile_path.get<ProfilePath::tmpProfilePath>());
+    case ProfilePath::dexMetadataPath:
+      return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>());
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected profile path type {}"_format(profile_path.getTag());
+}
+
+Result<std::string> BuildVdexPath(const VdexPath& vdex_path) {
+  DCHECK(vdex_path.getTag() == VdexPath::artifactsPath);
+  return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>())));
+}
+
+}  // namespace artd
+}  // namespace art
diff --git a/artd/path_utils.h b/artd/path_utils.h
new file mode 100644
index 0000000..0cc017e
--- /dev/null
+++ b/artd/path_utils.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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_ARTD_PATH_UTILS_H_
+#define ART_ARTD_PATH_UTILS_H_
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace artd {
+
+android::base::Result<void> ValidateDexPath(const std::string& dex_path);
+
+android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name);
+
+// Returns the absolute path to the OAT file built from the `ArtifactsPath`.
+android::base::Result<std::string> BuildOatPath(
+    const aidl::com::android::server::art::ArtifactsPath& artifacts_path);
+
+// Returns the path to the VDEX file that corresponds to the OAT file.
+inline std::string OatPathToVdexPath(const std::string& oat_path) {
+  return ReplaceFileExtension(oat_path, "vdex");
+}
+
+// Returns the path to the ART file that corresponds to the OAT file.
+inline std::string OatPathToArtPath(const std::string& oat_path) {
+  return ReplaceFileExtension(oat_path, "art");
+}
+
+android::base::Result<std::string> BuildPrimaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryRefProfilePath&
+        primary_ref_profile_path);
+
+android::base::Result<std::string> BuildPrebuiltProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path);
+
+android::base::Result<std::string> BuildPrimaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryCurProfilePath&
+        primary_cur_profile_path);
+
+android::base::Result<std::string> BuildSecondaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryRefProfilePath&
+        secondary_ref_profile_path);
+
+android::base::Result<std::string> BuildSecondaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath&
+        secondary_cur_profile_path);
+
+android::base::Result<std::string> BuildFinalProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildTmpProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildDexMetadataPath(
+    const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path);
+
+android::base::Result<std::string> BuildProfileOrDmPath(
+    const aidl::com::android::server::art::ProfilePath& profile_path);
+
+android::base::Result<std::string> BuildVdexPath(
+    const aidl::com::android::server::art::VdexPath& vdex_path);
+
+}  // namespace artd
+}  // namespace art
+
+#endif  // ART_ARTD_PATH_UTILS_H_
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
new file mode 100644
index 0000000..b0e1a25
--- /dev/null
+++ b/artd/path_utils_test.cc
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 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 "path_utils.h"
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result-gmock.h"
+#include "base/common_art_test.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::WithMessage;
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using std::literals::operator""s;  // NOLINT
+
+class PathUtilsTest : public CommonArtTest {};
+
+TEST_F(PathUtilsTest, BuildArtBinPath) {
+  auto scratch_dir = std::make_unique<ScratchDir>();
+  auto art_root_env = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+  setenv("ANDROID_ART_ROOT", scratch_dir->GetPath().c_str(), /*overwrite=*/1);
+  EXPECT_THAT(BuildArtBinPath("foo"), HasValue(scratch_dir->GetPath() + "/bin/foo"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPath) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasValue("/a/oat/arm64/b.odex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathDalvikCache) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = true}),
+      HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.dex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path is empty")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) {
+  EXPECT_THAT(
+      BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasError(WithMessage("Path 'a/b.apk' is not an absolute path")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{
+                  .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNul) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{
+                  .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidDexExtension) {
+  EXPECT_THAT(BuildOatPath(ArtifactsPath{
+                  .dexPath = "/a/b.invalid", .isa = "arm64", .isInDalvikCache = false}),
+              HasError(WithMessage("Dex path '/a/b.invalid' has an invalid extension")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) {
+  EXPECT_THAT(BuildOatPath(
+                  ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}),
+              HasError(WithMessage("Instruction set 'invalid' is invalid")));
+}
+
+TEST_F(PathUtilsTest, OatPathToVdexPath) {
+  EXPECT_EQ(OatPathToVdexPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.vdex");
+}
+
+TEST_F(PathUtilsTest, OatPathToArtPath) {
+  EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePath) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}),
+              HasError(WithMessage("packageName is empty")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}),
+              HasError(WithMessage("Invalid packageName '.'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}),
+              HasError(WithMessage("Invalid packageName '..'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}),
+              HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
+              HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "!@#$%^&*()_+-="}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
+              HasError(WithMessage("profileName is empty")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
+              HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
+              HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForSecondary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForPrimary) {
+  EXPECT_THAT(
+      BuildTmpProfilePath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+          .id = "12345"}),
+      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForSecondary) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = ""}),
+              HasError(WithMessage("id is empty")));
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "123/45"}),
+              HasError(WithMessage("id '123/45' has invalid character '/'")));
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "123\0a"s}),
+              HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) {
+  EXPECT_THAT(BuildPrebuiltProfilePath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+              HasValue("/a/b.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryCurProfilePath) {
+  EXPECT_THAT(BuildPrimaryCurProfilePath(PrimaryCurProfilePath{
+                  .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryRefProfilePath) {
+  EXPECT_THAT(BuildSecondaryRefProfilePath(SecondaryRefProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryCurProfilePath) {
+  EXPECT_THAT(BuildSecondaryCurProfilePath(SecondaryCurProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.cur.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildDexMetadataPath) {
+  EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildProfilePath) {
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                         .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+  EXPECT_THAT(
+      BuildProfileOrDmPath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+          .id = "12345"}),
+      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+  EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+              HasValue("/a/b.apk.prof"));
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryCurProfilePath{
+                  .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+              HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+  EXPECT_THAT(BuildProfileOrDmPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildVdexPath) {
+  EXPECT_THAT(
+      BuildVdexPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+      HasValue("/a/oat/arm64/b.vdex"));
+}
+
+}  // namespace
+}  // namespace artd
+}  // namespace art
diff --git a/artd/testing.h b/artd/testing.h
new file mode 100644
index 0000000..df01a9a
--- /dev/null
+++ b/artd/testing.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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_ARTD_TESTING_H_
+#define ART_ARTD_TESTING_H_
+
+// Returns the value of the given `android::base::Result`, or reports the error as a gMock matcher
+// mismatch. This is only to be used in a gMock matcher.
+#define OR_MISMATCH(expr)                          \
+  ({                                               \
+    decltype(expr)&& tmp__ = (expr);               \
+    if (!tmp__.ok()) {                             \
+      *result_listener << tmp__.error().message(); \
+      return false;                                \
+    }                                              \
+    std::move(tmp__).value();                      \
+  })
+
+// Returns the value of the given `android::base::Result`, or fails the GoogleTest.
+#define OR_FAIL(expr)                                   \
+  ({                                                    \
+    decltype(expr)&& tmp__ = (expr);                    \
+    ASSERT_TRUE(tmp__.ok()) << tmp__.error().message(); \
+    std::move(tmp__).value();                           \
+  })
+
+#endif  // ART_ARTD_TESTING_H_
diff --git a/build/Android.bp b/build/Android.bp
index 504ed5b..54eb639 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -42,6 +42,7 @@
     "performance-faster-string-find",
     "performance-for-range-copy",
     "performance-implicit-conversion-in-loop",
+    "performance-inefficient-vector-operation",
     "performance-no-automatic-move",
     "performance-noexcept-move-constructor",
     "performance-unnecessary-copy-initialization",
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 504ce92..2a76057 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -271,6 +271,7 @@
         "libartservice",
     ],
     binaries: [
+        "art_exec",
         "artd",
     ],
     multilib: {
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 6e58cf6..b56c501 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -550,6 +550,7 @@
     # removed in Android R.
 
     # Check binaries for ART.
+    self._checker.check_executable('art_exec')
     self._checker.check_executable('artd')
     self._checker.check_executable('oatdump')
     self._checker.check_executable("odrefresh")
diff --git a/build/boot/boot-image-profile.txt b/build/boot/boot-image-profile.txt
index ca2f8f2..34b919b 100644
--- a/build/boot/boot-image-profile.txt
+++ b/build/boot/boot-image-profile.txt
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+HSPLandroid/compat/Compatibility$BehaviorChangeDelegate;->isChangeEnabled(J)Z
 HSPLandroid/compat/Compatibility;->isChangeEnabled(J)Z
 HSPLandroid/compat/Compatibility;->setBehaviorChangeDelegate(Landroid/compat/Compatibility$BehaviorChangeDelegate;)V
 HSPLandroid/system/ErrnoException;-><init>(Ljava/lang/String;I)V
@@ -33,7 +34,7 @@
 HSPLandroid/system/Os;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;
 HSPLandroid/system/Os;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
 HSPLandroid/system/Os;->getpgid(I)I
-HSPLandroid/system/Os;->getpid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLandroid/system/Os;->getpid()I
 HSPLandroid/system/Os;->gettid()I
 HSPLandroid/system/Os;->getuid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLandroid/system/Os;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
@@ -415,7 +416,7 @@
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSink;->write(Lcom/android/okhttp/okio/Buffer;J)V
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;-><init>(Lcom/android/okhttp/internal/http/Http1xStream;J)V
 HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->close()V
-HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->read(Lcom/android/okhttp/okio/Buffer;J)J
+HSPLcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;]Lcom/android/okhttp/okio/BufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
 HSPLcom/android/okhttp/internal/http/Http1xStream;-><init>(Lcom/android/okhttp/internal/http/StreamAllocation;Lcom/android/okhttp/okio/BufferedSource;Lcom/android/okhttp/okio/BufferedSink;)V
 HSPLcom/android/okhttp/internal/http/Http1xStream;->access$300(Lcom/android/okhttp/internal/http/Http1xStream;)Lcom/android/okhttp/okio/BufferedSink;
 HSPLcom/android/okhttp/internal/http/Http1xStream;->access$400(Lcom/android/okhttp/internal/http/Http1xStream;Lcom/android/okhttp/okio/ForwardingTimeout;)V
@@ -669,7 +670,7 @@
 HSPLcom/android/okhttp/okio/Buffer;->getByte(J)B
 HSPLcom/android/okhttp/okio/Buffer;->indexOf(BJ)J
 HSPLcom/android/okhttp/okio/Buffer;->read(Lcom/android/okhttp/okio/Buffer;J)J
-HSPLcom/android/okhttp/okio/Buffer;->read([BII)I
+HSPLcom/android/okhttp/okio/Buffer;->read([BII)I+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
 HSPLcom/android/okhttp/okio/Buffer;->readByte()B
 HSPLcom/android/okhttp/okio/Buffer;->readByteArray()[B
 HSPLcom/android/okhttp/okio/Buffer;->readByteArray(J)[B
@@ -686,7 +687,7 @@
 HSPLcom/android/okhttp/okio/Buffer;->size()J
 HSPLcom/android/okhttp/okio/Buffer;->skip(J)V
 HSPLcom/android/okhttp/okio/Buffer;->writableSegment(I)Lcom/android/okhttp/okio/Segment;
-HSPLcom/android/okhttp/okio/Buffer;->write(Lcom/android/okhttp/okio/Buffer;J)V
+HSPLcom/android/okhttp/okio/Buffer;->write(Lcom/android/okhttp/okio/Buffer;J)V+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
 HSPLcom/android/okhttp/okio/Buffer;->write([BII)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeByte(I)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeHexadecimalUnsignedLong(J)Lcom/android/okhttp/okio/Buffer;
@@ -717,7 +718,7 @@
 HSPLcom/android/okhttp/okio/Okio$1;->flush()V
 HSPLcom/android/okhttp/okio/Okio$1;->write(Lcom/android/okhttp/okio/Buffer;J)V
 HSPLcom/android/okhttp/okio/Okio$2;-><init>(Lcom/android/okhttp/okio/Timeout;Ljava/io/InputStream;)V
-HSPLcom/android/okhttp/okio/Okio$2;->read(Lcom/android/okhttp/okio/Buffer;J)J
+HSPLcom/android/okhttp/okio/Okio$2;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Timeout;Lcom/android/okhttp/okio/Okio$3;
 HSPLcom/android/okhttp/okio/Okio$3;-><init>(Ljava/net/Socket;)V
 HSPLcom/android/okhttp/okio/Okio$3;->newTimeoutException(Ljava/io/IOException;)Ljava/io/IOException;
 HSPLcom/android/okhttp/okio/Okio$3;->timedOut()V
@@ -749,7 +750,7 @@
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->available()I
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->close()V
 HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read()I
-HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read([BII)I
+HSPLcom/android/okhttp/okio/RealBufferedSource$1;->read([BII)I+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/internal/http/Http1xStream$FixedLengthSource;
 HSPLcom/android/okhttp/okio/RealBufferedSource;-><init>(Lcom/android/okhttp/okio/Source;)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;-><init>(Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/Buffer;)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->access$000(Lcom/android/okhttp/okio/RealBufferedSource;)Z
@@ -759,7 +760,7 @@
 HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J
 HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J
 HSPLcom/android/okhttp/okio/RealBufferedSource;->inputStream()Ljava/io/InputStream;
-HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readIntLe()I
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readShort()S
@@ -801,14 +802,14 @@
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;-><init>(Ljava/io/InputStream;IZ)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;-><init>([B)V
-HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->buildObject(III)Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
+HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->buildObject(III)Lcom/android/org/bouncycastle/asn1/ASN1Primitive;+]Lcom/android/org/bouncycastle/asn1/ASN1InputStream;Lcom/android/org/bouncycastle/asn1/ASN1InputStream;]Lcom/android/org/bouncycastle/asn1/ASN1StreamParser;Lcom/android/org/bouncycastle/asn1/ASN1StreamParser;
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->createPrimitiveDERObject(ILcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->getBuffer(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)[B
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength()I
-HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I+]Ljava/io/InputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;,Lcom/android/org/bouncycastle/asn1/ASN1InputStream;
+HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readObject()Lcom/android/org/bouncycastle/asn1/ASN1Primitive;
 HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readTagNumber(Ljava/io/InputStream;I)I
-HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;]Lcom/android/org/bouncycastle/asn1/ASN1InputStream;Lcom/android/org/bouncycastle/asn1/ASN1InputStream;]Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;
+HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>(Ljava/math/BigInteger;)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>([BZ)V
 HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;->encode(Lcom/android/org/bouncycastle/asn1/ASN1OutputStream;Z)V
@@ -878,9 +879,9 @@
 HSPLcom/android/org/bouncycastle/asn1/DLSequence;-><init>()V
 HSPLcom/android/org/bouncycastle/asn1/DLSequence;-><init>(Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;)V
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->getRemaining()I
-HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read()I
-HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read([BII)I
-HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;
+HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read()I+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;]Ljava/io/InputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;,Lcom/android/org/bouncycastle/asn1/ASN1InputStream;
+HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read([BII)I+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;]Ljava/io/InputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;,Lcom/android/org/bouncycastle/asn1/ASN1InputStream;
+HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V
 HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->toByteArray()[B
 HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;->getLimit()I
@@ -932,10 +933,10 @@
 HSPLcom/android/org/bouncycastle/crypto/engines/DESEngine;->generateWorkingKey(Z[B)[I
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->adjust([BI[B)V
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B+]Lcom/android/org/bouncycastle/crypto/Digest;Lcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest$SHA1;,Lcom/android/org/bouncycastle/crypto/digests/SHA1Digest;
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac;
-HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac;
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V
+HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedMacParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters;
 HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters;
 HSPLcom/android/org/bouncycastle/crypto/macs/HMac;-><clinit>()V
@@ -960,7 +961,7 @@
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getOutputSize(I)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getUpdateOutputSize(I)I
 HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->init(ZLcom/android/org/bouncycastle/crypto/CipherParameters;)V
-HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I+]Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;]Lcom/android/org/bouncycastle/crypto/BlockCipher;Lcom/android/org/bouncycastle/crypto/modes/CBCBlockCipher;,Lcom/android/org/bouncycastle/crypto/engines/AESEngine;
+HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I
 HSPLcom/android/org/bouncycastle/crypto/params/AsymmetricKeyParameter;-><init>(Z)V
 HSPLcom/android/org/bouncycastle/crypto/params/DSAKeyParameters;-><init>(ZLcom/android/org/bouncycastle/crypto/params/DSAParameters;)V
 HSPLcom/android/org/bouncycastle/crypto/params/DSAParameters;-><init>(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)V
@@ -1033,10 +1034,10 @@
 HSPLcom/android/org/bouncycastle/util/io/Streams;->readFully(Ljava/io/InputStream;[B)I
 HSPLcom/android/org/bouncycastle/util/io/Streams;->readFully(Ljava/io/InputStream;[BII)I
 HSPLcom/android/org/kxml2/io/KXmlParser;-><init>()V
-HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z
+HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z+]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->close()V
 HSPLcom/android/org/kxml2/io/KXmlParser;->ensureCapacity([Ljava/lang/String;I)[Ljava/lang/String;
-HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z
+HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z+]Ljava/io/Reader;Ljava/io/InputStreamReader;
 HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeCount()I
 HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String;
@@ -1063,9 +1064,9 @@
 HSPLcom/android/org/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readEndTag()V
 HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V
-HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String;
+HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String;
-HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String;+]Llibcore/internal/StringPool;Llibcore/internal/StringPool;
+HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool;
 HSPLcom/android/org/kxml2/io/KXmlParser;->readXmlDeclaration()V
 HSPLcom/android/org/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V
 HSPLcom/android/org/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V
@@ -1092,7 +1093,7 @@
 HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;)V
 HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;Z)V
 HSPLdalvik/system/BaseDexClassLoader;->addNativePath(Ljava/util/Collection;)V
-HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ldalvik/system/DexPathList;Ldalvik/system/DexPathList;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/ClassLoader;Ldalvik/system/PathClassLoader;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;]Ljava/lang/ClassNotFoundException;Ljava/lang/ClassNotFoundException;
+HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;
 HSPLdalvik/system/BaseDexClassLoader;->findLibrary(Ljava/lang/String;)Ljava/lang/String;
 HSPLdalvik/system/BaseDexClassLoader;->findResource(Ljava/lang/String;)Ljava/net/URL;
 HSPLdalvik/system/BaseDexClassLoader;->findResources(Ljava/lang/String;)Ljava/util/Enumeration;
@@ -1111,14 +1112,14 @@
 HSPLdalvik/system/BlockGuard$3;->initialValue()Ljava/lang/Object;
 HSPLdalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy;+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3;
 HSPLdalvik/system/BlockGuard;->getVmPolicy()Ldalvik/system/BlockGuard$VmPolicy;
-HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V
+HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3;
 HSPLdalvik/system/BlockGuard;->setVmPolicy(Ldalvik/system/BlockGuard$VmPolicy;)V
 HSPLdalvik/system/CloseGuard;-><init>()V
 HSPLdalvik/system/CloseGuard;->close()V
 HSPLdalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard;
 HSPLdalvik/system/CloseGuard;->getReporter()Ldalvik/system/CloseGuard$Reporter;
 HSPLdalvik/system/CloseGuard;->open(Ljava/lang/String;)V
-HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V
 HSPLdalvik/system/CloseGuard;->setEnabled(Z)V
 HSPLdalvik/system/CloseGuard;->setReporter(Ldalvik/system/CloseGuard$Reporter;)V
 HSPLdalvik/system/CloseGuard;->warnIfOpen()V
@@ -1173,8 +1174,10 @@
 HSPLdalvik/system/SocketTagger;->set(Ldalvik/system/SocketTagger;)V
 HSPLdalvik/system/SocketTagger;->tag(Ljava/net/Socket;)V
 HSPLdalvik/system/SocketTagger;->untag(Ljava/net/Socket;)V
+HSPLdalvik/system/VMRuntime$SdkVersionContainer;->-$$Nest$sfgetsdkVersion()I
 HSPLdalvik/system/VMRuntime;->getInstructionSet(Ljava/lang/String;)Ljava/lang/String;
 HSPLdalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;
+HSPLdalvik/system/VMRuntime;->getSdkVersion()I
 HSPLdalvik/system/VMRuntime;->getTargetSdkVersion()I
 HSPLdalvik/system/VMRuntime;->hiddenApiUsed(ILjava/lang/String;Ljava/lang/String;IZ)V
 HSPLdalvik/system/VMRuntime;->notifyNativeAllocation()V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
@@ -1186,6 +1189,8 @@
 HSPLdalvik/system/VMRuntime;->setHiddenApiUsageLogger(Ldalvik/system/VMRuntime$HiddenApiUsageLogger;)V
 HSPLdalvik/system/VMRuntime;->setNonSdkApiUsageConsumer(Ljava/util/function/Consumer;)V
 HSPLdalvik/system/VMRuntime;->setTargetSdkVersion(I)V
+HSPLdalvik/system/ZipPathValidator;->getInstance()Ldalvik/system/ZipPathValidator$Callback;
+HSPLdalvik/system/ZipPathValidator;->setCallback(Ldalvik/system/ZipPathValidator$Callback;)V
 HSPLdalvik/system/ZygoteHooks;->cleanLocaleCaches()V
 HSPLdalvik/system/ZygoteHooks;->gcAndFinalize()V
 HSPLdalvik/system/ZygoteHooks;->isIndefiniteThreadSuspensionSafe()Z
@@ -1206,18 +1211,17 @@
 HSPLjava/io/Bits;->putInt([BII)V
 HSPLjava/io/Bits;->putLong([BIJ)V
 HSPLjava/io/Bits;->putShort([BIS)V
-HSPLjava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;)V
 HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLjava/io/BufferedInputStream;->available()I
 HSPLjava/io/BufferedInputStream;->close()V
-HSPLjava/io/BufferedInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/FileInputStream;
+HSPLjava/io/BufferedInputStream;->fill()V
 HSPLjava/io/BufferedInputStream;->getBufIfOpen()[B
 HSPLjava/io/BufferedInputStream;->getInIfOpen()Ljava/io/InputStream;
 HSPLjava/io/BufferedInputStream;->mark(I)V
 HSPLjava/io/BufferedInputStream;->markSupported()Z
 HSPLjava/io/BufferedInputStream;->read()I
-HSPLjava/io/BufferedInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream;
+HSPLjava/io/BufferedInputStream;->read([BII)I
 HSPLjava/io/BufferedInputStream;->read1([BII)I
 HSPLjava/io/BufferedInputStream;->reset()V
 HSPLjava/io/BufferedInputStream;->skip(J)J
@@ -1241,12 +1245,12 @@
 HSPLjava/io/BufferedWriter;-><init>(Ljava/io/Writer;I)V
 HSPLjava/io/BufferedWriter;->close()V
 HSPLjava/io/BufferedWriter;->ensureOpen()V
-HSPLjava/io/BufferedWriter;->flush()V
+HSPLjava/io/BufferedWriter;->flush()V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;]Ljava/io/BufferedWriter;Ljava/io/BufferedWriter;
 HSPLjava/io/BufferedWriter;->flushBuffer()V
 HSPLjava/io/BufferedWriter;->min(II)I
 HSPLjava/io/BufferedWriter;->newLine()V
 HSPLjava/io/BufferedWriter;->write(I)V
-HSPLjava/io/BufferedWriter;->write(Ljava/lang/String;II)V
+HSPLjava/io/BufferedWriter;->write(Ljava/lang/String;II)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/BufferedWriter;Ljava/io/BufferedWriter;
 HSPLjava/io/BufferedWriter;->write([CII)V
 HSPLjava/io/ByteArrayInputStream;-><init>([B)V
 HSPLjava/io/ByteArrayInputStream;-><init>([BII)V
@@ -1285,14 +1289,14 @@
 HSPLjava/io/DataInputStream;->readBoolean()Z
 HSPLjava/io/DataInputStream;->readByte()B
 HSPLjava/io/DataInputStream;->readFully([B)V
-HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/FileInputStream;
-HSPLjava/io/DataInputStream;->readInt()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readFully([BII)V
+HSPLjava/io/DataInputStream;->readInt()I
 HSPLjava/io/DataInputStream;->readLong()J
 HSPLjava/io/DataInputStream;->readShort()S
 HSPLjava/io/DataInputStream;->readUTF()Ljava/lang/String;
-HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;+]Ljava/io/DataInput;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;
 HSPLjava/io/DataInputStream;->readUnsignedByte()I
-HSPLjava/io/DataInputStream;->readUnsignedShort()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readUnsignedShort()I
 HSPLjava/io/DataInputStream;->skipBytes(I)I
 HSPLjava/io/DataOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/DataOutputStream;->flush()V
@@ -1305,25 +1309,25 @@
 HSPLjava/io/DataOutputStream;->writeLong(J)V
 HSPLjava/io/DataOutputStream;->writeShort(I)V
 HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;)V
-HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I+]Ljava/io/DataOutput;Ljava/io/DataOutputStream;
+HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I
 HSPLjava/io/EOFException;-><init>()V
 HSPLjava/io/EOFException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/ExpiringCache;->clear()V
 HSPLjava/io/File$TempDirectory;->generateFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
 HSPLjava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
-HSPLjava/io/File;-><init>(Ljava/lang/String;)V
+HSPLjava/io/File;-><init>(Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;-><init>(Ljava/lang/String;I)V
 HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/io/File;)V
 HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/io/File;->canExecute()Z
 HSPLjava/io/File;->canRead()Z
 HSPLjava/io/File;->canWrite()Z
-HSPLjava/io/File;->compareTo(Ljava/io/File;)I
+HSPLjava/io/File;->compareTo(Ljava/io/File;)I+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->compareTo(Ljava/lang/Object;)I
 HSPLjava/io/File;->createNewFile()Z
 HSPLjava/io/File;->createTempFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
 HSPLjava/io/File;->delete()Z
-HSPLjava/io/File;->equals(Ljava/lang/Object;)Z
+HSPLjava/io/File;->equals(Ljava/lang/Object;)Z+]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/File;->exists()Z+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->getAbsoluteFile()Ljava/io/File;
 HSPLjava/io/File;->getAbsolutePath()Ljava/lang/String;
@@ -1337,20 +1341,20 @@
 HSPLjava/io/File;->getPrefixLength()I
 HSPLjava/io/File;->getTotalSpace()J
 HSPLjava/io/File;->getUsableSpace()J
-HSPLjava/io/File;->hashCode()I
+HSPLjava/io/File;->hashCode()I+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->isAbsolute()Z
 HSPLjava/io/File;->isDirectory()Z+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
 HSPLjava/io/File;->isFile()Z
 HSPLjava/io/File;->isInvalid()Z
 HSPLjava/io/File;->lastModified()J
-HSPLjava/io/File;->length()J+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem;
+HSPLjava/io/File;->length()J
 HSPLjava/io/File;->list()[Ljava/lang/String;
 HSPLjava/io/File;->list(Ljava/io/FilenameFilter;)[Ljava/lang/String;
 HSPLjava/io/File;->listFiles()[Ljava/io/File;
 HSPLjava/io/File;->listFiles(Ljava/io/FileFilter;)[Ljava/io/File;
 HSPLjava/io/File;->listFiles(Ljava/io/FilenameFilter;)[Ljava/io/File;
 HSPLjava/io/File;->mkdir()Z
-HSPLjava/io/File;->mkdirs()Z
+HSPLjava/io/File;->mkdirs()Z+]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/File;->renameTo(Ljava/io/File;)Z
 HSPLjava/io/File;->setExecutable(Z)Z
 HSPLjava/io/File;->setExecutable(ZZ)Z
@@ -1373,7 +1377,7 @@
 HSPLjava/io/FileDescriptor;->setInt$(I)V
 HSPLjava/io/FileDescriptor;->setOwnerId$(J)V
 HSPLjava/io/FileDescriptor;->valid()Z
-HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V
+HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
 HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;)V
 HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;Z)V
 HSPLjava/io/FileInputStream;-><init>(Ljava/lang/String;)V
@@ -1383,23 +1387,23 @@
 HSPLjava/io/FileInputStream;->getChannel()Ljava/nio/channels/FileChannel;
 HSPLjava/io/FileInputStream;->getFD()Ljava/io/FileDescriptor;
 HSPLjava/io/FileInputStream;->read()I
-HSPLjava/io/FileInputStream;->read([B)I
+HSPLjava/io/FileInputStream;->read([B)I+]Ljava/io/FileInputStream;Ljava/io/FileInputStream;
 HSPLjava/io/FileInputStream;->read([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
 HSPLjava/io/FileInputStream;->skip(J)J
 HSPLjava/io/FileNotFoundException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;)V
-HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
+HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;Z)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/lang/String;)V
 HSPLjava/io/FileOutputStream;-><init>(Ljava/lang/String;Z)V
-HSPLjava/io/FileOutputStream;->close()V+]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
+HSPLjava/io/FileOutputStream;->close()V
 HSPLjava/io/FileOutputStream;->finalize()V
 HSPLjava/io/FileOutputStream;->getChannel()Ljava/nio/channels/FileChannel;
 HSPLjava/io/FileOutputStream;->getFD()Ljava/io/FileDescriptor;
 HSPLjava/io/FileOutputStream;->write(I)V
 HSPLjava/io/FileOutputStream;->write([B)V
-HSPLjava/io/FileOutputStream;->write([BII)V+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
+HSPLjava/io/FileOutputStream;->write([BII)V
 HSPLjava/io/FileReader;-><init>(Ljava/io/File;)V
 HSPLjava/io/FileReader;-><init>(Ljava/lang/String;)V
 HSPLjava/io/FileWriter;-><init>(Ljava/io/File;)V
@@ -1409,9 +1413,9 @@
 HSPLjava/io/FilterInputStream;->close()V
 HSPLjava/io/FilterInputStream;->mark(I)V
 HSPLjava/io/FilterInputStream;->markSupported()Z
-HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/PushbackInputStream;,Ljava/io/ByteArrayInputStream;
+HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/PushbackInputStream;,Landroid/content/res/AssetManager$AssetInputStream;
 HSPLjava/io/FilterInputStream;->read([B)I
-HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
+HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/FileInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/PushbackInputStream;
 HSPLjava/io/FilterInputStream;->reset()V
 HSPLjava/io/FilterInputStream;->skip(J)J
 HSPLjava/io/FilterOutputStream;-><init>(Ljava/io/OutputStream;)V
@@ -1444,35 +1448,36 @@
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->currentBlockRemaining()I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->getBlockDataMode()Z
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peek()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peekByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peek()I
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peekByte()B
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read()I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BII)I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BIIZ)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBoolean()Z
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readByte()B
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readFloat()F
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readFully([BIIZ)V
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readInt()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readLong()J+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readInt()I
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readLong()J
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readShort()S+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTF()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTF()Ljava/lang/String;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFBody(J)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFChar(Ljava/lang/StringBuilder;J)I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUTFSpan(Ljava/lang/StringBuilder;J)J+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUnsignedShort()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readUnsignedShort()I
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->refill()V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->setBlockDataMode(Z)Z
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;->skipBlockData()V
 HSPLjava/io/ObjectInputStream$GetField;-><init>()V
-HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;D)D
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;I)I
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;J)J
-HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Z)Z
 HSPLjava/io/ObjectInputStream$GetFieldImpl;->getFieldOffset(Ljava/lang/String;Ljava/lang/Class;)I
-HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
+HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V
 HSPLjava/io/ObjectInputStream$HandleTable$HandleList;-><init>()V
 HSPLjava/io/ObjectInputStream$HandleTable$HandleList;->add(I)V
 HSPLjava/io/ObjectInputStream$HandleTable;-><init>(I)V
@@ -1488,44 +1493,44 @@
 HSPLjava/io/ObjectInputStream$PeekInputStream;-><init>(Ljava/io/InputStream;)V
 HSPLjava/io/ObjectInputStream$PeekInputStream;->close()V
 HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I
 HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V
 HSPLjava/io/ObjectInputStream$ValidationList;-><init>()V
 HSPLjava/io/ObjectInputStream$ValidationList;->clear()V
 HSPLjava/io/ObjectInputStream$ValidationList;->doCallbacks()V
-HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;,Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/ObjectInputStream;->checkResolve(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->clear()V
-HSPLjava/io/ObjectInputStream;->close()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
-HSPLjava/io/ObjectInputStream;->defaultReadFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectInputStream;->close()V
+HSPLjava/io/ObjectInputStream;->defaultReadFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V
 HSPLjava/io/ObjectInputStream;->defaultReadObject()V
 HSPLjava/io/ObjectInputStream;->isCustomSubclass()Z
 HSPLjava/io/ObjectInputStream;->latestUserDefinedLoader()Ljava/lang/ClassLoader;
-HSPLjava/io/ObjectInputStream;->readArray(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectInputStream;->readArray(Z)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->readBoolean()Z
 HSPLjava/io/ObjectInputStream;->readByte()B
-HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectInputStream;->readClassDescriptor()Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectInputStream;->readEnum(Z)Ljava/lang/Enum;
-HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField;+]Ljava/io/ObjectInputStream$GetFieldImpl;Ljava/io/ObjectInputStream$GetFieldImpl;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext;
+HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField;
 HSPLjava/io/ObjectInputStream;->readFloat()F
-HSPLjava/io/ObjectInputStream;->readHandle(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream;->readHandle(Z)Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->readInt()I
 HSPLjava/io/ObjectInputStream;->readLong()J
-HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectInputStream;->readNull()Ljava/lang/Object;
-HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object;+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
+HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object;
 HSPLjava/io/ObjectInputStream;->readObject0(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
-HSPLjava/io/ObjectInputStream;->readOrdinaryObject(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
-HSPLjava/io/ObjectInputStream;->readSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext;
+HSPLjava/io/ObjectInputStream;->readOrdinaryObject(Z)Ljava/lang/Object;
+HSPLjava/io/ObjectInputStream;->readSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V
 HSPLjava/io/ObjectInputStream;->readShort()S
 HSPLjava/io/ObjectInputStream;->readStreamHeader()V
-HSPLjava/io/ObjectInputStream;->readString(Z)Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
-HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;->readString(Z)Ljava/lang/String;
+HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;
 HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String;
 HSPLjava/io/ObjectInputStream;->resolveClass(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;
-HSPLjava/io/ObjectInputStream;->skipCustomData()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;->skipCustomData()V
 HSPLjava/io/ObjectInputStream;->verifySubclass()V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->close()V
@@ -1567,7 +1572,7 @@
 HSPLjava/io/ObjectOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/ObjectOutputStream;->annotateClass(Ljava/lang/Class;)V
 HSPLjava/io/ObjectOutputStream;->close()V
-HSPLjava/io/ObjectOutputStream;->defaultWriteFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V
+HSPLjava/io/ObjectOutputStream;->defaultWriteFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/io/ObjectOutputStream;->defaultWriteObject()V
 HSPLjava/io/ObjectOutputStream;->flush()V
 HSPLjava/io/ObjectOutputStream;->isCustomSubclass()Z
@@ -1587,7 +1592,7 @@
 HSPLjava/io/ObjectOutputStream;->writeNull()V
 HSPLjava/io/ObjectOutputStream;->writeObject(Ljava/lang/Object;)V
 HSPLjava/io/ObjectOutputStream;->writeObject0(Ljava/lang/Object;Z)V
-HSPLjava/io/ObjectOutputStream;->writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V
+HSPLjava/io/ObjectOutputStream;->writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V+]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
 HSPLjava/io/ObjectOutputStream;->writeSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V
 HSPLjava/io/ObjectOutputStream;->writeShort(I)V
 HSPLjava/io/ObjectOutputStream;->writeStreamHeader()V
@@ -1622,10 +1627,10 @@
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getFields()[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
 HSPLjava/io/ObjectStreamClass$FieldReflector;->getPrimFieldValues(Ljava/lang/Object;[B)V
-HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
 HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V
-HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->equals(Ljava/lang/Object;)Z+]Ljava/io/ObjectStreamClass$FieldReflectorKey;Ljava/io/ObjectStreamClass$FieldReflectorKey;
+HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V
+HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->equals(Ljava/lang/Object;)Z
 HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->hashCode()I
 HSPLjava/io/ObjectStreamClass$MemberSignature;-><init>(Ljava/lang/reflect/Constructor;)V
 HSPLjava/io/ObjectStreamClass$MemberSignature;-><init>(Ljava/lang/reflect/Field;)V
@@ -1656,17 +1661,17 @@
 HSPLjava/io/ObjectStreamClass;->checkDefaultSerialize()V
 HSPLjava/io/ObjectStreamClass;->checkDeserialize()V
 HSPLjava/io/ObjectStreamClass;->checkSerialize()V
-HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/io/ObjectStreamClass;->computeDefaultSUID(Ljava/lang/Class;)J
-HSPLjava/io/ObjectStreamClass;->computeFieldOffsets()V+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
+HSPLjava/io/ObjectStreamClass;->computeFieldOffsets()V
 HSPLjava/io/ObjectStreamClass;->forClass()Ljava/lang/Class;
 HSPLjava/io/ObjectStreamClass;->getClassDataLayout()[Ljava/io/ObjectStreamClass$ClassDataSlot;
-HSPLjava/io/ObjectStreamClass;->getClassDataLayout0()[Ljava/io/ObjectStreamClass$ClassDataSlot;+]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/io/ObjectStreamClass;->getClassDataLayout0()[Ljava/io/ObjectStreamClass$ClassDataSlot;
 HSPLjava/io/ObjectStreamClass;->getClassSignature(Ljava/lang/Class;)Ljava/lang/String;
 HSPLjava/io/ObjectStreamClass;->getDeclaredSUID(Ljava/lang/Class;)Ljava/lang/Long;
 HSPLjava/io/ObjectStreamClass;->getDeclaredSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getDefaultSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getFields(Z)[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->getInheritableMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
 HSPLjava/io/ObjectStreamClass;->getMethodSignature([Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/String;
@@ -1677,10 +1682,10 @@
 HSPLjava/io/ObjectStreamClass;->getPrimDataSize()I
 HSPLjava/io/ObjectStreamClass;->getPrimFieldValues(Ljava/lang/Object;[B)V
 HSPLjava/io/ObjectStreamClass;->getPrivateMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
-HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture;
+HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;
 HSPLjava/io/ObjectStreamClass;->getResolveException()Ljava/lang/ClassNotFoundException;
 HSPLjava/io/ObjectStreamClass;->getSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J+]Ljava/lang/Long;Ljava/lang/Long;
+HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J
 HSPLjava/io/ObjectStreamClass;->getSerializableConstructor(Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
 HSPLjava/io/ObjectStreamClass;->getSuperDesc()Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectStreamClass;->getVariantFor(Ljava/lang/Class;)Ljava/io/ObjectStreamClass;
@@ -1698,19 +1703,19 @@
 HSPLjava/io/ObjectStreamClass;->isExternalizable()Z
 HSPLjava/io/ObjectStreamClass;->isInstantiable()Z
 HSPLjava/io/ObjectStreamClass;->isProxy()Z
-HSPLjava/io/ObjectStreamClass;->lookup(Ljava/lang/Class;Z)Ljava/io/ObjectStreamClass;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->lookup(Ljava/lang/Class;Z)Ljava/io/ObjectStreamClass;
 HSPLjava/io/ObjectStreamClass;->matchFields([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)[Ljava/io/ObjectStreamField;
 HSPLjava/io/ObjectStreamClass;->newInstance()Ljava/lang/Object;
 HSPLjava/io/ObjectStreamClass;->packageEquals(Ljava/lang/Class;Ljava/lang/Class;)Z
 HSPLjava/io/ObjectStreamClass;->processQueue(Ljava/lang/ref/ReferenceQueue;Ljava/util/concurrent/ConcurrentMap;)V
-HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;
+HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V
 HSPLjava/io/ObjectStreamClass;->requireInitialized()V
 HSPLjava/io/ObjectStreamClass;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
 HSPLjava/io/ObjectStreamClass;->setPrimFieldValues(Ljava/lang/Object;[B)V
 HSPLjava/io/ObjectStreamClass;->writeNonProxy(Ljava/io/ObjectOutputStream;)V
 HSPLjava/io/ObjectStreamField;-><init>(Ljava/lang/String;Ljava/lang/Class;)V
 HSPLjava/io/ObjectStreamField;-><init>(Ljava/lang/String;Ljava/lang/Class;Z)V
-HSPLjava/io/ObjectStreamField;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/io/ObjectStreamField;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V
 HSPLjava/io/ObjectStreamField;-><init>(Ljava/lang/reflect/Field;ZZ)V
 HSPLjava/io/ObjectStreamField;->compareTo(Ljava/lang/Object;)I
 HSPLjava/io/ObjectStreamField;->getClassSignature(Ljava/lang/Class;)Ljava/lang/String;
@@ -1752,8 +1757,8 @@
 HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;
 HSPLjava/io/PrintWriter;->close()V
 HSPLjava/io/PrintWriter;->ensureOpen()V
-HSPLjava/io/PrintWriter;->flush()V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
-HSPLjava/io/PrintWriter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;
+HSPLjava/io/PrintWriter;->flush()V
+HSPLjava/io/PrintWriter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;+]Ljava/util/Formatter;Ljava/util/Formatter;
 HSPLjava/io/PrintWriter;->newLine()V
 HSPLjava/io/PrintWriter;->print(C)V
 HSPLjava/io/PrintWriter;->print(I)V
@@ -1763,12 +1768,12 @@
 HSPLjava/io/PrintWriter;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->println()V
 HSPLjava/io/PrintWriter;->println(I)V
-HSPLjava/io/PrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;
-HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/io/PrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/FastPrintWriter;,Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V
 HSPLjava/io/PrintWriter;->write(I)V
 HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
 HSPLjava/io/PrintWriter;->write(Ljava/lang/String;II)V
-HSPLjava/io/PrintWriter;->write([CII)V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
+HSPLjava/io/PrintWriter;->write([CII)V
 HSPLjava/io/PushbackInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLjava/io/PushbackInputStream;->close()V
 HSPLjava/io/PushbackInputStream;->ensureOpen()V
@@ -1793,11 +1798,11 @@
 HSPLjava/io/RandomAccessFile;->read([B)I
 HSPLjava/io/RandomAccessFile;->read([BII)I
 HSPLjava/io/RandomAccessFile;->readByte()B
-HSPLjava/io/RandomAccessFile;->readBytes([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
+HSPLjava/io/RandomAccessFile;->readBytes([BII)I
 HSPLjava/io/RandomAccessFile;->readFully([B)V
 HSPLjava/io/RandomAccessFile;->readFully([BII)V
 HSPLjava/io/RandomAccessFile;->readInt()I
-HSPLjava/io/RandomAccessFile;->seek(J)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
+HSPLjava/io/RandomAccessFile;->seek(J)V
 HSPLjava/io/RandomAccessFile;->setLength(J)V
 HSPLjava/io/RandomAccessFile;->write(I)V
 HSPLjava/io/RandomAccessFile;->write([B)V
@@ -1835,24 +1840,24 @@
 HSPLjava/io/StringWriter;->flush()V
 HSPLjava/io/StringWriter;->getBuffer()Ljava/lang/StringBuffer;
 HSPLjava/io/StringWriter;->toString()Ljava/lang/String;
-HSPLjava/io/StringWriter;->write(I)V
-HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V
+HSPLjava/io/StringWriter;->write(I)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLjava/io/StringWriter;->write(Ljava/lang/String;II)V
 HSPLjava/io/StringWriter;->write([CII)V
 HSPLjava/io/UnixFileSystem;->canonicalize(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/io/UnixFileSystem;->checkAccess(Ljava/io/File;I)Z+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
-HSPLjava/io/UnixFileSystem;->compare(Ljava/io/File;Ljava/io/File;)I
+HSPLjava/io/UnixFileSystem;->compare(Ljava/io/File;Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/UnixFileSystem;->createDirectory(Ljava/io/File;)Z
 HSPLjava/io/UnixFileSystem;->createFileExclusively(Ljava/lang/String;)Z
 HSPLjava/io/UnixFileSystem;->delete(Ljava/io/File;)Z
-HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;,Ldalvik/system/BlockGuard$2;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLjava/io/UnixFileSystem;->getDefaultParent()Ljava/lang/String;
 HSPLjava/io/UnixFileSystem;->getLastModifiedTime(Ljava/io/File;)J
-HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J
 HSPLjava/io/UnixFileSystem;->getSpace(Ljava/io/File;I)J
-HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I
+HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/File;Ljava/io/File;
 HSPLjava/io/UnixFileSystem;->isAbsolute(Ljava/io/File;)Z
-HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
 HSPLjava/io/UnixFileSystem;->normalize(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/io/UnixFileSystem;->prefixLength(Ljava/lang/String;)I
 HSPLjava/io/UnixFileSystem;->rename(Ljava/io/File;Ljava/io/File;)Z
@@ -1866,39 +1871,48 @@
 HSPLjava/io/Writer;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;
 HSPLjava/io/Writer;->write(Ljava/lang/String;)V
 HSPLjava/lang/AbstractStringBuilder;-><init>(I)V
-HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->append(D)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(F)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append(Z)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->append([CII)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->appendChars(Ljava/lang/CharSequence;II)V+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;,Ljava/lang/StringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+HSPLjava/lang/AbstractStringBuilder;->appendChars([CII)V+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->appendCodePoint(I)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->appendNull()Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->charAt(I)C
+HSPLjava/lang/AbstractStringBuilder;->checkRange(III)V
+HSPLjava/lang/AbstractStringBuilder;->checkRangeSIOOBE(III)V
 HSPLjava/lang/AbstractStringBuilder;->codePointAt(I)I
 HSPLjava/lang/AbstractStringBuilder;->delete(II)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->deleteCharAt(I)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->ensureCapacity(I)V
 HSPLjava/lang/AbstractStringBuilder;->ensureCapacityInternal(I)V
+HSPLjava/lang/AbstractStringBuilder;->getBytes([BIB)V
 HSPLjava/lang/AbstractStringBuilder;->getChars(II[CI)V
+HSPLjava/lang/AbstractStringBuilder;->getCoder()B
 HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;)I
 HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;I)I
 HSPLjava/lang/AbstractStringBuilder;->insert(IC)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->insert(II)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->insert(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/AbstractStringBuilder;->isLatin1()Z
 HSPLjava/lang/AbstractStringBuilder;->lastIndexOf(Ljava/lang/String;I)I
 HSPLjava/lang/AbstractStringBuilder;->length()I
 HSPLjava/lang/AbstractStringBuilder;->newCapacity(I)I
+HSPLjava/lang/AbstractStringBuilder;->putStringAt(ILjava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
 HSPLjava/lang/AbstractStringBuilder;->replace(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->reverse()Ljava/lang/AbstractStringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->setCharAt(IC)V
 HSPLjava/lang/AbstractStringBuilder;->setLength(I)V
+HSPLjava/lang/AbstractStringBuilder;->shift(II)V
 HSPLjava/lang/AbstractStringBuilder;->subSequence(II)Ljava/lang/CharSequence;
 HSPLjava/lang/AbstractStringBuilder;->substring(I)Ljava/lang/String;
 HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String;
@@ -1944,10 +1958,10 @@
 HSPLjava/lang/Character;-><init>(C)V
 HSPLjava/lang/Character;->charCount(I)I
 HSPLjava/lang/Character;->charValue()C
-HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;Ljava/lang/String;,Landroid/icu/impl/StringSegment;
+HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;megamorphic_types
 HSPLjava/lang/Character;->codePointAtImpl([CII)I
 HSPLjava/lang/Character;->codePointBefore(Ljava/lang/CharSequence;I)I
-HSPLjava/lang/Character;->codePointCount(Ljava/lang/CharSequence;II)I+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/lang/Character;->codePointCount(Ljava/lang/CharSequence;II)I
 HSPLjava/lang/Character;->digit(CI)I
 HSPLjava/lang/Character;->digit(II)I
 HSPLjava/lang/Character;->equals(Ljava/lang/Object;)Z
@@ -2007,7 +2021,7 @@
 HSPLjava/lang/Class;->getAccessFlags()I
 HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
 HSPLjava/lang/Class;->getCanonicalName()Ljava/lang/String;
-HSPLjava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;
 HSPLjava/lang/Class;->getComponentType()Ljava/lang/Class;
 HSPLjava/lang/Class;->getConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor;
@@ -2026,19 +2040,19 @@
 HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;
 HSPLjava/lang/Class;->getInterfaces()[Ljava/lang/Class;
 HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
-HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/reflect/Method;
+HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/reflect/Method;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getMethods()[Ljava/lang/reflect/Method;
-HSPLjava/lang/Class;->getModifiers()I
+HSPLjava/lang/Class;->getModifiers()I+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getName()Ljava/lang/String;
 HSPLjava/lang/Class;->getPackage()Ljava/lang/Package;
 HSPLjava/lang/Class;->getPackageName()Ljava/lang/String;
 HSPLjava/lang/Class;->getProtectionDomain()Ljava/security/ProtectionDomain;
 HSPLjava/lang/Class;->getPublicFieldsRecursive(Ljava/util/List;)V
-HSPLjava/lang/Class;->getPublicMethodRecursive(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
+HSPLjava/lang/Class;->getPublicMethodRecursive(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;+]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getPublicMethodsInternal(Ljava/util/List;)V
 HSPLjava/lang/Class;->getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
 HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String;
-HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;
 HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;
 HSPLjava/lang/Class;->getTypeName()Ljava/lang/String;
 HSPLjava/lang/Class;->getTypeParameters()[Ljava/lang/reflect/TypeVariable;
@@ -2051,7 +2065,7 @@
 HSPLjava/lang/Class;->isInterface()Z
 HSPLjava/lang/Class;->isLocalClass()Z+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->isLocalOrAnonymousClass()Z
-HSPLjava/lang/Class;->isMemberClass()Z+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->isMemberClass()Z
 HSPLjava/lang/Class;->isPrimitive()Z
 HSPLjava/lang/Class;->isProxy()Z
 HSPLjava/lang/Class;->resolveName(Ljava/lang/String;)Ljava/lang/String;
@@ -2089,7 +2103,7 @@
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNotNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerWatchdogDaemon;
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->isActive(I)Z
-HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V+]Ljava/lang/Object;Ljava/lang/Daemons$FinalizerWatchdogDaemon;
+HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNotNeeded(I)V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->resetTimeouts()V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->runInternal()V
@@ -2102,7 +2116,6 @@
 HSPLjava/lang/Daemons$ReferenceQueueDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$ReferenceQueueDaemon;
 HSPLjava/lang/Daemons$ReferenceQueueDaemon;->runInternal()V
 HSPLjava/lang/Daemons;->-$$Nest$sfgetPOST_ZYGOTE_START_LATCH()Ljava/util/concurrent/CountDownLatch;
-HSPLjava/lang/Daemons;->-$$Nest$sfputMAX_FINALIZE_NANOS(J)V
 HSPLjava/lang/Daemons;->startPostZygoteFork()V
 HSPLjava/lang/Daemons;->stop()V
 HSPLjava/lang/Double;-><init>(D)V
@@ -2117,6 +2130,7 @@
 HSPLjava/lang/Double;->hashCode(D)I
 HSPLjava/lang/Double;->intValue()I
 HSPLjava/lang/Double;->isInfinite(D)Z
+HSPLjava/lang/Double;->isNaN()Z
 HSPLjava/lang/Double;->isNaN(D)Z
 HSPLjava/lang/Double;->longValue()J
 HSPLjava/lang/Double;->parseDouble(Ljava/lang/String;)D
@@ -2138,7 +2152,7 @@
 HSPLjava/lang/Enum;->name()Ljava/lang/String;
 HSPLjava/lang/Enum;->ordinal()I
 HSPLjava/lang/Enum;->toString()Ljava/lang/String;
-HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;+]Ljava/lang/Enum;Landroid/net/wifi/SupplicantState;,Landroid/net/NetworkInfo$State;,Landroid/net/NetworkRequest$Type;,Landroid/net/NetworkInfo$DetailedState;
 HSPLjava/lang/Error;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Exception;-><init>()V
 HSPLjava/lang/Exception;-><init>(Ljava/lang/String;)V
@@ -2148,7 +2162,7 @@
 HSPLjava/lang/Float;-><init>(F)V
 HSPLjava/lang/Float;->compare(FF)I
 HSPLjava/lang/Float;->compareTo(Ljava/lang/Float;)I
-HSPLjava/lang/Float;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Float;Ljava/lang/Float;
+HSPLjava/lang/Float;->compareTo(Ljava/lang/Object;)I
 HSPLjava/lang/Float;->doubleValue()D
 HSPLjava/lang/Float;->equals(Ljava/lang/Object;)Z
 HSPLjava/lang/Float;->floatToIntBits(F)I
@@ -2181,8 +2195,8 @@
 HSPLjava/lang/Integer;->byteValue()B
 HSPLjava/lang/Integer;->compare(II)I
 HSPLjava/lang/Integer;->compareTo(Ljava/lang/Integer;)I
-HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I
-HSPLjava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Integer;Ljava/lang/Integer;
+HSPLjava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer;
 HSPLjava/lang/Integer;->divideUnsigned(II)I
 HSPLjava/lang/Integer;->doubleValue()D
 HSPLjava/lang/Integer;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Integer;Ljava/lang/Integer;
@@ -2224,7 +2238,7 @@
 HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;)Ljava/lang/Integer;
 HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;I)Ljava/lang/Integer;
 HSPLjava/lang/InterruptedException;-><init>()V
-HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/lang/Iterable;Ljava/util/HashSet;,Ljava/util/WeakHashMap$KeySet;,Ljava/util/WeakHashMap$EntrySet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/WeakHashMap$KeyIterator;,Ljava/util/WeakHashMap$EntryIterator;]Ljava/util/function/Consumer;missing_types
+HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/ArrayDeque$$ExternalSyntheticLambda1;]Ljava/util/Iterator;missing_types
 HSPLjava/lang/LinkageError;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Long;-><init>(J)V
 HSPLjava/lang/Long;->bitCount(J)I
@@ -2249,6 +2263,7 @@
 HSPLjava/lang/Long;->lowestOneBit(J)J
 HSPLjava/lang/Long;->numberOfLeadingZeros(J)I
 HSPLjava/lang/Long;->numberOfTrailingZeros(J)I
+HSPLjava/lang/Long;->parseLong(Ljava/lang/CharSequence;III)J+]Ljava/lang/CharSequence;Ljava/lang/String;
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;)J
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;I)J
 HSPLjava/lang/Long;->reverse(J)J
@@ -2318,6 +2333,7 @@
 HSPLjava/lang/Number;-><init>()V
 HSPLjava/lang/NumberFormatException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;)Ljava/lang/NumberFormatException;
+HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;I)Ljava/lang/NumberFormatException;
 HSPLjava/lang/Object;-><init>()V
 HSPLjava/lang/Object;->clone()Ljava/lang/Object;
 HSPLjava/lang/Object;->equals(Ljava/lang/Object;)Z
@@ -2325,7 +2341,7 @@
 HSPLjava/lang/Object;->getClass()Ljava/lang/Class;
 HSPLjava/lang/Object;->hashCode()I
 HSPLjava/lang/Object;->identityHashCode(Ljava/lang/Object;)I
-HSPLjava/lang/Object;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;missing_types]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Object;->toString()Ljava/lang/String;
 HSPLjava/lang/Object;->wait()V
 HSPLjava/lang/Object;->wait(J)V
 HSPLjava/lang/Package;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/net/URL;Ljava/lang/ClassLoader;)V
@@ -2384,9 +2400,12 @@
 HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/String;Ljava/lang/String;)I
 HSPLjava/lang/String;->checkBoundsBeginEnd(III)V
+HSPLjava/lang/String;->checkBoundsOffCount(III)V
 HSPLjava/lang/String;->checkIndex(II)V
+HSPLjava/lang/String;->checkOffset(II)V
 HSPLjava/lang/String;->codePointAt(I)I
 HSPLjava/lang/String;->codePointCount(II)I
+HSPLjava/lang/String;->coder()B
 HSPLjava/lang/String;->compareTo(Ljava/lang/Object;)I
 HSPLjava/lang/String;->compareToIgnoreCase(Ljava/lang/String;)I
 HSPLjava/lang/String;->contains(Ljava/lang/CharSequence;)Z+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/CharSequence;Ljava/lang/String;
@@ -2395,11 +2414,12 @@
 HSPLjava/lang/String;->endsWith(Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->equals(Ljava/lang/Object;)Z
 HSPLjava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;+]Ljava/util/Formatter;Ljava/util/Formatter;
-HSPLjava/lang/String;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
+HSPLjava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
+HSPLjava/lang/String;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;+]Ljava/util/Formatter;Ljava/util/Formatter;
 HSPLjava/lang/String;->getBytes()[B
-HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B+]Ljava/nio/charset/Charset;missing_types
+HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B
+HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B
+HSPLjava/lang/String;->getBytes([BIB)V+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->getChars(II[CI)V
 HSPLjava/lang/String;->getChars([CI)V
 HSPLjava/lang/String;->hashCode()I
@@ -2408,17 +2428,16 @@
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;I)I
 HSPLjava/lang/String;->indexOf(Ljava/lang/String;Ljava/lang/String;I)I
-HSPLjava/lang/String;->indexOf([CIILjava/lang/String;I)I
-HSPLjava/lang/String;->indexOf([CII[CIII)I
+HSPLjava/lang/String;->indexOf([BBILjava/lang/String;I)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->isEmpty()Z
 HSPLjava/lang/String;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
 HSPLjava/lang/String;->join(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
 HSPLjava/lang/String;->lastIndexOf(I)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->lastIndexOf(II)I
-HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;I)I
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;Ljava/lang/String;I)I
-HSPLjava/lang/String;->lastIndexOf([CIILjava/lang/String;I)I
+HSPLjava/lang/String;->lastIndexOf([BBILjava/lang/String;I)I
 HSPLjava/lang/String;->lastIndexOf([CII[CIII)I
 HSPLjava/lang/String;->length()I
 HSPLjava/lang/String;->matches(Ljava/lang/String;)Z
@@ -2435,10 +2454,10 @@
 HSPLjava/lang/String;->subSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->substring(I)Ljava/lang/String;
 HSPLjava/lang/String;->substring(II)Ljava/lang/String;
-HSPLjava/lang/String;->toLowerCase()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/String;->toLowerCase()Ljava/lang/String;
 HSPLjava/lang/String;->toLowerCase(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/lang/String;->toString()Ljava/lang/String;
-HSPLjava/lang/String;->toUpperCase()Ljava/lang/String;
+HSPLjava/lang/String;->toUpperCase()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->toUpperCase(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/lang/String;->trim()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/String;->valueOf(C)Ljava/lang/String;
@@ -2446,7 +2465,7 @@
 HSPLjava/lang/String;->valueOf(F)Ljava/lang/String;
 HSPLjava/lang/String;->valueOf(I)Ljava/lang/String;
 HSPLjava/lang/String;->valueOf(J)Ljava/lang/String;
-HSPLjava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/Object;missing_types
+HSPLjava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/Object;megamorphic_types
 HSPLjava/lang/String;->valueOf(Z)Ljava/lang/String;
 HSPLjava/lang/String;->valueOf([C)Ljava/lang/String;
 HSPLjava/lang/StringBuffer;-><init>()V
@@ -2455,6 +2474,7 @@
 HSPLjava/lang/StringBuffer;->append(C)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(I)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(J)Ljava/lang/StringBuffer;
+HSPLjava/lang/StringBuffer;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;
@@ -2467,15 +2487,16 @@
 HSPLjava/lang/StringBuffer;->append([CII)Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuffer;->charAt(I)C
 HSPLjava/lang/StringBuffer;->codePointAt(I)I
+HSPLjava/lang/StringBuffer;->getBytes([BIB)V
 HSPLjava/lang/StringBuffer;->getChars(II[CI)V
 HSPLjava/lang/StringBuffer;->length()I
 HSPLjava/lang/StringBuffer;->setLength(I)V
-HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String;
+HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLjava/lang/StringBuilder;-><init>()V
 HSPLjava/lang/StringBuilder;-><init>(I)V
 HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/CharSequence;)V
-HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
-HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
+HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;
 HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(D)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(F)Ljava/lang/StringBuilder;
@@ -2483,8 +2504,8 @@
 HSPLjava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;
-HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
-HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;
+HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;
+HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
@@ -2513,7 +2534,7 @@
 HSPLjava/lang/StringBuilder;->subSequence(II)Ljava/lang/CharSequence;
 HSPLjava/lang/StringBuilder;->substring(I)Ljava/lang/String;
 HSPLjava/lang/StringBuilder;->substring(II)Ljava/lang/String;
-HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String;
+HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/StringFactory;->newEmptyString()Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromBytes([B)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromBytes([BI)Ljava/lang/String;
@@ -2524,6 +2545,17 @@
 HSPLjava/lang/StringFactory;->newStringFromBytes([BLjava/nio/charset/Charset;)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromChars([C)Ljava/lang/String;
 HSPLjava/lang/StringFactory;->newStringFromChars([CII)Ljava/lang/String;
+HSPLjava/lang/StringLatin1;->canEncode(I)Z
+HSPLjava/lang/StringLatin1;->indexOf([BILjava/lang/String;II)I
+HSPLjava/lang/StringLatin1;->inflate([BI[BII)V
+HSPLjava/lang/StringLatin1;->lastIndexOf([BILjava/lang/String;II)I
+HSPLjava/lang/StringLatin1;->newString([BII)Ljava/lang/String;
+HSPLjava/lang/StringUTF16;->checkBoundsOffCount(II[B)V
+HSPLjava/lang/StringUTF16;->getChar([BI)C
+HSPLjava/lang/StringUTF16;->inflate([BI[BII)V
+HSPLjava/lang/StringUTF16;->length([B)I
+HSPLjava/lang/StringUTF16;->newBytesFor(I)[B
+HSPLjava/lang/StringUTF16;->putChar([BII)V
 HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/System;->arraycopy([BI[BII)V
@@ -2537,7 +2569,7 @@
 HSPLjava/lang/System;->clearProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/System;->gc()V
 HSPLjava/lang/System;->getProperties()Ljava/util/Properties;
-HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/lang/System$PropertiesWithNonOverrideableDefaults;
+HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/System;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/System;->getSecurityManager()Ljava/lang/SecurityManager;
 HSPLjava/lang/System;->getenv(Ljava/lang/String;)Ljava/lang/String;
@@ -2558,7 +2590,7 @@
 HSPLjava/lang/Thread;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V
 HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;J)V
-HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;JLjava/security/AccessControlContext;Z)V+]Ljava/lang/Thread;missing_types]Ljava/lang/ThreadGroup;Ljava/lang/ThreadGroup;
+HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;JLjava/security/AccessControlContext;Z)V
 HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/String;)V
 HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V
 HSPLjava/lang/Thread;->activeCount()I
@@ -2573,7 +2605,7 @@
 HSPLjava/lang/Thread;->getState()Ljava/lang/Thread$State;
 HSPLjava/lang/Thread;->getThreadGroup()Ljava/lang/ThreadGroup;
 HSPLjava/lang/Thread;->getUncaughtExceptionHandler()Ljava/lang/Thread$UncaughtExceptionHandler;
-HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V+]Ljava/lang/Thread;missing_types
+HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V
 HSPLjava/lang/Thread;->interrupt()V
 HSPLjava/lang/Thread;->isAlive()Z
 HSPLjava/lang/Thread;->isDaemon()Z
@@ -2636,12 +2668,12 @@
 HSPLjava/lang/ThreadLocal;-><init>()V
 HSPLjava/lang/ThreadLocal;->createInheritedMap(Ljava/lang/ThreadLocal$ThreadLocalMap;)Ljava/lang/ThreadLocal$ThreadLocalMap;
 HSPLjava/lang/ThreadLocal;->createMap(Ljava/lang/Thread;Ljava/lang/Object;)V
-HSPLjava/lang/ThreadLocal;->get()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;missing_types
+HSPLjava/lang/ThreadLocal;->get()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;megamorphic_types
 HSPLjava/lang/ThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap;
 HSPLjava/lang/ThreadLocal;->initialValue()Ljava/lang/Object;
 HSPLjava/lang/ThreadLocal;->nextHashCode()I
 HSPLjava/lang/ThreadLocal;->remove()V
-HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;megamorphic_types
+HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;missing_types
 HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;
 HSPLjava/lang/ThreadLocal;->withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;
 HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>()V
@@ -2651,12 +2683,12 @@
 HSPLjava/lang/Throwable$WrappedPrintStream;->println(Ljava/lang/Object;)V
 HSPLjava/lang/Throwable$WrappedPrintWriter;-><init>(Ljava/io/PrintWriter;)V
 HSPLjava/lang/Throwable$WrappedPrintWriter;->lock()Ljava/lang/Object;
-HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/FastPrintWriter;,Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
 HSPLjava/lang/Throwable;-><init>()V
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V+]Ljava/lang/Throwable;missing_types
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
 HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;ZZ)V
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V
 HSPLjava/lang/Throwable;->addSuppressed(Ljava/lang/Throwable;)V
 HSPLjava/lang/Throwable;->fillInStackTrace()Ljava/lang/Throwable;
 HSPLjava/lang/Throwable;->getCause()Ljava/lang/Throwable;
@@ -2664,16 +2696,16 @@
 HSPLjava/lang/Throwable;->getMessage()Ljava/lang/String;
 HSPLjava/lang/Throwable;->getOurStackTrace()[Ljava/lang/StackTraceElement;
 HSPLjava/lang/Throwable;->getStackTrace()[Ljava/lang/StackTraceElement;
-HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable;+]Ljava/util/List;Ljava/util/Collections$EmptyList;
+HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable;
 HSPLjava/lang/Throwable;->initCause(Ljava/lang/Throwable;)Ljava/lang/Throwable;
-HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;Ljava/util/concurrent/CancellationException;,Ljava/io/IOException;,Ljava/lang/Throwable;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement;]Ljava/util/Set;Ljava/util/Collections$SetFromMap;
+HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V
 HSPLjava/lang/Throwable;->printStackTrace()V
 HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V
 HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintWriter;)V
 HSPLjava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;missing_types]Ljava/util/Set;Ljava/util/Collections$SetFromMap;
 HSPLjava/lang/Throwable;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/lang/Throwable;->setStackTrace([Ljava/lang/StackTraceElement;)V
-HSPLjava/lang/Throwable;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;missing_types]Ljava/lang/Throwable;missing_types]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Throwable;->toString()Ljava/lang/String;
 HSPLjava/lang/Throwable;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/lang/UNIXProcess$2;-><init>(Ljava/lang/UNIXProcess;[I)V
 HSPLjava/lang/UNIXProcess$2;->run()Ljava/lang/Object;
@@ -2833,9 +2865,9 @@
 HSPLjava/lang/reflect/Executable;->sharedToString(IZ[Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/String;
 HSPLjava/lang/reflect/Field;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/Field;->getDeclaringClass()Ljava/lang/Class;
-HSPLjava/lang/reflect/Field;->getGenericType()Ljava/lang/reflect/Type;
+HSPLjava/lang/reflect/Field;->getGenericType()Ljava/lang/reflect/Type;+]Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;]Ljava/lang/Class;Ljava/lang/Class;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLjava/lang/reflect/Field;->getModifiers()I
-HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String;
+HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/reflect/Field;->getOffset()I
 HSPLjava/lang/reflect/Field;->getSignatureAttribute()Ljava/lang/String;
 HSPLjava/lang/reflect/Field;->getType()Ljava/lang/Class;
@@ -2937,7 +2969,7 @@
 HSPLjava/math/BigDecimal;->divideAndRound(JJIII)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->getValueString(ILjava/lang/String;I)Ljava/lang/String;
 HSPLjava/math/BigDecimal;->inflated()Ljava/math/BigInteger;
-HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/math/BigDecimal$StringBuilderHelper;Ljava/math/BigDecimal$StringBuilderHelper;]Ljava/lang/ThreadLocal;Ljava/math/BigDecimal$1;]Ljava/math/BigDecimal;Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String;
 HSPLjava/math/BigDecimal;->longCompareMagnitude(JJ)I
 HSPLjava/math/BigDecimal;->longMultiplyPowerTen(JI)J
 HSPLjava/math/BigDecimal;->longValueExact()J
@@ -2946,12 +2978,15 @@
 HSPLjava/math/BigDecimal;->multiply(JJ)J
 HSPLjava/math/BigDecimal;->multiply(JJI)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->needIncrement(JIIJJ)Z
 HSPLjava/math/BigDecimal;->scale()I
+HSPLjava/math/BigDecimal;->scaleByPowerOfTen(I)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->setScale(II)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->setScale(ILjava/math/RoundingMode;)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->signum()I
 HSPLjava/math/BigDecimal;->stripTrailingZeros()Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->subtract(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+HSPLjava/math/BigDecimal;->toBigInteger()Ljava/math/BigInteger;
 HSPLjava/math/BigDecimal;->toBigIntegerExact()Ljava/math/BigInteger;
 HSPLjava/math/BigDecimal;->toPlainString()Ljava/lang/String;
 HSPLjava/math/BigDecimal;->toString()Ljava/lang/String;
@@ -2996,8 +3031,9 @@
 HSPLjava/math/BigInteger;->multiply(Ljava/math/BigInteger;Z)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->multiplyByInt([III)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->multiplyToLen([II[II[I)[I
-HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger;+]Ljava/math/BigInteger;Ljava/math/BigInteger;
-HSPLjava/math/BigInteger;->readObject(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$GetField;Ljava/io/ObjectInputStream$GetFieldImpl;
+HSPLjava/math/BigInteger;->padWithZeros(Ljava/lang/StringBuilder;I)V
+HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/math/BigInteger;->remainder(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->remainderKnuth(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->reverse([I)[I
@@ -3007,15 +3043,16 @@
 HSPLjava/math/BigInteger;->shiftRightImpl(I)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->signInt()I
 HSPLjava/math/BigInteger;->signum()I
-HSPLjava/math/BigInteger;->smallToString(I)Ljava/lang/String;
+HSPLjava/math/BigInteger;->smallToString(ILjava/lang/StringBuilder;I)V
 HSPLjava/math/BigInteger;->stripLeadingZeroBytes([BII)[I
 HSPLjava/math/BigInteger;->stripLeadingZeroInts([I)[I
 HSPLjava/math/BigInteger;->subtract(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->subtract([I[I)[I
 HSPLjava/math/BigInteger;->testBit(I)Z
-HSPLjava/math/BigInteger;->toByteArray()[B+]Ljava/math/BigInteger;Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->toByteArray()[B
 HSPLjava/math/BigInteger;->toString()Ljava/lang/String;
 HSPLjava/math/BigInteger;->toString(I)Ljava/lang/String;
+HSPLjava/math/BigInteger;->toString(Ljava/math/BigInteger;Ljava/lang/StringBuilder;II)V
 HSPLjava/math/BigInteger;->trustedStripLeadingZeroInts([I)[I
 HSPLjava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger;
 HSPLjava/math/MathContext;->equals(Ljava/lang/Object;)Z
@@ -3044,6 +3081,7 @@
 HSPLjava/math/MutableBigInteger;->rightShift(I)V
 HSPLjava/math/MutableBigInteger;->toBigInteger(I)Ljava/math/BigInteger;
 HSPLjava/math/MutableBigInteger;->unsignedLongCompare(JJ)Z
+HSPLjava/math/RoundingMode;->valueOf(I)Ljava/math/RoundingMode;
 HSPLjava/math/RoundingMode;->values()[Ljava/math/RoundingMode;
 HSPLjava/net/AbstractPlainDatagramSocketImpl;-><init>()V
 HSPLjava/net/AbstractPlainDatagramSocketImpl;->bind(ILjava/net/InetAddress;)V
@@ -3071,7 +3109,7 @@
 HSPLjava/net/AbstractPlainSocketImpl;->isConnectionReset()Z
 HSPLjava/net/AbstractPlainSocketImpl;->isConnectionResetPending()Z
 HSPLjava/net/AbstractPlainSocketImpl;->listen(I)V
-HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;
+HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V
 HSPLjava/net/AbstractPlainSocketImpl;->setOption(ILjava/lang/Object;)V
 HSPLjava/net/AbstractPlainSocketImpl;->socketClose()V
 HSPLjava/net/AbstractPlainSocketImpl;->socketPreClose()V
@@ -3140,8 +3178,8 @@
 HSPLjava/net/HttpCookie;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/HttpCookie;->assignAttribute(Ljava/net/HttpCookie;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/net/HttpCookie;->domainMatches(Ljava/lang/String;Ljava/lang/String;)Z
-HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z+]Ljava/net/HttpCookie;Ljava/net/HttpCookie;
-HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z
+HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/net/HttpCookie;->getDomain()Ljava/lang/String;
 HSPLjava/net/HttpCookie;->getMaxAge()J
 HSPLjava/net/HttpCookie;->getName()Ljava/lang/String;
@@ -3176,7 +3214,7 @@
 HSPLjava/net/IDN;->toASCII(Ljava/lang/String;I)Ljava/lang/String;
 HSPLjava/net/InMemoryCookieStore;-><init>()V
 HSPLjava/net/InMemoryCookieStore;-><init>(I)V
-HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
+HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V
 HSPLjava/net/InMemoryCookieStore;->addIndex(Ljava/util/Map;Ljava/lang/Object;Ljava/net/HttpCookie;)V
 HSPLjava/net/InMemoryCookieStore;->get(Ljava/net/URI;)Ljava/util/List;
 HSPLjava/net/InMemoryCookieStore;->getEffectiveURI(Ljava/net/URI;)Ljava/net/URI;
@@ -3379,12 +3417,12 @@
 HSPLjava/net/SocketImpl;->setSocket(Ljava/net/Socket;)V
 HSPLjava/net/SocketInputStream;-><init>(Ljava/net/AbstractPlainSocketImpl;)V
 HSPLjava/net/SocketInputStream;->finalize()V
-HSPLjava/net/SocketInputStream;->read([BII)I+]Ljava/net/SocketInputStream;Ljava/net/SocketInputStream;]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;
-HSPLjava/net/SocketInputStream;->read([BIII)I+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLjava/net/SocketInputStream;->read([BII)I
+HSPLjava/net/SocketInputStream;->read([BIII)I
 HSPLjava/net/SocketInputStream;->socketRead(Ljava/io/FileDescriptor;[BIII)I
 HSPLjava/net/SocketOutputStream;-><init>(Ljava/net/AbstractPlainSocketImpl;)V
 HSPLjava/net/SocketOutputStream;->finalize()V
-HSPLjava/net/SocketOutputStream;->socketWrite([BII)V+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLjava/net/SocketOutputStream;->socketWrite([BII)V
 HSPLjava/net/SocketOutputStream;->write([BII)V
 HSPLjava/net/SocketTimeoutException;-><init>(Ljava/lang/String;)V
 HSPLjava/net/SocksSocketImpl;-><init>()V
@@ -3434,7 +3472,7 @@
 HSPLjava/net/URI;->compare(Ljava/lang/String;Ljava/lang/String;)I
 HSPLjava/net/URI;->compareIgnoringCase(Ljava/lang/String;Ljava/lang/String;)I
 HSPLjava/net/URI;->compareTo(Ljava/lang/Object;)I
-HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I+]Ljava/net/URI;Ljava/net/URI;
+HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I
 HSPLjava/net/URI;->create(Ljava/lang/String;)Ljava/net/URI;
 HSPLjava/net/URI;->decode(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/net/URI;->defineString()V
@@ -3469,7 +3507,7 @@
 HSPLjava/net/URL;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V
 HSPLjava/net/URL;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/net/URLStreamHandler;)V
 HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;)V
-HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler;
+HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V
 HSPLjava/net/URL;->createBuiltinHandler(Ljava/lang/String;)Ljava/net/URLStreamHandler;
 HSPLjava/net/URL;->getAuthority()Ljava/lang/String;
 HSPLjava/net/URL;->getFile()Ljava/lang/String;
@@ -3503,14 +3541,14 @@
 HSPLjava/net/URLConnection;->setReadTimeout(I)V
 HSPLjava/net/URLConnection;->setUseCaches(Z)V
 HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;
 HSPLjava/net/URLDecoder;->isValidHexChar(C)Z
 HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/BitSet;Ljava/util/BitSet;]Ljava/io/CharArrayWriter;Ljava/io/CharArrayWriter;
+HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;
 HSPLjava/net/URLStreamHandler;-><init>()V
-HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V+]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler;]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URL;Ljava/net/URL;
+HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V
 HSPLjava/net/URLStreamHandler;->setURL(Ljava/net/URL;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/net/URL;Ljava/net/URL;
+HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;
 HSPLjava/net/UnknownHostException;-><init>(Ljava/lang/String;)V
 HSPLjava/nio/Bits;->byteOrder()Ljava/nio/ByteOrder;
 HSPLjava/nio/Bits;->char0(C)B
@@ -3519,12 +3557,12 @@
 HSPLjava/nio/Bits;->getFloatL(Ljava/nio/ByteBuffer;I)F
 HSPLjava/nio/Bits;->getInt(Ljava/nio/ByteBuffer;IZ)I
 HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I
-HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I
 HSPLjava/nio/Bits;->getLong(Ljava/nio/ByteBuffer;IZ)J
 HSPLjava/nio/Bits;->getLongB(Ljava/nio/ByteBuffer;I)J
 HSPLjava/nio/Bits;->getLongL(Ljava/nio/ByteBuffer;I)J
 HSPLjava/nio/Bits;->getShort(Ljava/nio/ByteBuffer;IZ)S
-HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S
 HSPLjava/nio/Bits;->getShortL(Ljava/nio/ByteBuffer;I)S
 HSPLjava/nio/Bits;->int0(I)B
 HSPLjava/nio/Bits;->int1(I)B
@@ -3548,8 +3586,8 @@
 HSPLjava/nio/Bits;->putCharL(Ljava/nio/ByteBuffer;IC)V
 HSPLjava/nio/Bits;->putFloat(Ljava/nio/ByteBuffer;IFZ)V
 HSPLjava/nio/Bits;->putInt(Ljava/nio/ByteBuffer;IIZ)V
-HSPLjava/nio/Bits;->putIntB(Ljava/nio/ByteBuffer;II)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->putIntB(Ljava/nio/ByteBuffer;II)V
+HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V
 HSPLjava/nio/Bits;->putLong(Ljava/nio/ByteBuffer;IJZ)V
 HSPLjava/nio/Bits;->putLongB(Ljava/nio/ByteBuffer;IJ)V
 HSPLjava/nio/Bits;->putLongL(Ljava/nio/ByteBuffer;IJ)V
@@ -3559,7 +3597,7 @@
 HSPLjava/nio/Bits;->short0(S)B
 HSPLjava/nio/Bits;->short1(S)B
 HSPLjava/nio/Bits;->unsafe()Lsun/misc/Unsafe;
-HSPLjava/nio/Buffer;-><init>(IIIII)V+]Ljava/nio/Buffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/HeapCharBuffer;,Ljava/nio/DirectByteBuffer;,Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/Buffer;-><init>(IIIII)V
 HSPLjava/nio/Buffer;->capacity()I
 HSPLjava/nio/Buffer;->checkBounds(III)V
 HSPLjava/nio/Buffer;->checkIndex(I)I
@@ -3590,6 +3628,7 @@
 HSPLjava/nio/ByteBuffer;->arrayOffset()I
 HSPLjava/nio/ByteBuffer;->clear()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->compare(BB)I
+HSPLjava/nio/ByteBuffer;->compareTo(Ljava/lang/Object;)I+]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/ByteBuffer;->compareTo(Ljava/nio/ByteBuffer;)I
 HSPLjava/nio/ByteBuffer;->equals(BB)Z
 HSPLjava/nio/ByteBuffer;->equals(Ljava/lang/Object;)Z
@@ -3602,7 +3641,7 @@
 HSPLjava/nio/ByteBuffer;->order()Ljava/nio/ByteOrder;
 HSPLjava/nio/ByteBuffer;->order(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->position(I)Ljava/nio/Buffer;
-HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->put([B)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBuffer;->reset()Ljava/nio/Buffer;
 HSPLjava/nio/ByteBuffer;->rewind()Ljava/nio/Buffer;
@@ -3624,7 +3663,9 @@
 HSPLjava/nio/ByteBufferAsIntBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsIntBuffer;->get([III)Ljava/nio/IntBuffer;
 HSPLjava/nio/ByteBufferAsIntBuffer;->ix(I)I
-HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;+]Ljava/nio/ByteBufferAsIntBuffer;Ljava/nio/ByteBufferAsIntBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(I)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(II)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;
 HSPLjava/nio/ByteBufferAsLongBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsLongBuffer;->get([JII)Ljava/nio/LongBuffer;
 HSPLjava/nio/ByteBufferAsLongBuffer;->ix(I)I
@@ -3638,13 +3679,13 @@
 HSPLjava/nio/CharBuffer;->allocate(I)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->array()[C
 HSPLjava/nio/CharBuffer;->arrayOffset()I
-HSPLjava/nio/CharBuffer;->charAt(I)C+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;,Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/CharBuffer;->charAt(I)C
 HSPLjava/nio/CharBuffer;->clear()Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->flip()Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->get([C)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->get([CII)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->hasArray()Z
-HSPLjava/nio/CharBuffer;->length()I+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/CharBuffer;->length()I
 HSPLjava/nio/CharBuffer;->limit(I)Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->position(I)Ljava/nio/Buffer;
 HSPLjava/nio/CharBuffer;->toString()Ljava/lang/String;
@@ -3652,7 +3693,7 @@
 HSPLjava/nio/CharBuffer;->wrap(Ljava/lang/CharSequence;II)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->wrap([C)Ljava/nio/CharBuffer;
 HSPLjava/nio/CharBuffer;->wrap([CII)Ljava/nio/CharBuffer;
-HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(I)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
+HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(I)V
 HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(JLjava/lang/Object;)V
 HSPLjava/nio/DirectByteBuffer$MemoryRef;->free()V
 HSPLjava/nio/DirectByteBuffer;-><init>(IJLjava/io/FileDescriptor;Ljava/lang/Runnable;Z)V
@@ -3667,10 +3708,11 @@
 HSPLjava/nio/DirectByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer;
 HSPLjava/nio/DirectByteBuffer;->cleaner()Lsun/misc/Cleaner;
 HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/MappedByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->get()B
 HSPLjava/nio/DirectByteBuffer;->get(I)B
 HSPLjava/nio/DirectByteBuffer;->get(J)B
-HSPLjava/nio/DirectByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->get([BII)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->getChar()C
 HSPLjava/nio/DirectByteBuffer;->getChar(I)C
 HSPLjava/nio/DirectByteBuffer;->getCharUnchecked(I)C
@@ -3680,7 +3722,7 @@
 HSPLjava/nio/DirectByteBuffer;->getLong(I)J
 HSPLjava/nio/DirectByteBuffer;->getLong(J)J
 HSPLjava/nio/DirectByteBuffer;->getShort()S
-HSPLjava/nio/DirectByteBuffer;->getShort(I)S
+HSPLjava/nio/DirectByteBuffer;->getShort(I)S+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->getShort(J)S
 HSPLjava/nio/DirectByteBuffer;->getUnchecked(I[CII)V
 HSPLjava/nio/DirectByteBuffer;->getUnchecked(I[III)V
@@ -3705,6 +3747,7 @@
 HSPLjava/nio/DirectByteBuffer;->putUnchecked(I[FII)V
 HSPLjava/nio/DirectByteBuffer;->setAccessible(Z)V
 HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/MappedByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/FloatBuffer;-><init>(IIII)V
 HSPLjava/nio/FloatBuffer;-><init>(IIII[FI)V
 HSPLjava/nio/FloatBuffer;->limit(I)Ljava/nio/Buffer;
@@ -3721,25 +3764,25 @@
 HSPLjava/nio/HeapByteBuffer;->asLongBuffer()Ljava/nio/LongBuffer;
 HSPLjava/nio/HeapByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer;
-HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->duplicate()Ljava/nio/ByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->get()B
-HSPLjava/nio/HeapByteBuffer;->get(I)B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
-HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->get()B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->get(I)B
+HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->getFloat()F
 HSPLjava/nio/HeapByteBuffer;->getFloat(I)F
 HSPLjava/nio/HeapByteBuffer;->getInt()I
-HSPLjava/nio/HeapByteBuffer;->getInt(I)I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getInt(I)I
 HSPLjava/nio/HeapByteBuffer;->getLong()J
 HSPLjava/nio/HeapByteBuffer;->getLong(I)J
 HSPLjava/nio/HeapByteBuffer;->getShort()S
-HSPLjava/nio/HeapByteBuffer;->getShort(I)S+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getShort(I)S
 HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[III)V
 HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[SII)V
 HSPLjava/nio/HeapByteBuffer;->isDirect()Z
 HSPLjava/nio/HeapByteBuffer;->isReadOnly()Z
 HSPLjava/nio/HeapByteBuffer;->ix(I)I
-HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->put(IB)Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->put([BII)Ljava/nio/ByteBuffer;
 HSPLjava/nio/HeapByteBuffer;->putChar(C)Ljava/nio/ByteBuffer;
@@ -3756,9 +3799,9 @@
 HSPLjava/nio/HeapCharBuffer;-><init>([CII)V
 HSPLjava/nio/HeapCharBuffer;-><init>([CIIIIIZ)V
 HSPLjava/nio/HeapCharBuffer;-><init>([CIIZ)V
-HSPLjava/nio/HeapCharBuffer;->get(I)C+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/HeapCharBuffer;->get(I)C
 HSPLjava/nio/HeapCharBuffer;->ix(I)I
-HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/CharBuffer;Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->put([CII)Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->slice()Ljava/nio/CharBuffer;
 HSPLjava/nio/HeapCharBuffer;->toString(II)Ljava/lang/String;
@@ -3824,9 +3867,9 @@
 HSPLjava/nio/channels/SocketChannel;->validOps()I
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel$1;-><init>(Ljava/nio/channels/spi/AbstractInterruptibleChannel;)V
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;-><init>()V
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V+]Ljava/lang/Thread;Landroid/os/HandlerThread;
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V+]Ljava/lang/Thread;missing_types
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V+]Ljava/lang/Thread;Landroid/os/HandlerThread;
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V+]Ljava/nio/channels/spi/AbstractInterruptibleChannel;Lsun/nio/ch/FileChannelImpl;
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->end(Z)V
 HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->isOpen()Z
 HSPLjava/nio/channels/spi/AbstractSelectableChannel;-><init>(Ljava/nio/channels/spi/SelectorProvider;)V
@@ -3859,7 +3902,6 @@
 HSPLjava/nio/channels/spi/SelectorProvider;->provider()Ljava/nio/channels/spi/SelectorProvider;
 HSPLjava/nio/charset/Charset;-><init>(Ljava/lang/String;[Ljava/lang/String;)V
 HSPLjava/nio/charset/Charset;->aliases()Ljava/util/Set;
-HSPLjava/nio/charset/Charset;->atBugLevel(Ljava/lang/String;)Z
 HSPLjava/nio/charset/Charset;->cache(Ljava/lang/String;Ljava/nio/charset/Charset;)V
 HSPLjava/nio/charset/Charset;->checkName(Ljava/lang/String;)V
 HSPLjava/nio/charset/Charset;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;
@@ -3871,14 +3913,14 @@
 HSPLjava/nio/charset/Charset;->forName(Ljava/lang/String;)Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/Charset;->forNameUEE(Ljava/lang/String;)Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/Charset;->isSupported(Ljava/lang/String;)Z
-HSPLjava/nio/charset/Charset;->lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;
+HSPLjava/nio/charset/Charset;->lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/Charset;->lookup2(Ljava/lang/String;)Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/Charset;->name()Ljava/lang/String;
 HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FF)V
 HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FFLjava/lang/String;)V
 HSPLjava/nio/charset/CharsetDecoder;->averageCharsPerByte()F
 HSPLjava/nio/charset/CharsetDecoder;->charset()Ljava/nio/charset/Charset;
-HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
+HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;
 HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->flush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->implFlush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;
@@ -3887,9 +3929,9 @@
 HSPLjava/nio/charset/CharsetDecoder;->implReset()V
 HSPLjava/nio/charset/CharsetDecoder;->malformedInputAction()Ljava/nio/charset/CodingErrorAction;
 HSPLjava/nio/charset/CharsetDecoder;->maxCharsPerByte()F
-HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
-HSPLjava/nio/charset/CharsetDecoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
-HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
+HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;
 HSPLjava/nio/charset/CharsetDecoder;->replacement()Ljava/lang/String;
 HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/nio/charset/CharsetDecoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction;
@@ -3944,6 +3986,7 @@
 HSPLjava/nio/file/attribute/FileTime;-><init>(JLjava/util/concurrent/TimeUnit;Ljava/time/Instant;)V
 HSPLjava/nio/file/attribute/FileTime;->append(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder;
 HSPLjava/nio/file/attribute/FileTime;->from(JLjava/util/concurrent/TimeUnit;)Ljava/nio/file/attribute/FileTime;
+HSPLjava/nio/file/attribute/FileTime;->toMillis()J
 HSPLjava/nio/file/attribute/FileTime;->toString()Ljava/lang/String;
 HSPLjava/nio/file/spi/FileSystemProvider;->newInputStream(Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream;
 HSPLjava/security/AccessControlContext;-><init>([Ljava/security/ProtectionDomain;)V
@@ -3955,7 +3998,7 @@
 HSPLjava/security/CodeSigner;-><init>(Ljava/security/cert/CertPath;Ljava/security/Timestamp;)V
 HSPLjava/security/CodeSigner;->getSignerCertPath()Ljava/security/cert/CertPath;
 HSPLjava/security/DigestInputStream;-><init>(Ljava/io/InputStream;Ljava/security/MessageDigest;)V
-HSPLjava/security/DigestInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream;]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate;
+HSPLjava/security/DigestInputStream;->read([BII)I
 HSPLjava/security/DigestInputStream;->setMessageDigest(Ljava/security/MessageDigest;)V
 HSPLjava/security/GeneralSecurityException;-><init>(Ljava/lang/String;)V
 HSPLjava/security/KeyFactory;-><init>(Ljava/lang/String;)V
@@ -4063,7 +4106,7 @@
 HSPLjava/security/Provider;->getServices()Ljava/util/Set;
 HSPLjava/security/Provider;->getTypeAndAlgorithm(Ljava/lang/String;)[Ljava/lang/String;
 HSPLjava/security/Provider;->implPut(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/security/Provider;->parseLegacyPut(Ljava/lang/String;Ljava/lang/String;)V
+HSPLjava/security/Provider;->parseLegacyPut(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/security/Provider$Service;Ljava/security/Provider$Service;]Ljava/util/Map;Ljava/util/LinkedHashMap;
 HSPLjava/security/Provider;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/security/Provider;->putId()V
 HSPLjava/security/Provider;->removeInvalidServices(Ljava/util/Map;)V
@@ -4205,7 +4248,6 @@
 HSPLjava/security/spec/EllipticCurve;->getField()Ljava/security/spec/ECField;
 HSPLjava/security/spec/EncodedKeySpec;-><init>([B)V
 HSPLjava/security/spec/EncodedKeySpec;->getEncoded()[B
-HSPLjava/security/spec/NamedParameterSpec;->getName()Ljava/lang/String;
 HSPLjava/security/spec/PKCS8EncodedKeySpec;-><init>([B)V
 HSPLjava/security/spec/PKCS8EncodedKeySpec;->getEncoded()[B
 HSPLjava/security/spec/X509EncodedKeySpec;-><init>([B)V
@@ -4255,7 +4297,7 @@
 HSPLjava/text/DateFormatSymbols;->initializeSupplementaryData(Llibcore/icu/LocaleData;)V
 HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;Ljava/text/DecimalFormatSymbols;)V
-HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;
 HSPLjava/text/DecimalFormat;->equals(Ljava/lang/Object;)Z
 HSPLjava/text/DecimalFormat;->format(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
 HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
@@ -4273,29 +4315,29 @@
 HSPLjava/text/DecimalFormat;->initPattern(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormat;->isParseBigDecimal()Z
 HSPLjava/text/DecimalFormat;->isParseIntegerOnly()Z
-HSPLjava/text/DecimalFormat;->parse(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;+]Ljava/lang/Object;Ljava/lang/Long;]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->parse(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;
 HSPLjava/text/DecimalFormat;->setDecimalSeparatorAlwaysShown(Z)V
 HSPLjava/text/DecimalFormat;->setGroupingUsed(Z)V
 HSPLjava/text/DecimalFormat;->setMaximumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V
 HSPLjava/text/DecimalFormat;->setMinimumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V
 HSPLjava/text/DecimalFormat;->setParseIntegerOnly(Z)V
 HSPLjava/text/DecimalFormat;->toPattern()Ljava/lang/String;
 HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V
 HSPLjava/text/DecimalFormatSymbols;-><init>(Ljava/util/Locale;)V
 HSPLjava/text/DecimalFormatSymbols;->clone()Ljava/lang/Object;
-HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;]Landroid/icu/util/Currency;Landroid/icu/util/Currency;
+HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormatSymbols;->getCurrency()Ljava/util/Currency;
 HSPLjava/text/DecimalFormatSymbols;->getDecimalSeparator()C
 HSPLjava/text/DecimalFormatSymbols;->getGroupingSeparator()C
-HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols;
+HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/util/Currency;Ljava/util/Currency;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormatSymbols;->getInfinity()Ljava/lang/String;
 HSPLjava/text/DecimalFormatSymbols;->getInstance(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormatSymbols;->getNaN()Ljava/lang/String;
 HSPLjava/text/DecimalFormatSymbols;->getZeroDigit()C
-HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V+]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData;
-HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V+]Ljava/util/Currency;Ljava/util/Currency;]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V
+HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V
 HSPLjava/text/DecimalFormatSymbols;->maybeStripMarkers(Ljava/lang/String;C)C
 HSPLjava/text/DecimalFormatSymbols;->setCurrency(Ljava/util/Currency;)V
 HSPLjava/text/DecimalFormatSymbols;->setCurrencySymbol(Ljava/lang/String;)V
@@ -4330,9 +4372,9 @@
 HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;
 HSPLjava/text/IcuIteratorWrapper;-><init>(Landroid/icu/text/BreakIterator;)V
 HSPLjava/text/IcuIteratorWrapper;->checkOffset(ILjava/text/CharacterIterator;)V
-HSPLjava/text/IcuIteratorWrapper;->following(I)I+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->following(I)I
 HSPLjava/text/IcuIteratorWrapper;->getText()Ljava/text/CharacterIterator;
-HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z
 HSPLjava/text/IcuIteratorWrapper;->next()I
 HSPLjava/text/IcuIteratorWrapper;->preceding(I)I
 HSPLjava/text/IcuIteratorWrapper;->setText(Ljava/lang/String;)V
@@ -4341,7 +4383,7 @@
 HSPLjava/text/MessageFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
 HSPLjava/text/MessageFormat;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
 HSPLjava/text/MessageFormat;->makeFormat(II[Ljava/lang/StringBuilder;)V
-HSPLjava/text/MessageFormat;->subformat([Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;Ljava/util/List;)Ljava/lang/StringBuffer;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/FieldPosition;Ljava/text/FieldPosition;]Ljava/text/MessageFormat$Field;Ljava/text/MessageFormat$Field;
+HSPLjava/text/MessageFormat;->subformat([Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;Ljava/util/List;)Ljava/lang/StringBuffer;
 HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda0;->get()Ljava/lang/Object;
 HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda2;->get()Ljava/lang/Object;
 HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda3;->get()Ljava/lang/Object;
@@ -4352,7 +4394,6 @@
 HSPLjava/text/NumberFormat;->format(J)Ljava/lang/String;
 HSPLjava/text/NumberFormat;->getInstance()Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
-HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;I)Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getIntegerInstance()Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getIntegerInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
 HSPLjava/text/NumberFormat;->getNumberInstance(Ljava/util/Locale;)Ljava/text/NumberFormat;
@@ -4378,32 +4419,32 @@
 HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;)V
 HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;Ljava/util/Locale;)V
 HSPLjava/text/SimpleDateFormat;->checkNegativeNumberExpression()V
-HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C
 HSPLjava/text/SimpleDateFormat;->encode(IILjava/lang/StringBuilder;)V
 HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
-HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer;
+HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/text/SimpleDateFormat;->formatMonth(IIILjava/lang/StringBuffer;ZZII)Ljava/lang/String;
 HSPLjava/text/SimpleDateFormat;->formatWeekday(IIZZ)Ljava/lang/String;
 HSPLjava/text/SimpleDateFormat;->getDateTimeFormat(IILjava/util/Locale;)Ljava/lang/String;
 HSPLjava/text/SimpleDateFormat;->getExtendedTimeZoneNames()Lcom/android/icu/text/ExtendedTimeZoneNames;
 HSPLjava/text/SimpleDateFormat;->getTimeZoneNames()Landroid/icu/text/TimeZoneNames;
-HSPLjava/text/SimpleDateFormat;->initialize(Ljava/util/Locale;)V+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat;
+HSPLjava/text/SimpleDateFormat;->initialize(Ljava/util/Locale;)V
 HSPLjava/text/SimpleDateFormat;->initializeCalendar(Ljava/util/Locale;)V
 HSPLjava/text/SimpleDateFormat;->initializeDefaultCentury()V
 HSPLjava/text/SimpleDateFormat;->isDigit(C)Z
 HSPLjava/text/SimpleDateFormat;->matchString(Ljava/lang/String;II[Ljava/lang/String;Ljava/text/CalendarBuilder;)I
 HSPLjava/text/SimpleDateFormat;->parse(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;
 HSPLjava/text/SimpleDateFormat;->parseAmbiguousDatesAsAfter(Ljava/util/Date;)V
-HSPLjava/text/SimpleDateFormat;->parseInternal(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;+]Ljava/text/CalendarBuilder;Ljava/text/CalendarBuilder;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
+HSPLjava/text/SimpleDateFormat;->parseInternal(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;
 HSPLjava/text/SimpleDateFormat;->parseMonth(Ljava/lang/String;IIIILjava/text/ParsePosition;ZZLjava/text/CalendarBuilder;)I
 HSPLjava/text/SimpleDateFormat;->parseWeekday(Ljava/lang/String;IIZZLjava/text/CalendarBuilder;)I
 HSPLjava/text/SimpleDateFormat;->shouldObeyCount(II)Z
-HSPLjava/text/SimpleDateFormat;->subFormat(IILjava/text/Format$FieldDelegate;Ljava/lang/StringBuffer;Z)V+]Ljava/text/DateFormatSymbols;Ljava/text/DateFormatSymbols;]Ljava/text/Format$FieldDelegate;Ljava/text/DontCareFieldPosition$1;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
-HSPLjava/text/SimpleDateFormat;->subParse(Ljava/lang/String;IIIZ[ZLjava/text/ParsePosition;ZLjava/text/CalendarBuilder;)I+]Ljava/text/ParsePosition;Ljava/text/ParsePosition;]Ljava/text/CalendarBuilder;Ljava/text/CalendarBuilder;]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat;
+HSPLjava/text/SimpleDateFormat;->subFormat(IILjava/text/Format$FieldDelegate;Ljava/lang/StringBuffer;Z)V+]Ljava/text/Format$FieldDelegate;Ljava/text/FieldPosition$Delegate;,Ljava/text/DontCareFieldPosition$1;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar;]Ljava/text/DateFormatSymbols;Ljava/text/DateFormatSymbols;
+HSPLjava/text/SimpleDateFormat;->subParse(Ljava/lang/String;IIIZ[ZLjava/text/ParsePosition;ZLjava/text/CalendarBuilder;)I
 HSPLjava/text/SimpleDateFormat;->subParseNumericZone(Ljava/lang/String;IIIZLjava/text/CalendarBuilder;)I
 HSPLjava/text/SimpleDateFormat;->toPattern()Ljava/lang/String;
-HSPLjava/text/SimpleDateFormat;->useDateFormatSymbols()Z+]Ljava/lang/Object;Ljava/util/GregorianCalendar;]Ljava/lang/Class;Ljava/lang/Class;
-HSPLjava/text/SimpleDateFormat;->zeroPaddingNumber(IIILjava/lang/StringBuffer;)V+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat;
+HSPLjava/text/SimpleDateFormat;->useDateFormatSymbols()Z
+HSPLjava/text/SimpleDateFormat;->zeroPaddingNumber(IIILjava/lang/StringBuffer;)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat;]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;
 HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;)V
 HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;I)V
 HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;III)V
@@ -4511,7 +4552,7 @@
 HSPLjava/time/LocalDateTime;->isBefore(Ljava/time/chrono/ChronoLocalDateTime;)Z
 HSPLjava/time/LocalDateTime;->isSupported(Ljava/time/temporal/TemporalField;)Z
 HSPLjava/time/LocalDateTime;->now()Ljava/time/LocalDateTime;
-HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;+]Ljava/time/Clock;Ljava/time/Clock$SystemClock;]Ljava/time/Instant;Ljava/time/Instant;]Ljava/time/ZoneId;Ljava/time/ZoneRegion;]Ljava/time/zone/ZoneRules;Ljava/time/zone/ZoneRules;
+HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->of(Ljava/time/LocalDate;Ljava/time/LocalTime;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->ofEpochSecond(JILjava/time/ZoneOffset;)Ljava/time/LocalDateTime;
 HSPLjava/time/LocalDateTime;->ofInstant(Ljava/time/Instant;Ljava/time/ZoneId;)Ljava/time/LocalDateTime;
@@ -4597,7 +4638,7 @@
 HSPLjava/time/chrono/ChronoZonedDateTime;->getChronology()Ljava/time/chrono/Chronology;
 HSPLjava/time/chrono/ChronoZonedDateTime;->query(Ljava/time/temporal/TemporalQuery;)Ljava/lang/Object;
 HSPLjava/time/chrono/ChronoZonedDateTime;->toEpochSecond()J+]Ljava/time/ZoneOffset;Ljava/time/ZoneOffset;]Ljava/time/LocalTime;Ljava/time/LocalTime;]Ljava/time/chrono/ChronoZonedDateTime;Ljava/time/ZonedDateTime;]Ljava/time/chrono/ChronoLocalDate;Ljava/time/LocalDate;
-HSPLjava/time/chrono/ChronoZonedDateTime;->toInstant()Ljava/time/Instant;+]Ljava/time/LocalTime;Ljava/time/LocalTime;]Ljava/time/chrono/ChronoZonedDateTime;Ljava/time/ZonedDateTime;
+HSPLjava/time/chrono/ChronoZonedDateTime;->toInstant()Ljava/time/Instant;
 HSPLjava/time/chrono/IsoChronology;->isLeapYear(J)Z
 HSPLjava/time/chrono/IsoChronology;->resolveDate(Ljava/util/Map;Ljava/time/format/ResolverStyle;)Ljava/time/LocalDate;
 HSPLjava/time/chrono/IsoChronology;->resolveDate(Ljava/util/Map;Ljava/time/format/ResolverStyle;)Ljava/time/chrono/ChronoLocalDate;
@@ -4615,7 +4656,6 @@
 HSPLjava/time/format/DateTimeFormatter;->parse(Ljava/lang/CharSequence;Ljava/time/temporal/TemporalQuery;)Ljava/lang/Object;
 HSPLjava/time/format/DateTimeFormatter;->parseResolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/temporal/TemporalAccessor;
 HSPLjava/time/format/DateTimeFormatter;->parseUnresolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/format/DateTimeParseContext;
-HSPLjava/time/format/DateTimeFormatterBuilder$3;-><clinit>()V
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;-><init>(C)V
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->format(Ljava/time/format/DateTimePrintContext;Ljava/lang/StringBuilder;)Z
 HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->parse(Ljava/time/format/DateTimeParseContext;Ljava/lang/CharSequence;I)I
@@ -4761,16 +4801,16 @@
 HSPLjava/time/zone/ZoneRulesProvider;->getProvider(Ljava/lang/String;)Ljava/time/zone/ZoneRulesProvider;
 HSPLjava/time/zone/ZoneRulesProvider;->getRules(Ljava/lang/String;Z)Ljava/time/zone/ZoneRules;
 HSPLjava/util/AbstractCollection;-><init>()V
-HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/AbstractCollection;->clear()V
-HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;
+HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/AbstractCollection;->isEmpty()Z+]Ljava/util/AbstractCollection;missing_types
 HSPLjava/util/AbstractCollection;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractCollection;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/AbstractCollection;->retainAll(Ljava/util/Collection;)Z
-HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Iterator;missing_types
-HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/lang/Object;[Ljava/security/Provider;,[Ljava/lang/String;,[Ljava/lang/Object;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/AbstractList$Itr;,Ljava/util/ArrayList$SubList$1;
+HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;Ljava/util/TreeMap$KeySet;]Ljava/util/Iterator;Ljava/util/TreeMap$KeyIterator;
+HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/AbstractCollection;->toString()Ljava/lang/String;
 HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;)V
 HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;Ljava/util/AbstractList$Itr-IA;)V
@@ -4805,8 +4845,8 @@
 HSPLjava/util/AbstractList;-><init>()V
 HSPLjava/util/AbstractList;->add(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractList;->clear()V
-HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z+]Ljava/util/ListIterator;Ljava/util/ArrayList$ListItr;]Ljava/util/AbstractList;Ljava/util/ArrayList;]Ljava/util/List;Ljava/util/ArrayList;
-HSPLjava/util/AbstractList;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractList;Ljava/util/Collections$SingletonList;,Ljava/util/Arrays$ArrayList;,Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;,Ljava/util/Arrays$ArrayItr;,Ljava/util/Collections$1;
+HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/AbstractList;->hashCode()I
 HSPLjava/util/AbstractList;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/AbstractList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/AbstractList;->listIterator()Ljava/util/ListIterator;
@@ -4832,55 +4872,59 @@
 HSPLjava/util/AbstractMap;->clear()V
 HSPLjava/util/AbstractMap;->clone()Ljava/lang/Object;
 HSPLjava/util/AbstractMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/lang/Object;Ljava/lang/Boolean;]Ljava/util/Iterator;Ljava/util/Collections$UnmodifiableCollection$1;
-HSPLjava/util/AbstractMap;->hashCode()I+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/AbstractMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;,Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/HashMap$EntrySet;
-HSPLjava/util/AbstractMap;->isEmpty()Z+]Ljava/util/AbstractMap;missing_types
+HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z+]Ljava/util/Map$Entry;Ljava/util/HashMap$Node;]Ljava/lang/Object;Ljava/lang/Integer;]Ljava/util/AbstractMap;Ljava/util/HashMap;]Ljava/util/Map;Ljava/util/HashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;]Ljava/util/Set;Ljava/util/HashMap$EntrySet;
+HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/AbstractMap;->hashCode()I
+HSPLjava/util/AbstractMap;->isEmpty()Z
 HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V
-HSPLjava/util/AbstractMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/AbstractMap;->size()I
 HSPLjava/util/AbstractMap;->toString()Ljava/lang/String;
 HSPLjava/util/AbstractMap;->values()Ljava/util/Collection;
 HSPLjava/util/AbstractQueue;-><init>()V
 HSPLjava/util/AbstractQueue;->add(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractQueue;->addAll(Ljava/util/Collection;)Z
+HSPLjava/util/AbstractQueue;->addAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;Ljava/util/TreeMap$KeySet;]Ljava/util/Iterator;Ljava/util/TreeMap$KeyIterator;]Ljava/util/AbstractQueue;Ljava/util/PriorityQueue;
 HSPLjava/util/AbstractQueue;->clear()V
 HSPLjava/util/AbstractQueue;->remove()Ljava/lang/Object;
 HSPLjava/util/AbstractSequentialList;-><init>()V
 HSPLjava/util/AbstractSequentialList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/AbstractSet;-><init>()V
 HSPLjava/util/AbstractSet;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractSet;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractSet;Ljava/util/Collections$SingletonSet;,Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/LinkedHashMap$LinkedKeyIterator;,Ljava/util/Collections$1;
-HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types]Ljava/util/AbstractSet;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;,Ljava/util/LinkedHashMap$LinkedKeySet;]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractSet;->hashCode()I
+HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z
+HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;-><init>(Ljava/util/ArrayDeque;)V
+HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;->accept(Ljava/lang/Object;)V+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
 HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;)V
-HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DeqIterator-IA;)V
 HSPLjava/util/ArrayDeque$DeqIterator;->hasNext()Z
 HSPLjava/util/ArrayDeque$DeqIterator;->next()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque$DeqIterator;->postDelete(Z)V
 HSPLjava/util/ArrayDeque$DeqIterator;->remove()V
 HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;)V
-HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DescendingIterator-IA;)V
-HSPLjava/util/ArrayDeque$DescendingIterator;->hasNext()Z
 HSPLjava/util/ArrayDeque$DescendingIterator;->next()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;-><init>()V
 HSPLjava/util/ArrayDeque;-><init>(I)V
 HSPLjava/util/ArrayDeque;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/ArrayDeque;->add(Ljava/lang/Object;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
+HSPLjava/util/ArrayDeque;->addAll(Ljava/util/Collection;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;]Ljava/util/Collection;missing_types
 HSPLjava/util/ArrayDeque;->addFirst(Ljava/lang/Object;)V
 HSPLjava/util/ArrayDeque;->addLast(Ljava/lang/Object;)V
-HSPLjava/util/ArrayDeque;->allocateElements(I)V
 HSPLjava/util/ArrayDeque;->checkInvariants()V
+HSPLjava/util/ArrayDeque;->circularClear([Ljava/lang/Object;II)V
 HSPLjava/util/ArrayDeque;->clear()V
 HSPLjava/util/ArrayDeque;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayDeque;->copyElements(Ljava/util/Collection;)V+]Ljava/util/Collection;missing_types
+HSPLjava/util/ArrayDeque;->dec(II)I
 HSPLjava/util/ArrayDeque;->delete(I)Z
 HSPLjava/util/ArrayDeque;->descendingIterator()Ljava/util/Iterator;
-HSPLjava/util/ArrayDeque;->doubleCapacity()V
+HSPLjava/util/ArrayDeque;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->getFirst()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->getLast()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->grow(I)V
+HSPLjava/util/ArrayDeque;->inc(II)I
 HSPLjava/util/ArrayDeque;->isEmpty()Z
 HSPLjava/util/ArrayDeque;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayDeque;->offer(Ljava/lang/Object;)Z
 HSPLjava/util/ArrayDeque;->offerLast(Ljava/lang/Object;)Z
-HSPLjava/util/ArrayDeque;->peek()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
+HSPLjava/util/ArrayDeque;->peek()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->peekFirst()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->peekLast()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->poll()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
@@ -4894,7 +4938,9 @@
 HSPLjava/util/ArrayDeque;->removeFirstOccurrence(Ljava/lang/Object;)Z
 HSPLjava/util/ArrayDeque;->removeLast()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->size()I
+HSPLjava/util/ArrayDeque;->sub(III)I
 HSPLjava/util/ArrayDeque;->toArray()[Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->toArray(Ljava/lang/Class;)[Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/ArrayList$ArrayListSpliterator;-><init>(Ljava/util/ArrayList;III)V
 HSPLjava/util/ArrayList$ArrayListSpliterator;->characteristics()I
@@ -4903,72 +4949,92 @@
 HSPLjava/util/ArrayList$ArrayListSpliterator;->getFence()I
 HSPLjava/util/ArrayList$ArrayListSpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;)V
-HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;Ljava/util/ArrayList$Itr-IA;)V
+HSPLjava/util/ArrayList$Itr;->checkForComodification()V
 HSPLjava/util/ArrayList$Itr;->hasNext()Z
-HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object;
+HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$Itr;Ljava/util/ArrayList$ListItr;,Ljava/util/ArrayList$Itr;
 HSPLjava/util/ArrayList$Itr;->remove()V
 HSPLjava/util/ArrayList$ListItr;-><init>(Ljava/util/ArrayList;I)V
 HSPLjava/util/ArrayList$ListItr;->hasPrevious()Z
 HSPLjava/util/ArrayList$ListItr;->nextIndex()I
 HSPLjava/util/ArrayList$ListItr;->previous()Ljava/lang/Object;
 HSPLjava/util/ArrayList$ListItr;->set(Ljava/lang/Object;)V
-HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;II)V
+HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;I)V
+HSPLjava/util/ArrayList$SubList$1;->checkForComodification()V
 HSPLjava/util/ArrayList$SubList$1;->hasNext()Z
 HSPLjava/util/ArrayList$SubList$1;->next()Ljava/lang/Object;
-HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;Ljava/util/AbstractList;III)V
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetoffset(Ljava/util/ArrayList$SubList;)I
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetroot(Ljava/util/ArrayList$SubList;)Ljava/util/ArrayList;
+HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetsize(Ljava/util/ArrayList$SubList;)I
+HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;II)V
+HSPLjava/util/ArrayList$SubList;->checkForComodification()V
 HSPLjava/util/ArrayList$SubList;->get(I)Ljava/lang/Object;
 HSPLjava/util/ArrayList$SubList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayList$SubList;->listIterator(I)Ljava/util/ListIterator;
+HSPLjava/util/ArrayList$SubList;->rangeCheckForAdd(I)V
 HSPLjava/util/ArrayList$SubList;->removeRange(II)V
 HSPLjava/util/ArrayList$SubList;->size()I
 HSPLjava/util/ArrayList$SubList;->subList(II)Ljava/util/List;
+HSPLjava/util/ArrayList$SubList;->toArray()[Ljava/lang/Object;
+HSPLjava/util/ArrayList$SubList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/ArrayList;->-$$Nest$fgetsize(Ljava/util/ArrayList;)I
 HSPLjava/util/ArrayList;-><init>()V
 HSPLjava/util/ArrayList;-><init>(I)V
-HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;missing_types]Ljava/util/Collection;missing_types
+HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/ArrayList;]Ljava/util/Collection;Ljava/util/HashMap$Values;,Ljava/util/ArrayList;
 HSPLjava/util/ArrayList;->add(ILjava/lang/Object;)V
 HSPLjava/util/ArrayList;->add(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayList;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V
 HSPLjava/util/ArrayList;->addAll(ILjava/util/Collection;)Z
 HSPLjava/util/ArrayList;->addAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types
-HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;Z)Z+]Ljava/util/Collection;Ljava/util/Collections$SingletonSet;,Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;ZII)Z+]Ljava/util/Collection;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->checkForComodification(I)V
 HSPLjava/util/ArrayList;->clear()V
 HSPLjava/util/ArrayList;->clone()Ljava/lang/Object;
 HSPLjava/util/ArrayList;->contains(Ljava/lang/Object;)Z+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
+HSPLjava/util/ArrayList;->elementData(I)Ljava/lang/Object;
 HSPLjava/util/ArrayList;->ensureCapacity(I)V
-HSPLjava/util/ArrayList;->ensureCapacityInternal(I)V
-HSPLjava/util/ArrayList;->ensureExplicitCapacity(I)V
-HSPLjava/util/ArrayList;->fastRemove(I)V
+HSPLjava/util/ArrayList;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/ArrayList;->equalsArrayList(Ljava/util/ArrayList;)Z
+HSPLjava/util/ArrayList;->equalsRange(Ljava/util/List;II)Z+]Ljava/util/List;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/ArrayList;->fastRemove([Ljava/lang/Object;I)V
 HSPLjava/util/ArrayList;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object;
-HSPLjava/util/ArrayList;->grow(I)V
-HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
+HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object;+]Ljava/util/ArrayList;missing_types
+HSPLjava/util/ArrayList;->grow()[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->grow(I)[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->hashCode()I
+HSPLjava/util/ArrayList;->hashCodeRange(II)I
+HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->indexOfRange(Ljava/lang/Object;II)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;
 HSPLjava/util/ArrayList;->isEmpty()Z
 HSPLjava/util/ArrayList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ArrayList;->lastIndexOf(Ljava/lang/Object;)I
 HSPLjava/util/ArrayList;->listIterator()Ljava/util/ListIterator;
 HSPLjava/util/ArrayList;->listIterator(I)Ljava/util/ListIterator;
+HSPLjava/util/ArrayList;->newCapacity(I)I
+HSPLjava/util/ArrayList;->rangeCheckForAdd(I)V
 HSPLjava/util/ArrayList;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/ArrayList;->remove(I)Ljava/lang/Object;
-HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types
+HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/ArrayList;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;)Z
+HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;II)Z+]Ljava/util/function/Predicate;missing_types
 HSPLjava/util/ArrayList;->removeRange(II)V
 HSPLjava/util/ArrayList;->retainAll(Ljava/util/Collection;)Z
 HSPLjava/util/ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/ArrayList;->shiftTailOverGap([Ljava/lang/Object;II)V
 HSPLjava/util/ArrayList;->size()I
 HSPLjava/util/ArrayList;->sort(Ljava/util/Comparator;)V
 HSPLjava/util/ArrayList;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/ArrayList;->subList(II)Ljava/util/List;
-HSPLjava/util/ArrayList;->subListRangeCheck(III)V
 HSPLjava/util/ArrayList;->toArray()[Ljava/lang/Object;
-HSPLjava/util/ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;missing_types
 HSPLjava/util/ArrayList;->trimToSize()V
 HSPLjava/util/ArrayList;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/Arrays$ArrayItr;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/Arrays$ArrayItr;->hasNext()Z
 HSPLjava/util/Arrays$ArrayItr;->next()Ljava/lang/Object;
 HSPLjava/util/Arrays$ArrayList;-><init>([Ljava/lang/Object;)V
-HSPLjava/util/Arrays$ArrayList;->contains(Ljava/lang/Object;)Z+]Ljava/util/Arrays$ArrayList;Ljava/util/Arrays$ArrayList;
+HSPLjava/util/Arrays$ArrayList;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Arrays$ArrayList;->forEach(Ljava/util/function/Consumer;)V
 HSPLjava/util/Arrays$ArrayList;->get(I)Ljava/lang/Object;
 HSPLjava/util/Arrays$ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
@@ -4977,7 +5043,7 @@
 HSPLjava/util/Arrays$ArrayList;->size()I
 HSPLjava/util/Arrays$ArrayList;->sort(Ljava/util/Comparator;)V
 HSPLjava/util/Arrays$ArrayList;->spliterator()Ljava/util/Spliterator;
-HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;+][Ljava/lang/Object;missing_types
+HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;
 HSPLjava/util/Arrays$ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/Arrays;->asList([Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/Arrays;->binarySearch([CC)I
@@ -5035,7 +5101,7 @@
 HSPLjava/util/Arrays;->hashCode([F)I
 HSPLjava/util/Arrays;->hashCode([I)I
 HSPLjava/util/Arrays;->hashCode([J)I
-HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
+HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
 HSPLjava/util/Arrays;->rangeCheck(III)V
 HSPLjava/util/Arrays;->sort([C)V
 HSPLjava/util/Arrays;->sort([F)V
@@ -5103,9 +5169,10 @@
 HSPLjava/util/Calendar;->clone()Ljava/lang/Object;
 HSPLjava/util/Calendar;->compareTo(J)I
 HSPLjava/util/Calendar;->compareTo(Ljava/util/Calendar;)I
-HSPLjava/util/Calendar;->complete()V
+HSPLjava/util/Calendar;->complete()V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/Calendar;->createCalendar(Ljava/util/TimeZone;Ljava/util/Locale;)Ljava/util/Calendar;
-HSPLjava/util/Calendar;->get(I)I
+HSPLjava/util/Calendar;->defaultTimeZone(Ljava/util/Locale;)Ljava/util/TimeZone;
+HSPLjava/util/Calendar;->get(I)I+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/Calendar;->getFirstDayOfWeek()I
 HSPLjava/util/Calendar;->getInstance()Ljava/util/Calendar;
 HSPLjava/util/Calendar;->getInstance(Ljava/util/Locale;)Ljava/util/Calendar;
@@ -5127,19 +5194,19 @@
 HSPLjava/util/Calendar;->isPartiallyNormalized()Z
 HSPLjava/util/Calendar;->isSet(I)Z
 HSPLjava/util/Calendar;->selectFields()I
-HSPLjava/util/Calendar;->set(II)V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
+HSPLjava/util/Calendar;->set(II)V
 HSPLjava/util/Calendar;->set(III)V
 HSPLjava/util/Calendar;->set(IIIIII)V
 HSPLjava/util/Calendar;->setFieldsComputed(I)V
 HSPLjava/util/Calendar;->setFieldsNormalized(I)V
 HSPLjava/util/Calendar;->setLenient(Z)V
 HSPLjava/util/Calendar;->setTime(Ljava/util/Date;)V
-HSPLjava/util/Calendar;->setTimeInMillis(J)V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar;
+HSPLjava/util/Calendar;->setTimeInMillis(J)V
 HSPLjava/util/Calendar;->setTimeZone(Ljava/util/TimeZone;)V
 HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V
 HSPLjava/util/Calendar;->setZoneShared(Z)V
 HSPLjava/util/Calendar;->updateTime()V
-HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/LinkedList;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/LinkedList$ListItr;
+HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Landroid/util/MapCollections$KeySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
 HSPLjava/util/Collection;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/Collection;->stream()Ljava/util/stream/Stream;+]Ljava/util/Collection;megamorphic_types
 HSPLjava/util/Collections$1;-><init>(Ljava/lang/Object;)V
@@ -5183,10 +5250,10 @@
 HSPLjava/util/Collections$EmptySet;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/Collections$ReverseComparator2;-><init>(Ljava/util/Comparator;)V
 HSPLjava/util/Collections$ReverseComparator2;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
-HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
-HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I+]Ljava/lang/Comparable;Ljava/lang/String;
+HSPLjava/util/Collections$ReverseComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/util/Collections$ReverseComparator;Ljava/util/Collections$ReverseComparator;
 HSPLjava/util/Collections$SetFromMap;-><init>(Ljava/util/Map;)V
-HSPLjava/util/Collections$SetFromMap;->add(Ljava/lang/Object;)Z
+HSPLjava/util/Collections$SetFromMap;->add(Ljava/lang/Object;)Z+]Ljava/util/Map;Ljava/util/WeakHashMap;
 HSPLjava/util/Collections$SetFromMap;->clear()V
 HSPLjava/util/Collections$SetFromMap;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$SetFromMap;->forEach(Ljava/util/function/Consumer;)V
@@ -5199,6 +5266,7 @@
 HSPLjava/util/Collections$SingletonList;-><init>(Ljava/lang/Object;)V
 HSPLjava/util/Collections$SingletonList;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$SingletonList;->get(I)Ljava/lang/Object;
+HSPLjava/util/Collections$SingletonList;->hashCode()I
 HSPLjava/util/Collections$SingletonList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$SingletonList;->size()I
 HSPLjava/util/Collections$SingletonMap;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
@@ -5215,7 +5283,7 @@
 HSPLjava/util/Collections$SingletonSet;->size()I
 HSPLjava/util/Collections$SynchronizedCollection;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/Collections$SynchronizedCollection;-><init>(Ljava/util/Collection;Ljava/lang/Object;)V
-HSPLjava/util/Collections$SynchronizedCollection;->add(Ljava/lang/Object;)Z
+HSPLjava/util/Collections$SynchronizedCollection;->add(Ljava/lang/Object;)Z+]Ljava/util/Collection;Ljava/util/Collections$SetFromMap;
 HSPLjava/util/Collections$SynchronizedCollection;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/Collections$SynchronizedCollection;->clear()V
 HSPLjava/util/Collections$SynchronizedCollection;->contains(Ljava/lang/Object;)Z
@@ -5226,6 +5294,7 @@
 HSPLjava/util/Collections$SynchronizedCollection;->size()I
 HSPLjava/util/Collections$SynchronizedCollection;->toArray()[Ljava/lang/Object;
 HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/Collections$SynchronizedCollection;->toString()Ljava/lang/String;
 HSPLjava/util/Collections$SynchronizedList;-><init>(Ljava/util/List;)V
 HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object;
 HSPLjava/util/Collections$SynchronizedMap;-><init>(Ljava/util/Map;)V
@@ -5237,7 +5306,7 @@
 HSPLjava/util/Collections$SynchronizedMap;->isEmpty()Z
 HSPLjava/util/Collections$SynchronizedMap;->keySet()Ljava/util/Set;
 HSPLjava/util/Collections$SynchronizedMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map;Ljava/util/HashMap;
+HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/Collections$SynchronizedMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Collections$SynchronizedMap;->size()I
 HSPLjava/util/Collections$SynchronizedMap;->values()Ljava/util/Collection;
@@ -5245,19 +5314,19 @@
 HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;)V
 HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;Ljava/lang/Object;)V
 HSPLjava/util/Collections$SynchronizedSet;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V
 HSPLjava/util/Collections$UnmodifiableCollection$1;->hasNext()Z+]Ljava/util/Iterator;missing_types
-HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;megamorphic_types
 HSPLjava/util/Collections$UnmodifiableCollection;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z+]Ljava/util/Collection;megamorphic_types
-HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;Ljava/util/HashSet;
+HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/Collections$UnmodifiableCollection;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z
 HSPLjava/util/Collections$UnmodifiableCollection;->iterator()Ljava/util/Iterator;
-HSPLjava/util/Collections$UnmodifiableCollection;->size()I+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->size()I
 HSPLjava/util/Collections$UnmodifiableCollection;->stream()Ljava/util/stream/Stream;
-HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/Collection;megamorphic_types
-HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;
+HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/Collections$UnmodifiableCollection;->toString()Ljava/lang/String;
 HSPLjava/util/Collections$UnmodifiableList$1;-><init>(Ljava/util/Collections$UnmodifiableList;I)V
 HSPLjava/util/Collections$UnmodifiableList$1;->hasNext()Z
@@ -5265,18 +5334,18 @@
 HSPLjava/util/Collections$UnmodifiableList$1;->nextIndex()I
 HSPLjava/util/Collections$UnmodifiableList;-><init>(Ljava/util/List;)V
 HSPLjava/util/Collections$UnmodifiableList;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;+]Ljava/util/List;missing_types
+HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;
 HSPLjava/util/Collections$UnmodifiableList;->hashCode()I
 HSPLjava/util/Collections$UnmodifiableList;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/Collections$UnmodifiableList;->listIterator()Ljava/util/ListIterator;
 HSPLjava/util/Collections$UnmodifiableList;->listIterator(I)Ljava/util/ListIterator;
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;-><init>(Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;)V
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->hasNext()Z+]Ljava/util/Iterator;missing_types
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->next()Ljava/lang/Object;
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->next()Ljava/util/Map$Entry;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;-><init>(Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;)V+]Ljava/util/Collection;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/Collections$EmptySet;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->hasNext()Z+]Ljava/util/Iterator;Ljava/util/Collections$EmptyIterator;,Ljava/util/LinkedHashMap$LinkedEntryIterator;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->next()Ljava/lang/Object;+]Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->next()Ljava/util/Map$Entry;+]Ljava/util/Iterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
 HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;-><init>(Ljava/util/Map$Entry;)V
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;->getKey()Ljava/lang/Object;+]Ljava/util/Map$Entry;missing_types
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;->getValue()Ljava/lang/Object;+]Ljava/util/Map$Entry;missing_types
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;->getKey()Ljava/lang/Object;+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;->getValue()Ljava/lang/Object;+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;
 HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;-><init>(Ljava/util/Set;)V
 HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$UnmodifiableMap;-><init>(Ljava/util/Map;)V
@@ -5284,11 +5353,11 @@
 HSPLjava/util/Collections$UnmodifiableMap;->entrySet()Ljava/util/Set;
 HSPLjava/util/Collections$UnmodifiableMap;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$UnmodifiableMap;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;missing_types
+HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Collections$UnmodifiableMap;->hashCode()I
 HSPLjava/util/Collections$UnmodifiableMap;->isEmpty()Z
 HSPLjava/util/Collections$UnmodifiableMap;->keySet()Ljava/util/Set;
-HSPLjava/util/Collections$UnmodifiableMap;->size()I
+HSPLjava/util/Collections$UnmodifiableMap;->size()I+]Ljava/util/Map;missing_types
 HSPLjava/util/Collections$UnmodifiableMap;->toString()Ljava/lang/String;
 HSPLjava/util/Collections$UnmodifiableMap;->values()Ljava/util/Collection;
 HSPLjava/util/Collections$UnmodifiableRandomAccessList;-><init>(Ljava/util/List;)V
@@ -5297,7 +5366,7 @@
 HSPLjava/util/Collections$UnmodifiableSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Collections$UnmodifiableSortedMap;-><init>(Ljava/util/SortedMap;)V
 HSPLjava/util/Collections$UnmodifiableSortedSet;-><init>(Ljava/util/SortedSet;)V
-HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z
+HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z+]Ljava/util/Collection;Ljava/util/ArrayList;
 HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;)I
 HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I
 HSPLjava/util/Collections;->disjoint(Ljava/util/Collection;Ljava/util/Collection;)Z
@@ -5339,7 +5408,7 @@
 HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Collections;->unmodifiableCollection(Ljava/util/Collection;)Ljava/util/Collection;
 HSPLjava/util/Collections;->unmodifiableList(Ljava/util/List;)Ljava/util/List;
-HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;
+HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;+]Ljava/lang/Object;missing_types
 HSPLjava/util/Collections;->unmodifiableSet(Ljava/util/Set;)Ljava/util/Set;
 HSPLjava/util/Collections;->unmodifiableSortedMap(Ljava/util/SortedMap;)Ljava/util/SortedMap;
 HSPLjava/util/Collections;->unmodifiableSortedSet(Ljava/util/SortedSet;)Ljava/util/SortedSet;
@@ -5372,11 +5441,14 @@
 HSPLjava/util/Comparator;->lambda$comparingInt$7b0bb60$1(Ljava/util/function/ToIntFunction;Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Comparator;->lambda$thenComparing$36697e65$1(Ljava/util/Comparator;Ljava/util/Comparator;Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Comparator;->naturalOrder()Ljava/util/Comparator;
+HSPLjava/util/Comparator;->nullsFirst(Ljava/util/Comparator;)Ljava/util/Comparator;
 HSPLjava/util/Comparator;->reversed()Ljava/util/Comparator;
 HSPLjava/util/Comparator;->thenComparing(Ljava/util/Comparator;)Ljava/util/Comparator;
 HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;
 HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
 HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/util/Comparators$NullComparator;-><init>(ZLjava/util/Comparator;)V
+HSPLjava/util/Comparators$NullComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/Currency;-><init>(Landroid/icu/util/Currency;)V
 HSPLjava/util/Currency;->getCurrencyCode()Ljava/lang/String;
 HSPLjava/util/Currency;->getInstance(Ljava/lang/String;)Ljava/util/Currency;
@@ -5405,16 +5477,19 @@
 HSPLjava/util/Date;->toInstant()Ljava/time/Instant;
 HSPLjava/util/Date;->toString()Ljava/lang/String;
 HSPLjava/util/Dictionary;-><init>()V
-HSPLjava/util/DualPivotQuicksort;->doSort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->doSort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([CIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->sort([FIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([IIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([III[III)V
-HSPLjava/util/DualPivotQuicksort;->sort([JIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([JII[JII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([CII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([FII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([III)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([JII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[JIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([CIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([JIII)V
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[III)Z
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[JII)Z
 HSPLjava/util/EnumMap$EntryIterator$Entry;-><init>(Ljava/util/EnumMap$EntryIterator;I)V
 HSPLjava/util/EnumMap$EntryIterator$Entry;->checkIndexForEntryUse()V
 HSPLjava/util/EnumMap$EntryIterator$Entry;->getKey()Ljava/lang/Enum;
@@ -5445,9 +5520,9 @@
 HSPLjava/util/EnumMap;->clear()V
 HSPLjava/util/EnumMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/EnumMap;->entrySet()Ljava/util/Set;
-HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Enum;missing_types
+HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/EnumMap;->getKeyUniverse(Ljava/lang/Class;)[Ljava/lang/Enum;
-HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types
+HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z
 HSPLjava/util/EnumMap;->keySet()Ljava/util/Set;
 HSPLjava/util/EnumMap;->maskNull(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;
@@ -5479,18 +5554,20 @@
 HSPLjava/util/Formatter$Conversion;->isText(C)Z
 HSPLjava/util/Formatter$Conversion;->isValid(C)Z
 HSPLjava/util/Formatter$DateTime;->isValid(C)Z
-HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;)V
+HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;II)V
 HSPLjava/util/Formatter$FixedString;->index()I
-HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Appendable;Ljava/lang/StringBuilder;,Ljava/io/PrintWriter;
+HSPLjava/util/Formatter$Flags;->-$$Nest$madd(Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;-><init>(I)V
 HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->contains(Ljava/util/Formatter$Flags;)Z+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->parse(C)Ljava/util/Formatter$Flags;
-HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;)Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;II)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$Flags;->valueOf()I
 HSPLjava/util/Formatter$FormatSpecifier;-><init>(Ljava/util/Formatter;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLjava/util/Formatter$FormatSpecifier;->addZeros([CI)[C
+HSPLjava/util/Formatter$FormatSpecifier;->addZeros(Ljava/lang/StringBuilder;I)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifier;->adjustWidth(ILjava/util/Formatter$Flags;Z)I
+HSPLjava/util/Formatter$FormatSpecifier;->appendJustified(Ljava/lang/Appendable;Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/CharSequence;Ljava/lang/StringBuilder;,Ljava/lang/String;]Ljava/lang/Appendable;megamorphic_types
 HSPLjava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->checkCharacter()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkDateTime()V
@@ -5499,42 +5576,40 @@
 HSPLjava/util/Formatter$FormatSpecifier;->checkInteger()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->checkText()V
-HSPLjava/util/Formatter$FormatSpecifier;->conversion(Ljava/lang/String;)C
+HSPLjava/util/Formatter$FormatSpecifier;->conversion(C)C
 HSPLjava/util/Formatter$FormatSpecifier;->flags(Ljava/lang/String;)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->getZero(Ljava/util/Locale;)C+]Ljava/util/Formatter;Ljava/util/Formatter;]Ljava/util/Locale;Ljava/util/Locale;
 HSPLjava/util/Formatter$FormatSpecifier;->index()I
 HSPLjava/util/Formatter$FormatSpecifier;->index(Ljava/lang/String;)I
-HSPLjava/util/Formatter$FormatSpecifier;->justify(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Formatter$FormatSpecifier;->leadingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;JLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;
-HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;[CLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;Ljava/lang/CharSequence;ILjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale;]Ljava/lang/CharSequence;Ljava/lang/StringBuilder;,Ljava/lang/String;]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;
 HSPLjava/util/Formatter$FormatSpecifier;->precision(Ljava/lang/String;)I
 HSPLjava/util/Formatter$FormatSpecifier;->print(BLjava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->print(DLjava/util/Locale;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->print(DLjava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(FLjava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(ILjava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->print(JLjava/util/Locale;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->print(JLjava/util/Locale;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;Ljava/util/Locale;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;DLjava/util/Locale;Ljava/util/Formatter$Flags;CIZ)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;Ljava/util/Calendar;CLjava/util/Locale;)Ljava/lang/Appendable;
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/math/BigInteger;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/util/Calendar;CLjava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printBoolean(Ljava/lang/Object;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;)V
 HSPLjava/util/Formatter$FormatSpecifier;->printDateTime(Ljava/lang/Object;Ljava/util/Locale;)V
 HSPLjava/util/Formatter$FormatSpecifier;->printFloat(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer;
+HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Byte;Ljava/lang/Byte;
 HSPLjava/util/Formatter$FormatSpecifier;->printString(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Object;missing_types
-HSPLjava/util/Formatter$FormatSpecifier;->trailingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->trailingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$FormatSpecifier;->trailingZeros(Ljava/lang/StringBuilder;I)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifier;->width(Ljava/lang/String;)I
-HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V
+HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Formatter$FormatSpecifierParser;->advance()C
 HSPLjava/util/Formatter$FormatSpecifierParser;->back(I)V
 HSPLjava/util/Formatter$FormatSpecifierParser;->getEndIdx()I
 HSPLjava/util/Formatter$FormatSpecifierParser;->getFormatSpecifier()Ljava/util/Formatter$FormatSpecifier;
 HSPLjava/util/Formatter$FormatSpecifierParser;->isEnd()Z
-HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String;
+HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/util/Formatter$FormatSpecifierParser;->nextIsInt()Z
 HSPLjava/util/Formatter$FormatSpecifierParser;->peek()C
 HSPLjava/util/Formatter;->-$$Nest$fgeta(Ljava/util/Formatter;)Ljava/lang/Appendable;
@@ -5546,13 +5621,13 @@
 HSPLjava/util/Formatter;-><init>(Ljava/util/Locale;Ljava/lang/Appendable;)V
 HSPLjava/util/Formatter;->close()V
 HSPLjava/util/Formatter;->ensureOpen()V
-HSPLjava/util/Formatter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;
-HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier;
-HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C
+HSPLjava/util/Formatter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/Formatter;Ljava/util/Formatter;
+HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;
+HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C+]Ljava/util/Locale;Ljava/util/Locale;]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData;
 HSPLjava/util/Formatter;->locale()Ljava/util/Locale;
 HSPLjava/util/Formatter;->nonNullAppendable(Ljava/lang/Appendable;)Ljava/lang/Appendable;
 HSPLjava/util/Formatter;->out()Ljava/lang/Appendable;
-HSPLjava/util/Formatter;->parse(Ljava/lang/String;)[Ljava/util/Formatter$FormatString;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/util/Formatter;->parse(Ljava/lang/String;)Ljava/util/List;+]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList;
 HSPLjava/util/Formatter;->toString()Ljava/lang/String;+]Ljava/lang/Object;Ljava/lang/StringBuilder;
 HSPLjava/util/GregorianCalendar;-><init>()V
 HSPLjava/util/GregorianCalendar;-><init>(IIIIII)V
@@ -5564,12 +5639,12 @@
 HSPLjava/util/GregorianCalendar;->adjustForZoneAndDaylightSavingsTime(IJLjava/util/TimeZone;)J
 HSPLjava/util/GregorianCalendar;->clone()Ljava/lang/Object;
 HSPLjava/util/GregorianCalendar;->computeFields()V
-HSPLjava/util/GregorianCalendar;->computeFields(II)I+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;
-HSPLjava/util/GregorianCalendar;->computeTime()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
+HSPLjava/util/GregorianCalendar;->computeFields(II)I
+HSPLjava/util/GregorianCalendar;->computeTime()V
 HSPLjava/util/GregorianCalendar;->getActualMaximum(I)I
 HSPLjava/util/GregorianCalendar;->getCalendarDate(J)Lsun/util/calendar/BaseCalendar$Date;
 HSPLjava/util/GregorianCalendar;->getCurrentFixedDate()J
-HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
+HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J
 HSPLjava/util/GregorianCalendar;->getGregorianCutoverDate()Lsun/util/calendar/BaseCalendar$Date;
 HSPLjava/util/GregorianCalendar;->getJulianCalendarSystem()Lsun/util/calendar/BaseCalendar;
 HSPLjava/util/GregorianCalendar;->getLeastMaximum(I)I
@@ -5577,7 +5652,7 @@
 HSPLjava/util/GregorianCalendar;->getMinimum(I)I
 HSPLjava/util/GregorianCalendar;->getNormalizedCalendar()Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->getTimeZone()Ljava/util/TimeZone;
-HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I
+HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->internalGetEra()I
 HSPLjava/util/GregorianCalendar;->isCutoverYear(I)Z
 HSPLjava/util/GregorianCalendar;->isLeapYear(I)Z
@@ -5611,9 +5686,12 @@
 HSPLjava/util/HashMap$KeySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/HashMap$KeySet;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/HashMap$KeySet;->size()I
+HSPLjava/util/HashMap$KeySet;->toArray()[Ljava/lang/Object;
+HSPLjava/util/HashMap$KeySet;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap$KeySpliterator;-><init>(Ljava/util/HashMap;IIII)V
 HSPLjava/util/HashMap$KeySpliterator;->characteristics()I
 HSPLjava/util/HashMap$KeySpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V
+HSPLjava/util/HashMap$KeySpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/HashMap$Node;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap$Node;->getKey()Ljava/lang/Object;
 HSPLjava/util/HashMap$Node;->getValue()Ljava/lang/Object;
@@ -5621,7 +5699,7 @@
 HSPLjava/util/HashMap$Node;->setValue(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/HashMap$TreeNode;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap$TreeNode;->balanceInsertion(Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;)Ljava/util/HashMap$TreeNode;
-HSPLjava/util/HashMap$TreeNode;->find(ILjava/lang/Object;Ljava/lang/Class;)Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap$TreeNode;->find(ILjava/lang/Object;Ljava/lang/Class;)Ljava/util/HashMap$TreeNode;+]Ljava/lang/Object;Ljava/lang/Long;
 HSPLjava/util/HashMap$TreeNode;->getTreeNode(ILjava/lang/Object;)Ljava/util/HashMap$TreeNode;
 HSPLjava/util/HashMap$TreeNode;->moveRootToFront([Ljava/util/HashMap$Node;Ljava/util/HashMap$TreeNode;)V
 HSPLjava/util/HashMap$TreeNode;->putTreeVal(Ljava/util/HashMap;[Ljava/util/HashMap$Node;ILjava/lang/Object;Ljava/lang/Object;)Ljava/util/HashMap$TreeNode;
@@ -5631,7 +5709,7 @@
 HSPLjava/util/HashMap$TreeNode;->treeify([Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap$TreeNode;->untreeify(Ljava/util/HashMap;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap$ValueIterator;-><init>(Ljava/util/HashMap;)V
-HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object;+]Ljava/util/HashMap$ValueIterator;Ljava/util/HashMap$ValueIterator;
+HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object;
 HSPLjava/util/HashMap$ValueSpliterator;-><init>(Ljava/util/HashMap;IIII)V
 HSPLjava/util/HashMap$ValueSpliterator;->characteristics()I
 HSPLjava/util/HashMap$ValueSpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V
@@ -5641,6 +5719,8 @@
 HSPLjava/util/HashMap$Values;->iterator()Ljava/util/Iterator;
 HSPLjava/util/HashMap$Values;->size()I
 HSPLjava/util/HashMap$Values;->spliterator()Ljava/util/Spliterator;
+HSPLjava/util/HashMap$Values;->toArray()[Ljava/lang/Object;
+HSPLjava/util/HashMap$Values;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;-><init>()V
 HSPLjava/util/HashMap;-><init>(I)V
 HSPLjava/util/HashMap;-><init>(IF)V
@@ -5656,25 +5736,27 @@
 HSPLjava/util/HashMap;->containsValue(Ljava/lang/Object;)Z
 HSPLjava/util/HashMap;->entrySet()Ljava/util/Set;
 HSPLjava/util/HashMap;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;megamorphic_types
-HSPLjava/util/HashMap;->getNode(ILjava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;missing_types
+HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
+HSPLjava/util/HashMap;->getNode(Ljava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/HashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/HashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
+HSPLjava/util/HashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/HashMap;->internalWriteEntries(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/HashMap;->isEmpty()Z
 HSPLjava/util/HashMap;->keySet()Ljava/util/Set;
+HSPLjava/util/HashMap;->keysToArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->loadFactor()F
 HSPLjava/util/HashMap;->merge(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
 HSPLjava/util/HashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->prepareArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
-HSPLjava/util/HashMap;->putAll(Ljava/util/Map;)V+]Ljava/util/HashMap;missing_types
+HSPLjava/util/HashMap;->putAll(Ljava/util/Map;)V
 HSPLjava/util/HashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/HashMap;->putMapEntries(Ljava/util/Map;Z)V+]Ljava/util/HashMap;missing_types]Ljava/util/Map$Entry;megamorphic_types]Ljava/util/Map;megamorphic_types]Ljava/util/Iterator;megamorphic_types]Ljava/util/Set;megamorphic_types
+HSPLjava/util/HashMap;->putMapEntries(Ljava/util/Map;Z)V+]Ljava/util/HashMap;missing_types]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;,Ljava/util/HashMap$Node;,Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;]Ljava/util/Map;missing_types]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;,Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;,Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;,Ljava/util/HashMap$EntrySet;
 HSPLjava/util/HashMap;->putVal(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;megamorphic_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
 HSPLjava/util/HashMap;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/HashMap;->reinitialize()V
-HSPLjava/util/HashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types
+HSPLjava/util/HashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
 HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
 HSPLjava/util/HashMap;->replacementNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
@@ -5683,6 +5765,7 @@
 HSPLjava/util/HashMap;->tableSizeFor(I)I
 HSPLjava/util/HashMap;->treeifyBin([Ljava/util/HashMap$Node;I)V
 HSPLjava/util/HashMap;->values()Ljava/util/Collection;
+HSPLjava/util/HashMap;->valuesToArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/HashMap;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/HashSet;-><init>()V
 HSPLjava/util/HashSet;-><init>(I)V
@@ -5694,10 +5777,10 @@
 HSPLjava/util/HashSet;->clone()Ljava/lang/Object;
 HSPLjava/util/HashSet;->contains(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
 HSPLjava/util/HashSet;->isEmpty()Z
-HSPLjava/util/HashSet;->iterator()Ljava/util/Iterator;+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Set;Ljava/util/HashMap$KeySet;,Ljava/util/LinkedHashMap$LinkedKeySet;
+HSPLjava/util/HashSet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/HashSet;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
-HSPLjava/util/HashSet;->size()I+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
+HSPLjava/util/HashSet;->size()I
 HSPLjava/util/HashSet;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/HashSet;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/Hashtable$EntrySet;-><init>(Ljava/util/Hashtable;)V
@@ -5727,7 +5810,7 @@
 HSPLjava/util/Hashtable;->clone()Ljava/lang/Object;
 HSPLjava/util/Hashtable;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/Hashtable;->entrySet()Ljava/util/Set;
-HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Ljava/lang/String;
+HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Hashtable;->getEnumeration(I)Ljava/util/Enumeration;
 HSPLjava/util/Hashtable;->getIterator(I)Ljava/util/Iterator;
 HSPLjava/util/Hashtable;->isEmpty()Z
@@ -5744,10 +5827,11 @@
 HSPLjava/util/IdentityHashMap$EntryIterator$Entry;->getKey()Ljava/lang/Object;
 HSPLjava/util/IdentityHashMap$EntryIterator$Entry;->getValue()Ljava/lang/Object;
 HSPLjava/util/IdentityHashMap$EntryIterator;-><init>(Ljava/util/IdentityHashMap;)V
-HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/lang/Object;
-HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/util/Map$Entry;
+HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/lang/Object;+]Ljava/util/IdentityHashMap$EntryIterator;Ljava/util/IdentityHashMap$EntryIterator;
+HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/IdentityHashMap$EntryIterator;Ljava/util/IdentityHashMap$EntryIterator;
 HSPLjava/util/IdentityHashMap$EntrySet;-><init>(Ljava/util/IdentityHashMap;)V
 HSPLjava/util/IdentityHashMap$EntrySet;->iterator()Ljava/util/Iterator;
+HSPLjava/util/IdentityHashMap$EntrySet;->size()I
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;-><init>(Ljava/util/IdentityHashMap;)V
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->hasNext()Z
 HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->nextIndex()I
@@ -5783,29 +5867,32 @@
 HSPLjava/util/IdentityHashMap;->values()Ljava/util/Collection;
 HSPLjava/util/ImmutableCollections$AbstractImmutableCollection;-><init>()V
 HSPLjava/util/ImmutableCollections$AbstractImmutableList;-><init>()V
-HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator;+]Ljava/util/ImmutableCollections$AbstractImmutableList;Ljava/util/ImmutableCollections$ListN;,Ljava/util/ImmutableCollections$List12;
+HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/ImmutableCollections$AbstractImmutableMap;-><init>()V
 HSPLjava/util/ImmutableCollections$AbstractImmutableSet;-><init>()V
 HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$List12;->get(I)Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$List12;->size()I
+HSPLjava/util/ImmutableCollections$ListItr;-><init>(Ljava/util/List;I)V
+HSPLjava/util/ImmutableCollections$ListItr;->hasNext()Z
+HSPLjava/util/ImmutableCollections$ListItr;->next()Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$ListN;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$ListN;->get(I)Ljava/lang/Object;
 HSPLjava/util/ImmutableCollections$ListN;->size()I
+HSPLjava/util/ImmutableCollections$Map1;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$Map1;->entrySet()Ljava/util/Set;
 HSPLjava/util/ImmutableCollections$MapN;-><init>([Ljava/lang/Object;)V
+HSPLjava/util/ImmutableCollections$MapN;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/ImmutableCollections$MapN;->get(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Landroid/hardware/biometrics/BiometricSourceType;
-HSPLjava/util/ImmutableCollections$Set0;->instance()Ljava/util/ImmutableCollections$Set0;
-HSPLjava/util/ImmutableCollections$Set1;-><init>(Ljava/lang/Object;)V
-HSPLjava/util/ImmutableCollections$Set1;->iterator()Ljava/util/Iterator;
+HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I
 HSPLjava/util/ImmutableCollections$SetN;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/ImmutableCollections$SetN;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/util/Pair;,Ljava/lang/String;
+HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I
 HSPLjava/util/ImmutableCollections;-><clinit>()V
 HSPLjava/util/ImmutableCollections;->emptyList()Ljava/util/List;
-HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;,Landroid/util/MapCollections$ArrayIterator;
+HSPLjava/util/ImmutableCollections;->listCopy(Ljava/util/Collection;)Ljava/util/List;
+HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/stream/ReferencePipeline$3$1;]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;
 HSPLjava/util/JumboEnumSet$EnumSetIterator;-><init>(Ljava/util/JumboEnumSet;)V
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->hasNext()Z
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->next()Ljava/lang/Enum;
@@ -5821,24 +5908,24 @@
 HSPLjava/util/KeyValueHolder;->getKey()Ljava/lang/Object;
 HSPLjava/util/KeyValueHolder;->getValue()Ljava/lang/Object;
 HSPLjava/util/LinkedHashMap$LinkedEntryIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/LinkedHashMap$LinkedEntrySet;->size()I
 HSPLjava/util/LinkedHashMap$LinkedHashIterator;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedHashIterator;->hasNext()Z
 HSPLjava/util/LinkedHashMap$LinkedHashIterator;->nextNode()Ljava/util/LinkedHashMap$LinkedHashMapEntry;
-HSPLjava/util/LinkedHashMap$LinkedHashIterator;->remove()V+]Ljava/util/LinkedHashMap;Ljava/util/LinkedHashMap;
+HSPLjava/util/LinkedHashMap$LinkedHashIterator;->remove()V
 HSPLjava/util/LinkedHashMap$LinkedHashMapEntry;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
 HSPLjava/util/LinkedHashMap$LinkedKeyIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedHashMapEntry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/LinkedHashMap$LinkedKeyIterator;Ljava/util/LinkedHashMap$LinkedKeyIterator;
+HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/LinkedHashMap$LinkedKeySet;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedKeySet;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/LinkedHashMap$LinkedKeySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/LinkedHashMap$LinkedKeySet;->size()I
 HSPLjava/util/LinkedHashMap$LinkedValueIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedValueIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedValueIterator;Ljava/util/LinkedHashMap$LinkedValueIterator;
+HSPLjava/util/LinkedHashMap$LinkedValueIterator;->next()Ljava/lang/Object;
 HSPLjava/util/LinkedHashMap$LinkedValues;-><init>(Ljava/util/LinkedHashMap;)V
 HSPLjava/util/LinkedHashMap$LinkedValues;->iterator()Ljava/util/Iterator;
 HSPLjava/util/LinkedHashMap$LinkedValues;->size()I
@@ -5857,6 +5944,7 @@
 HSPLjava/util/LinkedHashMap;->keySet()Ljava/util/Set;
 HSPLjava/util/LinkedHashMap;->linkNodeLast(Ljava/util/LinkedHashMap$LinkedHashMapEntry;)V
 HSPLjava/util/LinkedHashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
+HSPLjava/util/LinkedHashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
 HSPLjava/util/LinkedHashMap;->reinitialize()V
 HSPLjava/util/LinkedHashMap;->removeEldestEntry(Ljava/util/Map$Entry;)Z
 HSPLjava/util/LinkedHashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
@@ -5866,12 +5954,12 @@
 HSPLjava/util/LinkedHashSet;-><init>(I)V
 HSPLjava/util/LinkedHashSet;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/LinkedList$ListItr;-><init>(Ljava/util/LinkedList;I)V
-HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V+]Ljava/util/LinkedList;Ljava/util/LinkedList;]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList$ListItr;->checkForComodification()V
 HSPLjava/util/LinkedList$ListItr;->hasNext()Z
 HSPLjava/util/LinkedList$ListItr;->hasPrevious()Z
 HSPLjava/util/LinkedList$ListItr;->next()Ljava/lang/Object;
-HSPLjava/util/LinkedList$ListItr;->previous()Ljava/lang/Object;+]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;->previous()Ljava/lang/Object;
 HSPLjava/util/LinkedList$ListItr;->remove()V
 HSPLjava/util/LinkedList$ListItr;->set(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList$Node;-><init>(Ljava/util/LinkedList$Node;Ljava/lang/Object;Ljava/util/LinkedList$Node;)V
@@ -5905,6 +5993,7 @@
 HSPLjava/util/LinkedList;->peekLast()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->poll()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->pollFirst()Ljava/lang/Object;
+HSPLjava/util/LinkedList;->pollLast()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->pop()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->push(Ljava/lang/Object;)V
 HSPLjava/util/LinkedList;->remove()Ljava/lang/Object;
@@ -5919,11 +6008,13 @@
 HSPLjava/util/LinkedList;->unlink(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
 HSPLjava/util/LinkedList;->unlinkFirst(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
 HSPLjava/util/LinkedList;->unlinkLast(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
+HSPLjava/util/List;->copyOf(Ljava/util/Collection;)Ljava/util/List;
 HSPLjava/util/List;->of()Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
-HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;]Ljava/util/List;Ljava/util/LinkedList;
+HSPLjava/util/List;->of([Ljava/lang/Object;)Ljava/util/List;
+HSPLjava/util/List;->sort(Ljava/util/Comparator;)V
 HSPLjava/util/List;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/Locale$Builder;-><init>()V
 HSPLjava/util/Locale$Builder;->build()Ljava/util/Locale;
@@ -5932,12 +6023,12 @@
 HSPLjava/util/Locale$Builder;->setScript(Ljava/lang/String;)Ljava/util/Locale$Builder;
 HSPLjava/util/Locale$Builder;->setVariant(Ljava/lang/String;)Ljava/util/Locale$Builder;
 HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Locale$Cache;->createObject(Ljava/util/Locale$LocaleKey;)Ljava/util/Locale;
+HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/util/Locale;
 HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetbase(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetexts(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/LocaleExtensions;
 HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)V
 HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;Ljava/util/Locale$LocaleKey-IA;)V
-HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
+HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/Locale$LocaleKey;->hashCode()I
 HSPLjava/util/Locale;-><init>(Ljava/lang/String;)V
 HSPLjava/util/Locale;-><init>(Ljava/lang/String;Ljava/lang/String;)V
@@ -5946,13 +6037,13 @@
 HSPLjava/util/Locale;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;Ljava/util/Locale-IA;)V
 HSPLjava/util/Locale;->cleanCache()V
 HSPLjava/util/Locale;->clone()Ljava/lang/Object;
-HSPLjava/util/Locale;->convertOldISOCodes(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/Locale;->convertOldISOCodes(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Locale;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->forLanguageTag(Ljava/lang/String;)Ljava/util/Locale;
 HSPLjava/util/Locale;->getAvailableLocales()[Ljava/util/Locale;
 HSPLjava/util/Locale;->getBaseLocale()Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getCompatibilityExtensions(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/LocaleExtensions;
-HSPLjava/util/Locale;->getCountry()Ljava/lang/String;
+HSPLjava/util/Locale;->getCountry()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getDefault()Ljava/util/Locale;
 HSPLjava/util/Locale;->getDefault(Ljava/util/Locale$Category;)Ljava/util/Locale;
 HSPLjava/util/Locale;->getDisplayCountry(Ljava/util/Locale;)Ljava/lang/String;
@@ -5966,22 +6057,25 @@
 HSPLjava/util/Locale;->getInstance(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)Ljava/util/Locale;
 HSPLjava/util/Locale;->getLanguage()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->getScript()Ljava/lang/String;
-HSPLjava/util/Locale;->getVariant()Ljava/lang/String;
+HSPLjava/util/Locale;->getUnicodeLocaleType(Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/util/Locale;->getVariant()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 HSPLjava/util/Locale;->hasExtensions()Z
 HSPLjava/util/Locale;->hashCode()I
+HSPLjava/util/Locale;->isUnicodeExtensionKey(Ljava/lang/String;)Z
 HSPLjava/util/Locale;->isValidBcp47Alpha(Ljava/lang/String;II)Z
 HSPLjava/util/Locale;->normalizeAndValidateLanguage(Ljava/lang/String;Z)Ljava/lang/String;
 HSPLjava/util/Locale;->normalizeAndValidateRegion(Ljava/lang/String;Z)Ljava/lang/String;
-HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$GetField;Ljava/io/ObjectInputStream$GetFieldImpl;
-HSPLjava/util/Locale;->readResolve()Ljava/lang/Object;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
+HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V
+HSPLjava/util/Locale;->readResolve()Ljava/lang/Object;
 HSPLjava/util/Locale;->setDefault(Ljava/util/Locale$Category;Ljava/util/Locale;)V
 HSPLjava/util/Locale;->setDefault(Ljava/util/Locale;)V
-HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String;
+HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/LanguageTag;Lsun/util/locale/LanguageTag;]Ljava/util/List;Ljava/util/Collections$EmptyList;]Ljava/util/Iterator;Ljava/util/Collections$EmptyIterator;
 HSPLjava/util/Locale;->toString()Ljava/lang/String;
 HSPLjava/util/Locale;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/Map;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;+]Ljava/util/function/Function;missing_types]Ljava/util/Map;Landroid/util/ArrayMap;
 HSPLjava/util/Map;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;,Ljava/util/ImmutableCollections$MapN;
+HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;
+HSPLjava/util/Map;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;
 HSPLjava/util/MissingResourceException;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/util/NoSuchElementException;-><init>()V
 HSPLjava/util/NoSuchElementException;-><init>(Ljava/lang/String;)V
@@ -6032,12 +6126,12 @@
 HSPLjava/util/PriorityQueue;-><init>(ILjava/util/Comparator;)V
 HSPLjava/util/PriorityQueue;-><init>(Ljava/util/Comparator;)V
 HSPLjava/util/PriorityQueue;-><init>(Ljava/util/PriorityQueue;)V
-HSPLjava/util/PriorityQueue;->add(Ljava/lang/Object;)Z
+HSPLjava/util/PriorityQueue;->add(Ljava/lang/Object;)Z+]Ljava/util/PriorityQueue;Ljava/util/PriorityQueue;
 HSPLjava/util/PriorityQueue;->clear()V
 HSPLjava/util/PriorityQueue;->comparator()Ljava/util/Comparator;
 HSPLjava/util/PriorityQueue;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/PriorityQueue;->grow(I)V
-HSPLjava/util/PriorityQueue;->indexOf(Ljava/lang/Object;)I
+HSPLjava/util/PriorityQueue;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;
 HSPLjava/util/PriorityQueue;->initFromPriorityQueue(Ljava/util/PriorityQueue;)V
 HSPLjava/util/PriorityQueue;->iterator()Ljava/util/Iterator;
 HSPLjava/util/PriorityQueue;->offer(Ljava/lang/Object;)Z
@@ -6046,11 +6140,9 @@
 HSPLjava/util/PriorityQueue;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/PriorityQueue;->removeAt(I)Ljava/lang/Object;
 HSPLjava/util/PriorityQueue;->siftDown(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;)V
+HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;[Ljava/lang/Object;I)V+]Ljava/lang/Comparable;Ljava/lang/String;
+HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;[Ljava/lang/Object;ILjava/util/Comparator;)V+]Ljava/util/Comparator;Ljava/util/Collections$ReverseComparator;
 HSPLjava/util/PriorityQueue;->siftUp(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpComparable(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpUsingComparator(ILjava/lang/Object;)V
 HSPLjava/util/PriorityQueue;->size()I
 HSPLjava/util/PriorityQueue;->toArray()[Ljava/lang/Object;
 HSPLjava/util/PriorityQueue;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
@@ -6059,20 +6151,20 @@
 HSPLjava/util/Properties$LineReader;->readLine()I
 HSPLjava/util/Properties;-><init>()V
 HSPLjava/util/Properties;-><init>(Ljava/util/Properties;)V
-HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/util/Properties;
+HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Properties;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/Properties;->load(Ljava/io/InputStream;)V
 HSPLjava/util/Properties;->load(Ljava/io/Reader;)V
 HSPLjava/util/Properties;->load0(Ljava/util/Properties$LineReader;)V
 HSPLjava/util/Properties;->loadConvert([CII[C)Ljava/lang/String;
-HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;
 HSPLjava/util/Properties;->setProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
 HSPLjava/util/Properties;->store(Ljava/io/OutputStream;Ljava/lang/String;)V
 HSPLjava/util/Properties;->store0(Ljava/io/BufferedWriter;Ljava/lang/String;Z)V
 HSPLjava/util/Properties;->writeComments(Ljava/io/BufferedWriter;Ljava/lang/String;)V
 HSPLjava/util/PropertyResourceBundle;-><init>(Ljava/io/Reader;)V
 HSPLjava/util/Random;-><init>()V
-HSPLjava/util/Random;-><init>(J)V
+HSPLjava/util/Random;-><init>(J)V+]Ljava/lang/Object;Ljava/util/Random;
 HSPLjava/util/Random;->initialScramble(J)J
 HSPLjava/util/Random;->next(I)I
 HSPLjava/util/Random;->nextBoolean()Z
@@ -6085,7 +6177,7 @@
 HSPLjava/util/Random;->nextLong()J
 HSPLjava/util/Random;->readObject(Ljava/io/ObjectInputStream;)V
 HSPLjava/util/Random;->resetSeed(J)V
-HSPLjava/util/Random;->seedUniquifier()J
+HSPLjava/util/Random;->seedUniquifier()J+]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
 HSPLjava/util/Random;->setSeed(J)V
 HSPLjava/util/Random;->writeObject(Ljava/io/ObjectOutputStream;)V
 HSPLjava/util/RegularEnumSet$EnumSetIterator;-><init>(Ljava/util/RegularEnumSet;)V
@@ -6099,7 +6191,7 @@
 HSPLjava/util/RegularEnumSet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/RegularEnumSet;->clear()V
 HSPLjava/util/RegularEnumSet;->complement()V
-HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/lang/Enum;missing_types
 HSPLjava/util/RegularEnumSet;->containsAll(Ljava/util/Collection;)Z
 HSPLjava/util/RegularEnumSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/RegularEnumSet;->isEmpty()Z
@@ -6109,8 +6201,6 @@
 HSPLjava/util/ResourceBundle$BundleReference;-><init>(Ljava/util/ResourceBundle;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V
 HSPLjava/util/ResourceBundle$BundleReference;->getCacheKey()Ljava/util/ResourceBundle$CacheKey;
 HSPLjava/util/ResourceBundle$CacheKey;-><init>(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;)V
-HSPLjava/util/ResourceBundle$CacheKey;->calculateHashCode()V
-HSPLjava/util/ResourceBundle$CacheKey;->clone()Ljava/lang/Object;
 HSPLjava/util/ResourceBundle$CacheKey;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/ResourceBundle$CacheKey;->getCause()Ljava/lang/Throwable;
 HSPLjava/util/ResourceBundle$CacheKey;->getLoader()Ljava/lang/ClassLoader;
@@ -6119,7 +6209,6 @@
 HSPLjava/util/ResourceBundle$CacheKey;->hashCode()I
 HSPLjava/util/ResourceBundle$CacheKey;->setFormat(Ljava/lang/String;)V
 HSPLjava/util/ResourceBundle$CacheKey;->setLocale(Ljava/util/Locale;)Ljava/util/ResourceBundle$CacheKey;
-HSPLjava/util/ResourceBundle$Control$1;-><init>(Ljava/util/ResourceBundle$Control;ZLjava/lang/ClassLoader;Ljava/lang/String;)V
 HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/io/InputStream;
 HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/lang/Object;
 HSPLjava/util/ResourceBundle$Control$CandidateListCache;->createObject(Ljava/lang/Object;)Ljava/lang/Object;
@@ -6133,7 +6222,6 @@
 HSPLjava/util/ResourceBundle$Control;->toBundleName(Ljava/lang/String;Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/ResourceBundle$Control;->toResourceName(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/ResourceBundle$Control;->toResourceName0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/ResourceBundle$LoaderReference;-><init>(Ljava/lang/ClassLoader;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V
 HSPLjava/util/ResourceBundle;-><init>()V
 HSPLjava/util/ResourceBundle;->findBundle(Ljava/util/ResourceBundle$CacheKey;Ljava/util/List;Ljava/util/List;ILjava/util/ResourceBundle$Control;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle;
 HSPLjava/util/ResourceBundle;->findBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;
@@ -6147,9 +6235,6 @@
 HSPLjava/util/ResourceBundle;->putBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;
 HSPLjava/util/ResourceBundle;->setExpirationTime(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)V
 HSPLjava/util/ResourceBundle;->setParent(Ljava/util/ResourceBundle;)V
-HSPLjava/util/Scanner$1;-><init>(Ljava/util/Scanner;I)V
-HSPLjava/util/Scanner$1;->create(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Scanner$1;->create(Ljava/lang/String;)Ljava/util/regex/Pattern;
 HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;)V
 HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V
 HSPLjava/util/Scanner;-><init>(Ljava/lang/Readable;Ljava/util/regex/Pattern;)V
@@ -6195,8 +6280,8 @@
 HSPLjava/util/SimpleTimeZone;->getOffsets(J[I)I
 HSPLjava/util/SimpleTimeZone;->getRawOffset()I
 HSPLjava/util/SimpleTimeZone;->hasSameRules(Ljava/util/TimeZone;)Z
-HSPLjava/util/Spliterator$OfInt;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/Spliterator$OfInt;Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/stream/Streams$RangeIntSpliterator;
-HSPLjava/util/Spliterator;->getExactSizeIfKnown()J+]Ljava/util/Spliterator;megamorphic_types
+HSPLjava/util/Spliterator$OfInt;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/Spliterator$OfInt;Ljava/util/Spliterators$IntArraySpliterator;
+HSPLjava/util/Spliterator;->getExactSizeIfKnown()J+]Ljava/util/Spliterator;Ljava/util/ArrayList$ArrayListSpliterator;,Ljava/util/HashMap$KeySpliterator;,Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/Spliterators$ArraySpliterator;,Ljava/util/Spliterators$IteratorSpliterator;
 HSPLjava/util/Spliterators$ArraySpliterator;-><init>([Ljava/lang/Object;I)V
 HSPLjava/util/Spliterators$ArraySpliterator;-><init>([Ljava/lang/Object;III)V
 HSPLjava/util/Spliterators$ArraySpliterator;->characteristics()I
@@ -6205,6 +6290,7 @@
 HSPLjava/util/Spliterators$ArraySpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/Spliterators$EmptySpliterator$OfInt;->forEachRemaining(Ljava/util/function/IntConsumer;)V
 HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->forEachRemaining(Ljava/util/function/Consumer;)V
+HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->tryAdvance(Ljava/util/function/Consumer;)Z
 HSPLjava/util/Spliterators$EmptySpliterator;->characteristics()I
 HSPLjava/util/Spliterators$EmptySpliterator;->estimateSize()J
 HSPLjava/util/Spliterators$EmptySpliterator;->forEachRemaining(Ljava/lang/Object;)V
@@ -6234,7 +6320,8 @@
 HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;)V
 HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V
 HSPLjava/util/StringJoiner;->add(Ljava/lang/CharSequence;)Ljava/util/StringJoiner;
-HSPLjava/util/StringJoiner;->prepareBuilder()Ljava/lang/StringBuilder;
+HSPLjava/util/StringJoiner;->compactElts()V
+HSPLjava/util/StringJoiner;->getChars(Ljava/lang/String;[CI)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/util/StringJoiner;->toString()Ljava/lang/String;
 HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;)V
 HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;Ljava/lang/String;)V
@@ -6257,7 +6344,7 @@
 HSPLjava/util/TaskQueue;->rescheduleMin(J)V
 HSPLjava/util/TimSort;-><init>([Ljava/lang/Object;Ljava/util/Comparator;[Ljava/lang/Object;II)V
 HSPLjava/util/TimSort;->binarySort([Ljava/lang/Object;IIILjava/util/Comparator;)V
-HSPLjava/util/TimSort;->countRunAndMakeAscending([Ljava/lang/Object;IILjava/util/Comparator;)I+]Ljava/util/Comparator;missing_types
+HSPLjava/util/TimSort;->countRunAndMakeAscending([Ljava/lang/Object;IILjava/util/Comparator;)I
 HSPLjava/util/TimSort;->ensureCapacity(I)[Ljava/lang/Object;
 HSPLjava/util/TimSort;->gallopLeft(Ljava/lang/Object;[Ljava/lang/Object;IIILjava/util/Comparator;)I
 HSPLjava/util/TimSort;->gallopRight(Ljava/lang/Object;[Ljava/lang/Object;IIILjava/util/Comparator;)I
@@ -6275,12 +6362,12 @@
 HSPLjava/util/TimeZone;->clone()Ljava/lang/Object;
 HSPLjava/util/TimeZone;->createGmtOffsetString(ZZI)Ljava/lang/String;
 HSPLjava/util/TimeZone;->getAvailableIDs()[Ljava/lang/String;
-HSPLjava/util/TimeZone;->getDefault()Ljava/util/TimeZone;+]Ljava/util/TimeZone;Llibcore/util/ZoneInfo;
-HSPLjava/util/TimeZone;->getDefaultRef()Ljava/util/TimeZone;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/function/Supplier;Lcom/android/internal/os/RuntimeInit$$ExternalSyntheticLambda1;
+HSPLjava/util/TimeZone;->getDefault()Ljava/util/TimeZone;
+HSPLjava/util/TimeZone;->getDefaultRef()Ljava/util/TimeZone;
 HSPLjava/util/TimeZone;->getDisplayName(ZI)Ljava/lang/String;
 HSPLjava/util/TimeZone;->getDisplayName(ZILjava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/TimeZone;->getID()Ljava/lang/String;
-HSPLjava/util/TimeZone;->getTimeZone(Ljava/lang/String;)Ljava/util/TimeZone;+]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone;]Lcom/android/i18n/timezone/ZoneInfoDb;Lcom/android/i18n/timezone/ZoneInfoDb;
+HSPLjava/util/TimeZone;->getTimeZone(Ljava/lang/String;)Ljava/util/TimeZone;
 HSPLjava/util/TimeZone;->setDefault(Ljava/util/TimeZone;)V
 HSPLjava/util/TimeZone;->setID(Ljava/lang/String;)V
 HSPLjava/util/TimeZone;->toZoneId()Ljava/time/ZoneId;
@@ -6292,6 +6379,7 @@
 HSPLjava/util/Timer;->cancel()V
 HSPLjava/util/Timer;->sched(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;J)V
+HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->scheduleAtFixedRate(Ljava/util/TimerTask;JJ)V
 HSPLjava/util/Timer;->serialNumber()I
 HSPLjava/util/TimerTask;-><init>()V
@@ -6317,10 +6405,10 @@
 HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$KeyIterator;Ljava/util/TreeMap$KeyIterator;
 HSPLjava/util/TreeMap$KeySet;-><init>(Ljava/util/NavigableMap;)V
 HSPLjava/util/TreeMap$KeySet;->isEmpty()Z
-HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/TreeMap$KeySet;->size()I
 HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;-><init>(Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;
+HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;-><init>(Ljava/util/TreeMap$NavigableSubMap;)V
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->isEmpty()Z
 HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->size()I
@@ -6374,9 +6462,9 @@
 HSPLjava/util/TreeMap;->clone()Ljava/lang/Object;
 HSPLjava/util/TreeMap;->colorOf(Ljava/util/TreeMap$TreeMapEntry;)Z
 HSPLjava/util/TreeMap;->comparator()Ljava/util/Comparator;
-HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer;
+HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/util/TreeMap;->computeRedLevel(I)I
-HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z
+HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap;->deleteEntry(Ljava/util/TreeMap$TreeMapEntry;)V
 HSPLjava/util/TreeMap;->descendingKeySet()Ljava/util/NavigableSet;
 HSPLjava/util/TreeMap;->descendingMap()Ljava/util/NavigableMap;
@@ -6387,9 +6475,9 @@
 HSPLjava/util/TreeMap;->fixAfterInsertion(Ljava/util/TreeMap$TreeMapEntry;)V
 HSPLjava/util/TreeMap;->floorEntry(Ljava/lang/Object;)Ljava/util/Map$Entry;
 HSPLjava/util/TreeMap;->floorKey(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/TreeMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/TreeMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap;->getCeilingEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
-HSPLjava/util/TreeMap;->getEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
+HSPLjava/util/TreeMap;->getEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;+]Ljava/lang/Comparable;missing_types
 HSPLjava/util/TreeMap;->getEntryUsingComparator(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->getFirstEntry()Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->getFloorEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
@@ -6398,7 +6486,7 @@
 HSPLjava/util/TreeMap;->getLowerEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->headMap(Ljava/lang/Object;Z)Ljava/util/NavigableMap;
 HSPLjava/util/TreeMap;->key(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object;
-HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator;
 HSPLjava/util/TreeMap;->keyOrNull(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object;
 HSPLjava/util/TreeMap;->keySet()Ljava/util/Set;
 HSPLjava/util/TreeMap;->lastKey()Ljava/lang/Object;
@@ -6408,9 +6496,9 @@
 HSPLjava/util/TreeMap;->parentOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->pollFirstEntry()Ljava/util/Map$Entry;
 HSPLjava/util/TreeMap;->predecessor(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
-HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer;
+HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;Ljava/lang/String$CaseInsensitiveComparator;]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;missing_types
 HSPLjava/util/TreeMap;->putAll(Ljava/util/Map;)V
-HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/TreeMap;->rightOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->rotateLeft(Ljava/util/TreeMap$TreeMapEntry;)V
 HSPLjava/util/TreeMap;->rotateRight(Ljava/util/TreeMap$TreeMapEntry;)V
@@ -6426,7 +6514,7 @@
 HSPLjava/util/TreeSet;-><init>(Ljava/util/Comparator;)V
 HSPLjava/util/TreeSet;-><init>(Ljava/util/NavigableMap;)V
 HSPLjava/util/TreeSet;-><init>(Ljava/util/SortedSet;)V
-HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z
 HSPLjava/util/TreeSet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/TreeSet;->ceiling(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/TreeSet;->clear()V
@@ -6434,9 +6522,9 @@
 HSPLjava/util/TreeSet;->contains(Ljava/lang/Object;)Z
 HSPLjava/util/TreeSet;->descendingSet()Ljava/util/NavigableSet;
 HSPLjava/util/TreeSet;->first()Ljava/lang/Object;
-HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/TreeSet;->isEmpty()Z
-HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;]Ljava/util/NavigableSet;Ljava/util/TreeMap$KeySet;
+HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/TreeSet;->last()Ljava/lang/Object;
 HSPLjava/util/TreeSet;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/TreeSet;->size()I
@@ -6445,7 +6533,7 @@
 HSPLjava/util/UUID;-><init>(JJ)V
 HSPLjava/util/UUID;-><init>([B)V
 HSPLjava/util/UUID;->digits(JI)Ljava/lang/String;
-HSPLjava/util/UUID;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/UUID;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;Ljava/util/UUID;
 HSPLjava/util/UUID;->fromString(Ljava/lang/String;)Ljava/util/UUID;
 HSPLjava/util/UUID;->getLeastSignificantBits()J
 HSPLjava/util/UUID;->getMostSignificantBits()J
@@ -6464,6 +6552,7 @@
 HSPLjava/util/Vector;-><init>(I)V
 HSPLjava/util/Vector;-><init>(II)V
 HSPLjava/util/Vector;->add(Ljava/lang/Object;)Z
+HSPLjava/util/Vector;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V
 HSPLjava/util/Vector;->addElement(Ljava/lang/Object;)V
 HSPLjava/util/Vector;->clear()V
 HSPLjava/util/Vector;->contains(Ljava/lang/Object;)Z
@@ -6471,13 +6560,12 @@
 HSPLjava/util/Vector;->elementAt(I)Ljava/lang/Object;
 HSPLjava/util/Vector;->elementData(I)Ljava/lang/Object;
 HSPLjava/util/Vector;->elements()Ljava/util/Enumeration;
-HSPLjava/util/Vector;->ensureCapacityHelper(I)V
 HSPLjava/util/Vector;->get(I)Ljava/lang/Object;
-HSPLjava/util/Vector;->grow(I)V
 HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;I)I
 HSPLjava/util/Vector;->isEmpty()Z
 HSPLjava/util/Vector;->iterator()Ljava/util/Iterator;
+HSPLjava/util/Vector;->newCapacity(I)I
 HSPLjava/util/Vector;->removeAllElements()V
 HSPLjava/util/Vector;->removeElement(Ljava/lang/Object;)Z
 HSPLjava/util/Vector;->removeElementAt(I)V
@@ -6514,18 +6602,18 @@
 HSPLjava/util/WeakHashMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/WeakHashMap;->entrySet()Ljava/util/Set;
 HSPLjava/util/WeakHashMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/WeakHashMap;->expungeStaleEntries()V
+HSPLjava/util/WeakHashMap;->expungeStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue;
 HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/WeakHashMap;->getEntry(Ljava/lang/Object;)Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap;->getTable()[Ljava/util/WeakHashMap$Entry;
-HSPLjava/util/WeakHashMap;->hash(Ljava/lang/Object;)I
+HSPLjava/util/WeakHashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/WeakHashMap;->indexFor(II)I
 HSPLjava/util/WeakHashMap;->isEmpty()Z
 HSPLjava/util/WeakHashMap;->keySet()Ljava/util/Set;
 HSPLjava/util/WeakHashMap;->maskNull(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/WeakHashMap;->newTable(I)[Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;
-HSPLjava/util/WeakHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
+HSPLjava/util/WeakHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/WeakHashMap;->resize(I)V
 HSPLjava/util/WeakHashMap;->size()I
 HSPLjava/util/WeakHashMap;->transfer([Ljava/util/WeakHashMap$Entry;[Ljava/util/WeakHashMap$Entry;)V
@@ -6558,7 +6646,7 @@
 HSPLjava/util/concurrent/CompletableFuture$AsyncRun;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/CompletableFuture$AsyncRun;->run()V
 HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/util/function/Supplier;)V
-HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V+]Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture;
+HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V
 HSPLjava/util/concurrent/CompletableFuture$Completion;-><init>()V
 HSPLjava/util/concurrent/CompletableFuture$Signaller;-><init>(ZJJ)V
 HSPLjava/util/concurrent/CompletableFuture$Signaller;->block()Z
@@ -6566,7 +6654,7 @@
 HSPLjava/util/concurrent/CompletableFuture$Signaller;->tryFire(I)Ljava/util/concurrent/CompletableFuture;
 HSPLjava/util/concurrent/CompletableFuture;-><init>()V
 HSPLjava/util/concurrent/CompletableFuture;->asyncRunStage(Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)Ljava/util/concurrent/CompletableFuture;
-HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;+]Ljava/util/concurrent/Executor;Ljava/util/concurrent/ForkJoinPool;
+HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;
 HSPLjava/util/concurrent/CompletableFuture;->complete(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CompletableFuture;->completeNull()Z
 HSPLjava/util/concurrent/CompletableFuture;->completeValue(Ljava/lang/Object;)Z
@@ -6627,8 +6715,9 @@
 HSPLjava/util/concurrent/ConcurrentHashMap;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentHashMap;->entrySet()Ljava/util/Set;
+HSPLjava/util/concurrent/ConcurrentHashMap;->forEach(Ljava/util/function/BiConsumer;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->fullAddCount(JZ)V
-HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;missing_types
+HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/concurrent/ConcurrentHashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->helpTransfer([Ljava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$Node;)[Ljava/util/concurrent/ConcurrentHashMap$Node;
 HSPLjava/util/concurrent/ConcurrentHashMap;->initTable()[Ljava/util/concurrent/ConcurrentHashMap$Node;
@@ -6640,8 +6729,9 @@
 HSPLjava/util/concurrent/ConcurrentHashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentHashMap;->replace(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/nio/ch/FileKey;
 HSPLjava/util/concurrent/ConcurrentHashMap;->resizeStamp(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->setTabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;ILjava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->size()I
@@ -6686,13 +6776,13 @@
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->bulkRemove(Ljava/util/function/Predicate;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->clear()V
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->isEmpty()Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->iterator()Ljava/util/Iterator;
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->lambda$clear$2(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->offer(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->peek()Ljava/lang/Object;
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->poll()Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue;]Ljava/util/concurrent/ConcurrentLinkedQueue$Node;Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->poll()Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->size()I
 HSPLjava/util/concurrent/ConcurrentLinkedQueue;->succ(Ljava/util/concurrent/ConcurrentLinkedQueue$Node;)Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
@@ -6740,8 +6830,8 @@
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->get(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->getArray()[Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/media/ImageReader$SurfaceImage;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->lambda$removeAll$0(Ljava/util/Collection;Ljava/lang/Object;)Z
@@ -6755,7 +6845,7 @@
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->toString()Ljava/lang/String;
 HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>()V
-HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArraySet;
+HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->add(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/concurrent/CopyOnWriteArraySet;->clear()V
@@ -6781,7 +6871,7 @@
 HSPLjava/util/concurrent/Executors$DefaultThreadFactory;->newThread(Ljava/lang/Runnable;)Ljava/lang/Thread;
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->awaitTermination(JLjava/util/concurrent/TimeUnit;)Z
-HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->execute(Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->isShutdown()Z
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->shutdown()V
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->shutdownNow()Ljava/util/List;
@@ -6789,13 +6879,13 @@
 HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;-><init>(Ljava/util/concurrent/ScheduledExecutorService;)V
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
-HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledExecutorService;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V
 HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;->finalize()V
 HSPLjava/util/concurrent/Executors$RunnableAdapter;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V
-HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object;+]Ljava/lang/Runnable;missing_types
+HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object;
 HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;)Ljava/util/concurrent/Callable;
 HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;Ljava/lang/Object;)Ljava/util/concurrent/Callable;
 HSPLjava/util/concurrent/Executors;->defaultThreadFactory()Ljava/util/concurrent/ThreadFactory;
@@ -6819,7 +6909,7 @@
 HSPLjava/util/concurrent/FutureTask;->awaitDone(ZJ)I
 HSPLjava/util/concurrent/FutureTask;->cancel(Z)Z
 HSPLjava/util/concurrent/FutureTask;->done()V
-HSPLjava/util/concurrent/FutureTask;->finishCompletion()V+]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/FutureTask;->finishCompletion()V
 HSPLjava/util/concurrent/FutureTask;->get()Ljava/lang/Object;
 HSPLjava/util/concurrent/FutureTask;->get(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
 HSPLjava/util/concurrent/FutureTask;->handlePossibleCancellationInterrupt(I)V
@@ -6827,7 +6917,7 @@
 HSPLjava/util/concurrent/FutureTask;->isDone()Z
 HSPLjava/util/concurrent/FutureTask;->removeWaiter(Ljava/util/concurrent/FutureTask$WaitNode;)V
 HSPLjava/util/concurrent/FutureTask;->report(I)Ljava/lang/Object;
-HSPLjava/util/concurrent/FutureTask;->run()V+]Ljava/util/concurrent/Callable;Ljava/util/concurrent/Executors$RunnableAdapter;]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;,Landroid/os/AsyncTask$4;
+HSPLjava/util/concurrent/FutureTask;->run()V
 HSPLjava/util/concurrent/FutureTask;->runAndReset()Z
 HSPLjava/util/concurrent/FutureTask;->set(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/FutureTask;->setException(Ljava/lang/Throwable;)V
@@ -6862,14 +6952,14 @@
 HSPLjava/util/concurrent/LinkedBlockingQueue;->enqueue(Ljava/util/concurrent/LinkedBlockingQueue$Node;)V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyLock()V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyUnlock()V
-HSPLjava/util/concurrent/LinkedBlockingQueue;->offer(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/LinkedBlockingQueue;->offer(Ljava/lang/Object;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->put(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotEmpty()V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotFull()V
 HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I
-HSPLjava/util/concurrent/LinkedBlockingQueue;->take()Ljava/lang/Object;
+HSPLjava/util/concurrent/LinkedBlockingQueue;->take()Ljava/lang/Object;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>()V
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>(ILjava/util/Comparator;)V
 HSPLjava/util/concurrent/PriorityBlockingQueue;->add(Ljava/lang/Object;)Z
@@ -6894,8 +6984,8 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->next()Ljava/lang/Runnable;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->remove()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;-><init>()V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;I)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->finishPoll(Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
@@ -6903,40 +6993,40 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->isEmpty()Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/RunnableScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->setIndex(Ljava/util/concurrent/RunnableScheduledFuture;I)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->size()I
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->toArray()[Ljava/lang/Object;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJ)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJJ)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/Callable;JJ)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/lang/Object;)I+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/util/concurrent/Delayed;)I
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->isPeriodic()Z
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->setNextRunTime()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(I)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/lang/Runnable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/util/concurrent/Callable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getContinueExistingPeriodicTasksAfterShutdownPolicy()Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getExecuteExistingDelayedTasksAfterShutdownPolicy()Z
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->onShutdown()V
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->reExecutePeriodic(Ljava/util/concurrent/RunnableScheduledFuture;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
@@ -6946,7 +7036,7 @@
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(J)J
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J
 HSPLjava/util/concurrent/Semaphore$FairSync;-><init>(I)V
 HSPLjava/util/concurrent/Semaphore$FairSync;->tryAcquireShared(I)I
 HSPLjava/util/concurrent/Semaphore$NonfairSync;-><init>(I)V
@@ -6968,14 +7058,11 @@
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;-><init>(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->casNext(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isCancelled()Z
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryCancel()V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryMatch(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;-><init>()V
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->awaitFulfill(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;ZJ)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/lang/Thread;Ljava/lang/Thread;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack;
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->casHead(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->clean(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->isFulfilling(I)Z
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->shouldSpin(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->snode(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/lang/Object;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;I)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack;
 HSPLjava/util/concurrent/SynchronousQueue$Transferer;-><init>()V
@@ -7024,8 +7111,8 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->ctlOf(II)I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->decrementWorkerCount()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->drainQueue()Ljava/util/List;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/LinkedBlockingDeque;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->finalize()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getCorePoolSize()I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getMaximumPoolSize()I
@@ -7037,7 +7124,7 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptIdleWorkers(Z)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptWorkers()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->isRunning(I)Z
-HSPLjava/util/concurrent/ThreadPoolExecutor;->isShutdown()Z+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->isShutdown()Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->isTerminated()Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->onShutdown()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartAllCoreThreads()I
@@ -7060,7 +7147,7 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->toString()Ljava/lang/String;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->workerCountOf(I)I
-HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J
 HSPLjava/util/concurrent/TimeUnit;->cvt(JJJ)J
 HSPLjava/util/concurrent/TimeUnit;->sleep(J)V
 HSPLjava/util/concurrent/TimeUnit;->toDays(J)J
@@ -7092,11 +7179,12 @@
 HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndIncrement()I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndSet(I)I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->incrementAndGet()I
-HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I
 HSPLjava/util/concurrent/atomic/AtomicInteger;->lazySet(I)V
 HSPLjava/util/concurrent/atomic/AtomicInteger;->set(I)V
+HSPLjava/util/concurrent/atomic/AtomicInteger;->weakCompareAndSetVolatile(II)Z
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
-HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;II)Z
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->decrementAndGet(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I
@@ -7121,7 +7209,7 @@
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->addAndGet(Ljava/lang/Object;J)J
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->compareAndSet(Ljava/lang/Object;JJ)Z
-HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->incrementAndGet(Ljava/lang/Object;)J
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;-><init>()V
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;
@@ -7138,10 +7226,12 @@
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;-><init>(I)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->compareAndSet(ILjava/lang/Object;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->get(I)Ljava/lang/Object;
+HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAcquire(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAndSet(ILjava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->lazySet(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->length()I
 HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->set(ILjava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->setRelease(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
@@ -7152,7 +7242,7 @@
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;-><init>()V
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;
 HSPLjava/util/concurrent/atomic/LongAdder;-><init>()V
-HSPLjava/util/concurrent/atomic/LongAdder;->add(J)V+]Ljava/util/concurrent/atomic/Striped64$Cell;Ljava/util/concurrent/atomic/Striped64$Cell;]Ljava/util/concurrent/atomic/LongAdder;Ljava/util/concurrent/atomic/LongAdder;
+HSPLjava/util/concurrent/atomic/LongAdder;->add(J)V
 HSPLjava/util/concurrent/atomic/Striped64$Cell;-><clinit>()V
 HSPLjava/util/concurrent/atomic/Striped64$Cell;-><init>(J)V
 HSPLjava/util/concurrent/atomic/Striped64$Cell;->cas(JJ)Z
@@ -7160,69 +7250,46 @@
 HSPLjava/util/concurrent/atomic/Striped64;->casBase(JJ)Z
 HSPLjava/util/concurrent/atomic/Striped64;->casCellsBusy()Z
 HSPLjava/util/concurrent/atomic/Striped64;->getProbe()I
-HSPLjava/util/concurrent/atomic/Striped64;->longAccumulate(JLjava/util/function/LongBinaryOperator;Z)V
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;-><init>()V
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->getExclusiveOwnerThread()Ljava/lang/Thread;
 HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->setExclusiveOwnerThread(Ljava/lang/Thread;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;-><init>()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->block()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->isReleasable()Z+]Ljava/lang/Thread;missing_types
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->addConditionWaiter()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->await()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->await()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->awaitNanos(J)J
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->checkInterruptWhileWaiting(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignalAll(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->enableWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)I+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->isOwnedBy(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->reportInterruptAfterWait(I)V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signal()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signalAll()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetWaitStatus(II)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->isShared()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->predecessor()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setStatusRelaxed(I)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->-$$Nest$sfgetU()Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;-><init>()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/CountDownLatch$Sync;,Ljava/util/concurrent/Semaphore$FairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/Semaphore$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireQueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireSharedInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->addWaiter(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cancelAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->casTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetState(II)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireShared(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedInterruptibly(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedNanos(IJ)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doReleaseShared()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enq(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->findNodeFromTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->fullyRelease(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enqueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getState()I
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedPredecessors()Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->initializeSyncQueue()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isOnSyncQueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->owns(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->parkAndCheckInterrupt()Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/CountDownLatch$Sync;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->selfInterrupt()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHead(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHeadAndPropagate(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setState(I)V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->shouldParkAfterFailedAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferAfterCancelledWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferForSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireNanos(IJ)Z
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireSharedNanos(IJ)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->unparkSuccessor(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryInitializeHead()V
+HSPLjava/util/concurrent/locks/LockSupport;->park()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/LockSupport;->park(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(J)V
 HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(Ljava/lang/Object;J)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
@@ -7231,15 +7298,15 @@
 HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->tryAcquire(I)Z
 HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;-><init>()V
+HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->newCondition()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
-HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->nonfairTryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryRelease(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantLock;-><init>(Z)V
-HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z
 HSPLjava/util/concurrent/locks/ReentrantLock;->isHeldByCurrentThread()Z
 HSPLjava/util/concurrent/locks/ReentrantLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
 HSPLjava/util/concurrent/locks/ReentrantLock;->lockInterruptibly()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
@@ -7251,11 +7318,11 @@
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->readerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->writerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;-><init>()V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->writerShouldBlock()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$HoldCounter;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;->initialValue()Ljava/lang/Object;
@@ -7263,23 +7330,18 @@
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->exclusiveCount(I)I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->fullTryAcquireShared(Ljava/lang/Thread;)I
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getWriteHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->sharedCount(I)I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryRelease(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->lock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->unlock()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>()V
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>(Z)V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->getReadHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->getWriteHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;+]Ljava/util/concurrent/locks/ReentrantReadWriteLock;Ljava/util/concurrent/locks/ReentrantReadWriteLock;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/Lock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;
@@ -7288,7 +7350,7 @@
 HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;-><init>(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;)V
 HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;->applyAsDouble(D)D
 HSPLjava/util/function/DoubleUnaryOperator;->andThen(Ljava/util/function/DoubleUnaryOperator;)Ljava/util/function/DoubleUnaryOperator;
-HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D+]Ljava/util/function/DoubleUnaryOperator;Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda3;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda1;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda0;
+HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D
 HSPLjava/util/function/Function$$ExternalSyntheticLambda1;-><init>()V
 HSPLjava/util/function/Function$$ExternalSyntheticLambda1;->apply(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/function/Function$$ExternalSyntheticLambda2;->apply(Ljava/lang/Object;)Ljava/lang/Object;
@@ -7381,7 +7443,7 @@
 HSPLjava/util/logging/Handler;->getFilter()Ljava/util/logging/Filter;
 HSPLjava/util/logging/Handler;->getFormatter()Ljava/util/logging/Formatter;
 HSPLjava/util/logging/Handler;->getLevel()Ljava/util/logging/Level;
-HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z
+HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/Level;Ljava/util/logging/Level;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;
 HSPLjava/util/logging/Handler;->setEncoding(Ljava/lang/String;)V
 HSPLjava/util/logging/Handler;->setErrorManager(Ljava/util/logging/ErrorManager;)V
 HSPLjava/util/logging/Handler;->setFilter(Ljava/util/logging/Filter;)V
@@ -7474,7 +7536,7 @@
 HSPLjava/util/logging/Logger;->findResourceBundle(Ljava/lang/String;Z)Ljava/util/ResourceBundle;
 HSPLjava/util/logging/Logger;->findSystemResourceBundle(Ljava/util/Locale;)Ljava/util/ResourceBundle;
 HSPLjava/util/logging/Logger;->getCallersClassLoader()Ljava/lang/ClassLoader;
-HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle;
+HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle;+]Ljava/util/logging/Logger$LoggerBundle;Ljava/util/logging/Logger$LoggerBundle;]Ljava/util/logging/Logger;Ljava/util/logging/LogManager$RootLogger;,Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->getHandlers()[Ljava/util/logging/Handler;
 HSPLjava/util/logging/Logger;->getLogger(Ljava/lang/String;)Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->getName()Ljava/lang/String;
@@ -7486,7 +7548,7 @@
 HSPLjava/util/logging/Logger;->info(Ljava/lang/String;)V
 HSPLjava/util/logging/Logger;->isLoggable(Ljava/util/logging/Level;)Z+]Ljava/util/logging/Level;Ljava/util/logging/Level;
 HSPLjava/util/logging/Logger;->log(Ljava/util/logging/Level;Ljava/lang/String;)V
-HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V
 HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V
@@ -7514,29 +7576,32 @@
 HSPLjava/util/logging/StreamHandler;->setOutputStream(Ljava/io/OutputStream;)V
 HSPLjava/util/logging/XMLFormatter;-><init>()V
 HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuffer;Ljava/lang/String;)V
+HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuilder;Ljava/lang/String;)V
 HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuffer;Ljava/lang/String;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendReplacementInternal(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;
-HSPLjava/util/regex/Matcher;->end()I+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->end(I)I+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;
+HSPLjava/util/regex/Matcher;->end()I
+HSPLjava/util/regex/Matcher;->end(I)I
 HSPLjava/util/regex/Matcher;->ensureMatch()V
-HSPLjava/util/regex/Matcher;->find()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
-HSPLjava/util/regex/Matcher;->find(I)Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->find()Z
+HSPLjava/util/regex/Matcher;->find(I)Z
+HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;
 HSPLjava/util/regex/Matcher;->getTextLength()I
 HSPLjava/util/regex/Matcher;->group()Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;+]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;
 HSPLjava/util/regex/Matcher;->groupCount()I+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->hitEnd()Z
 HSPLjava/util/regex/Matcher;->lookingAt()Z
-HSPLjava/util/regex/Matcher;->matches()Z
+HSPLjava/util/regex/Matcher;->matches()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->pattern()Ljava/util/regex/Pattern;
 HSPLjava/util/regex/Matcher;->region(II)Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/regex/Matcher;->replaceFirst(Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;
 HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/StringBuilder;,Ljava/lang/String;
 HSPLjava/util/regex/Matcher;->resetForInput()V+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
 HSPLjava/util/regex/Matcher;->start()I
 HSPLjava/util/regex/Matcher;->start(I)I
@@ -7559,7 +7624,6 @@
 HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V
 HSPLjava/util/stream/AbstractPipeline;->close()V
 HSPLjava/util/stream/AbstractPipeline;->copyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V
-HSPLjava/util/stream/AbstractPipeline;->copyIntoWithCancel(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V
 HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/Spliterator;ZLjava/util/function/IntFunction;)Ljava/util/stream/Node;
 HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/stream/TerminalOp;)Ljava/lang/Object;
 HSPLjava/util/stream/AbstractPipeline;->evaluateToArrayNode(Ljava/util/function/IntFunction;)Ljava/util/stream/Node;
@@ -7576,6 +7640,7 @@
 HSPLjava/util/stream/AbstractSpinedBuffer;-><init>()V
 HSPLjava/util/stream/AbstractSpinedBuffer;->count()J
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;-><init>()V
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda15;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda1;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda20;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
@@ -7585,7 +7650,7 @@
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/Set;Ljava/util/HashSet;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda51;-><init>()V
@@ -7598,7 +7663,7 @@
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;->get()Ljava/lang/Object;
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;-><init>()V
-HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/List;Ljava/util/ArrayList;
+HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda76;-><init>()V
 HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda87;-><init>()V
 HSPLjava/util/stream/Collectors$CollectorImpl;-><init>(Ljava/util/function/Supplier;Ljava/util/function/BiConsumer;Ljava/util/function/BinaryOperator;Ljava/util/Set;)V
@@ -7658,16 +7723,18 @@
 HSPLjava/util/stream/ForEachOps$ForEachOp;->get()Ljava/lang/Void;
 HSPLjava/util/stream/ForEachOps$ForEachOp;->getOpFlags()I
 HSPLjava/util/stream/ForEachOps;->makeRef(Ljava/util/function/Consumer;Z)Ljava/util/stream/TerminalOp;
+HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda13;->applyAsInt(II)I
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;-><init>()V
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;->apply(I)Ljava/lang/Object;
+HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;-><init>()V
 HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;->apply(I)Ljava/lang/Object;
 HSPLjava/util/stream/IntPipeline$4$1;-><init>(Ljava/util/stream/IntPipeline$4;Ljava/util/stream/Sink;)V
 HSPLjava/util/stream/IntPipeline$4$1;->accept(I)V
 HSPLjava/util/stream/IntPipeline$4;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntFunction;)V
 HSPLjava/util/stream/IntPipeline$4;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/IntPipeline$9$1;-><init>(Ljava/util/stream/IntPipeline$9;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V+]Ljava/util/stream/Sink;megamorphic_types]Ljava/util/function/IntPredicate;Landroid/telephony/TelephonyManager$$ExternalSyntheticLambda11;
-HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V+]Ljava/util/stream/Sink;Ljava/util/stream/IntPipeline$4$1;
+HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V
+HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V
 HSPLjava/util/stream/IntPipeline$9;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntPredicate;)V
 HSPLjava/util/stream/IntPipeline$9;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/IntPipeline$Head;-><init>(Ljava/util/Spliterator;IZ)V
@@ -7683,7 +7750,6 @@
 HSPLjava/util/stream/IntPipeline;->boxed()Ljava/util/stream/Stream;
 HSPLjava/util/stream/IntPipeline;->distinct()Ljava/util/stream/IntStream;
 HSPLjava/util/stream/IntPipeline;->filter(Ljava/util/function/IntPredicate;)Ljava/util/stream/IntStream;
-HSPLjava/util/stream/IntPipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V
 HSPLjava/util/stream/IntPipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/IntPipeline;->mapToObj(Ljava/util/function/IntFunction;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/IntPipeline;->reduce(ILjava/util/function/IntBinaryOperator;)I
@@ -7708,7 +7774,7 @@
 HSPLjava/util/stream/MatchOps$1MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/Predicate;)V
 HSPLjava/util/stream/MatchOps$1MatchSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/MatchOps$2MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/IntPredicate;)V
-HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V+]Ljava/util/function/IntPredicate;missing_types
+HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;)V
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->cancellationRequested()Z
 HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->getAndClearState()Z
@@ -7741,6 +7807,11 @@
 HSPLjava/util/stream/Nodes$IntFixedNodeBuilder;->end()V
 HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><clinit>()V
 HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><init>()V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object;
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->begin(J)V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->build()Ljava/util/stream/Node;
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->copyInto([Ljava/lang/Object;I)V
+HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->end()V
 HSPLjava/util/stream/Nodes;->builder()Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/Nodes;->builder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/Nodes;->flatten(Ljava/util/stream/Node;Ljava/util/function/IntFunction;)Ljava/util/stream/Node;
@@ -7774,6 +7845,7 @@
 HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$5ReducingSink;
 HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;
 HSPLjava/util/stream/ReduceOps$5ReducingSink;-><init>(ILjava/util/function/IntBinaryOperator;)V
+HSPLjava/util/stream/ReduceOps$5ReducingSink;->accept(I)V
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->begin(J)V
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Integer;
 HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Object;
@@ -7818,7 +7890,7 @@
 HSPLjava/util/stream/ReferencePipeline$6;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/ToDoubleFunction;)V
 HSPLjava/util/stream/ReferencePipeline$6;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/ReferencePipeline$7$1;-><init>(Ljava/util/stream/ReferencePipeline$7;Ljava/util/stream/Sink;)V
-HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V+]Ljava/util/function/Function;missing_types]Ljava/util/stream/Stream;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$Head;,Ljava/util/stream/ReferencePipeline$3;
+HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/ReferencePipeline$7$1;->begin(J)V
 HSPLjava/util/stream/ReferencePipeline$7;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/Function;)V
 HSPLjava/util/stream/ReferencePipeline$7;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
@@ -7840,7 +7912,7 @@
 HSPLjava/util/stream/ReferencePipeline;->findFirst()Ljava/util/Optional;
 HSPLjava/util/stream/ReferencePipeline;->flatMap(Ljava/util/function/Function;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/ReferencePipeline;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V
+HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)Z
 HSPLjava/util/stream/ReferencePipeline;->lambda$count$2(Ljava/lang/Object;)J
 HSPLjava/util/stream/ReferencePipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder;
 HSPLjava/util/stream/ReferencePipeline;->map(Ljava/util/function/Function;)Ljava/util/stream/Stream;
@@ -7867,18 +7939,24 @@
 HSPLjava/util/stream/SortedOps$OfRef;-><init>(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)V
 HSPLjava/util/stream/SortedOps$OfRef;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink;
 HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;-><init>(Ljava/util/stream/Sink;)V
+HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V
+HSPLjava/util/stream/SortedOps$RefSortingSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;->begin(J)V
 HSPLjava/util/stream/SortedOps$RefSortingSink;->end()V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->accept(Ljava/lang/Object;)V
 HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->begin(J)V
-HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V+]Ljava/util/stream/Sink;Ljava/util/stream/ReduceOps$3ReducingSink;,Ljava/util/stream/ForEachOps$ForEachOp$OfRef;
+HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V
 HSPLjava/util/stream/SortedOps;->makeRef(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/SpinedBuffer;-><init>()V
 HSPLjava/util/stream/SpinedBuffer;->accept(Ljava/lang/Object;)V
+HSPLjava/util/stream/SpinedBuffer;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object;
+HSPLjava/util/stream/SpinedBuffer;->capacity()J
 HSPLjava/util/stream/SpinedBuffer;->clear()V
+HSPLjava/util/stream/SpinedBuffer;->copyInto([Ljava/lang/Object;I)V
 HSPLjava/util/stream/SpinedBuffer;->count()J
+HSPLjava/util/stream/SpinedBuffer;->ensureCapacity(J)V
 HSPLjava/util/stream/Stream;->builder()Ljava/util/stream/Stream$Builder;
 HSPLjava/util/stream/Stream;->concat(Ljava/util/stream/Stream;Ljava/util/stream/Stream;)Ljava/util/stream/Stream;
 HSPLjava/util/stream/Stream;->of([Ljava/lang/Object;)Ljava/util/stream/Stream;
@@ -7910,6 +7988,8 @@
 HSPLjava/util/zip/CRC32;->update(I)V
 HSPLjava/util/zip/CRC32;->update([B)V
 HSPLjava/util/zip/CRC32;->update([BII)V
+HSPLjava/util/zip/CRC32;->updateByteBuffer(IJII)I
+HSPLjava/util/zip/CRC32;->updateBytes(I[BII)I
 HSPLjava/util/zip/CheckedInputStream;-><init>(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V
 HSPLjava/util/zip/CheckedInputStream;->read()I
 HSPLjava/util/zip/CheckedInputStream;->read([BII)I
@@ -7961,14 +8041,14 @@
 HSPLjava/util/zip/Inflater;-><init>(Z)V
 HSPLjava/util/zip/Inflater;->end()V
 HSPLjava/util/zip/Inflater;->ended()Z
-HSPLjava/util/zip/Inflater;->ensureOpen()V+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Inflater;->ensureOpen()V
 HSPLjava/util/zip/Inflater;->finalize()V
 HSPLjava/util/zip/Inflater;->finished()Z
 HSPLjava/util/zip/Inflater;->getBytesRead()J
 HSPLjava/util/zip/Inflater;->getBytesWritten()J
 HSPLjava/util/zip/Inflater;->getRemaining()I
 HSPLjava/util/zip/Inflater;->getTotalOut()I
-HSPLjava/util/zip/Inflater;->inflate([BII)I+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef;
+HSPLjava/util/zip/Inflater;->inflate([BII)I
 HSPLjava/util/zip/Inflater;->needsDictionary()Z
 HSPLjava/util/zip/Inflater;->needsInput()Z
 HSPLjava/util/zip/Inflater;->reset()V
@@ -7978,19 +8058,19 @@
 HSPLjava/util/zip/InflaterInputStream;->available()I
 HSPLjava/util/zip/InflaterInputStream;->close()V
 HSPLjava/util/zip/InflaterInputStream;->ensureOpen()V
-HSPLjava/util/zip/InflaterInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/PushbackInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
+HSPLjava/util/zip/InflaterInputStream;->fill()V
 HSPLjava/util/zip/InflaterInputStream;->read()I
-HSPLjava/util/zip/InflaterInputStream;->read([BII)I+]Ljava/util/zip/InflaterInputStream;Ljava/util/zip/ZipInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
+HSPLjava/util/zip/InflaterInputStream;->read([BII)I
 HSPLjava/util/zip/ZStreamRef;-><init>(J)V
 HSPLjava/util/zip/ZStreamRef;->address()J
 HSPLjava/util/zip/ZStreamRef;->clear()V
 HSPLjava/util/zip/ZipCoder;-><init>(Ljava/nio/charset/Charset;)V
-HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;
+HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/Charset;Lcom/android/icu/charset/CharsetICU;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/util/zip/ZipCoder;->encoder()Ljava/nio/charset/CharsetEncoder;
 HSPLjava/util/zip/ZipCoder;->get(Ljava/nio/charset/Charset;)Ljava/util/zip/ZipCoder;
 HSPLjava/util/zip/ZipCoder;->getBytes(Ljava/lang/String;)[B
 HSPLjava/util/zip/ZipCoder;->isUTF8()Z
-HSPLjava/util/zip/ZipCoder;->toString([BI)Ljava/lang/String;
+HSPLjava/util/zip/ZipCoder;->toString([BI)Ljava/lang/String;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
 HSPLjava/util/zip/ZipEntry;-><init>()V
 HSPLjava/util/zip/ZipEntry;-><init>(Ljava/lang/String;)V
 HSPLjava/util/zip/ZipEntry;-><init>(Ljava/util/zip/ZipEntry;)V
@@ -8032,6 +8112,7 @@
 HSPLjava/util/zip/ZipFile;->getInflater()Ljava/util/zip/Inflater;
 HSPLjava/util/zip/ZipFile;->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;
 HSPLjava/util/zip/ZipFile;->getZipEntry(Ljava/lang/String;J)Ljava/util/zip/ZipEntry;
+HSPLjava/util/zip/ZipFile;->onZipEntryAccess([BI)V+]Ldalvik/system/ZipPathValidator$Callback;Lcom/android/internal/os/SafeZipPathValidatorCallback;]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
 HSPLjava/util/zip/ZipFile;->releaseInflater(Ljava/util/zip/Inflater;)V
 HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;)V
 HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;Ljava/nio/charset/Charset;)V
@@ -8040,7 +8121,7 @@
 HSPLjava/util/zip/ZipInputStream;->createZipEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
 HSPLjava/util/zip/ZipInputStream;->ensureOpen()V
 HSPLjava/util/zip/ZipInputStream;->getNextEntry()Ljava/util/zip/ZipEntry;
-HSPLjava/util/zip/ZipInputStream;->read([BII)I+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;]Ljava/io/InputStream;Ljava/io/PushbackInputStream;
+HSPLjava/util/zip/ZipInputStream;->read([BII)I
 HSPLjava/util/zip/ZipInputStream;->readEnd(Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/zip/ZipInputStream;->readFully([BII)V
 HSPLjava/util/zip/ZipInputStream;->readLOC()Ljava/util/zip/ZipEntry;
@@ -8075,14 +8156,16 @@
 HSPLjavax/crypto/Cipher;->init(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;Ljava/security/SecureRandom;)V
 HSPLjavax/crypto/Cipher;->matchAttribute(Ljava/security/Provider$Service;Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjavax/crypto/Cipher;->tokenizeTransformation(Ljava/lang/String;)[Ljava/lang/String;
-HSPLjavax/crypto/Cipher;->tryCombinations(Ljavax/crypto/Cipher$InitParams;Ljava/security/Provider;[Ljava/lang/String;)Ljavax/crypto/Cipher$CipherSpiAndProvider;
+HSPLjavax/crypto/Cipher;->tryCombinations(Ljavax/crypto/Cipher$InitParams;Ljava/security/Provider;[Ljava/lang/String;)Ljavax/crypto/Cipher$CipherSpiAndProvider;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/security/Provider$Service;Ljava/security/Provider$Service;]Ljava/security/Provider;missing_types]Ljava/util/ArrayList;Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;
 HSPLjavax/crypto/Cipher;->tryTransformWithProvider(Ljavax/crypto/Cipher$InitParams;[Ljava/lang/String;Ljavax/crypto/Cipher$NeedToSet;Ljava/security/Provider$Service;)Ljavax/crypto/Cipher$CipherSpiAndProvider;
 HSPLjavax/crypto/Cipher;->unwrap([BLjava/lang/String;I)Ljava/security/Key;
-HSPLjavax/crypto/Cipher;->update([BII[BI)I+]Ljavax/crypto/Cipher;Ljavax/crypto/Cipher;]Ljavax/crypto/CipherSpi;Lcom/android/org/conscrypt/OpenSSLEvpCipherAES$AES$CBC$PKCS5Padding;,Lcom/android/org/conscrypt/OpenSSLEvpCipherAES$AES$CTR;
+HSPLjavax/crypto/Cipher;->update([BII[BI)I
 HSPLjavax/crypto/Cipher;->updateAAD([B)V
 HSPLjavax/crypto/Cipher;->updateAAD([BII)V
 HSPLjavax/crypto/Cipher;->updateProviderIfNeeded()V
 HSPLjavax/crypto/CipherSpi;-><init>()V
+HSPLjavax/crypto/CipherSpi;->bufferCrypt(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Z)I
+HSPLjavax/crypto/CipherSpi;->engineDoFinal(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I
 HSPLjavax/crypto/JarVerifier;-><init>(Ljava/net/URL;Z)V
 HSPLjavax/crypto/JarVerifier;->verify()V
 HSPLjavax/crypto/JceSecurity$1;-><init>(Ljava/lang/Class;)V
@@ -8250,11 +8333,11 @@
 HSPLjdk/internal/math/FDBigInteger;-><init>(J[CII)V
 HSPLjdk/internal/math/FDBigInteger;-><init>([II)V
 HSPLjdk/internal/math/FDBigInteger;->add(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I
 HSPLjdk/internal/math/FDBigInteger;->big5pow(I)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->checkZeroTail([II)I
 HSPLjdk/internal/math/FDBigInteger;->cmp(Ljdk/internal/math/FDBigInteger;)I
-HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I
 HSPLjdk/internal/math/FDBigInteger;->getNormalizationBias()I
 HSPLjdk/internal/math/FDBigInteger;->leftInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->leftShift(I)Ljdk/internal/math/FDBigInteger;
@@ -8271,20 +8354,20 @@
 HSPLjdk/internal/math/FDBigInteger;->rightInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->size()I
 HSPLjdk/internal/math/FDBigInteger;->trimLeadingZeros()V
-HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FDBigInteger;->valueOfPow2(I)Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;
 HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljava/lang/Object;
 HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;-><init>(ZI[CI)V
-HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D
+HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$mdtoa(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;IJIZ)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$msetSign(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;Z)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;-><init>()V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->developLongDigits(IJI)V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->estimateDecExp(JI)I
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getChars([C)I
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getDecimalExponent()I
@@ -8296,14 +8379,14 @@
 HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->toJavaFormatString()Ljava/lang/String;
 HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->doubleValue()D
 HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->floatValue()F
-HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V+]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
-HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FloatingDecimal$1;
+HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V
+HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(DZ)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
-HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D+]Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;
+HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D
 HSPLjdk/internal/math/FloatingDecimal;->parseFloat(Ljava/lang/String;)F
-HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;
 HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(D)Ljava/lang/String;
 HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(F)Ljava/lang/String;
 HSPLjdk/internal/math/FormattedFloatingDecimal$1;-><init>()V
@@ -8313,13 +8396,16 @@
 HSPLjdk/internal/math/FormattedFloatingDecimal$Form;-><init>(Ljava/lang/String;I)V
 HSPLjdk/internal/math/FormattedFloatingDecimal$Form;->values()[Ljdk/internal/math/FormattedFloatingDecimal$Form;
 HSPLjdk/internal/math/FormattedFloatingDecimal;-><clinit>()V
-HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V+]Ljdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FormattedFloatingDecimal$Form;]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
+HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V
 HSPLjdk/internal/math/FormattedFloatingDecimal;->applyPrecision(I[CII)I
 HSPLjdk/internal/math/FormattedFloatingDecimal;->create(ZI)[C
 HSPLjdk/internal/math/FormattedFloatingDecimal;->fillDecimal(I[CIIZ)V
-HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FormattedFloatingDecimal$1;
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponent()[C
+HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponentRounded()I
 HSPLjdk/internal/math/FormattedFloatingDecimal;->getMantissa()[C
 HSPLjdk/internal/math/FormattedFloatingDecimal;->valueOf(DILjdk/internal/math/FormattedFloatingDecimal$Form;)Ljdk/internal/math/FormattedFloatingDecimal;
+HSPLjdk/internal/misc/Unsafe;->compareAndSetObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z
 HSPLjdk/internal/misc/Unsafe;->getAndAddInt(Ljava/lang/Object;JI)I
 HSPLjdk/internal/misc/Unsafe;->getAndAddLong(Ljava/lang/Object;JJ)J
 HSPLjdk/internal/misc/Unsafe;->getAndSetInt(Ljava/lang/Object;JI)I
@@ -8329,14 +8415,21 @@
 HSPLjdk/internal/misc/Unsafe;->getIntUnaligned(Ljava/lang/Object;J)I
 HSPLjdk/internal/misc/Unsafe;->getLongAcquire(Ljava/lang/Object;J)J
 HSPLjdk/internal/misc/Unsafe;->getLongUnaligned(Ljava/lang/Object;J)J
+HSPLjdk/internal/misc/Unsafe;->getObject(Ljava/lang/Object;J)Ljava/lang/Object;
 HSPLjdk/internal/misc/Unsafe;->getObjectAcquire(Ljava/lang/Object;J)Ljava/lang/Object;
+HSPLjdk/internal/misc/Unsafe;->getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;
+HSPLjdk/internal/misc/Unsafe;->getReferenceAcquire(Ljava/lang/Object;J)Ljava/lang/Object;
 HSPLjdk/internal/misc/Unsafe;->getUnsafe()Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->makeLong(II)J
 HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/Class;Ljava/lang/String;)J
 HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/reflect/Field;)J
+HSPLjdk/internal/misc/Unsafe;->pickPos(II)I
 HSPLjdk/internal/misc/Unsafe;->putIntRelease(Ljava/lang/Object;JI)V
 HSPLjdk/internal/misc/Unsafe;->putLongRelease(Ljava/lang/Object;JJ)V
+HSPLjdk/internal/misc/Unsafe;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V
 HSPLjdk/internal/misc/Unsafe;->putObjectRelease(Ljava/lang/Object;JLjava/lang/Object;)V
+HSPLjdk/internal/misc/Unsafe;->putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V
+HSPLjdk/internal/misc/Unsafe;->putReferenceRelease(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->toUnsignedLong(I)J
 HSPLjdk/internal/misc/VM;->getSavedProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjdk/internal/reflect/Reflection;->getCallerClass()Ljava/lang/Class;
@@ -8369,7 +8462,7 @@
 HSPLlibcore/icu/DecimalFormatData;->getExponentSeparator()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getGroupingSeparator()C
 HSPLlibcore/icu/DecimalFormatData;->getInfinity()Ljava/lang/String;
-HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;+]Ljava/util/Locale;Ljava/util/Locale;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;
+HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;
 HSPLlibcore/icu/DecimalFormatData;->getMinusSign()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNaN()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNumberPattern()Ljava/lang/String;
@@ -8409,7 +8502,7 @@
 HSPLlibcore/io/BlockGuardOs;->close(Ljava/io/FileDescriptor;)V
 HSPLlibcore/io/BlockGuardOs;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V
 HSPLlibcore/io/BlockGuardOs;->fdatasync(Ljava/io/FileDescriptor;)V
-HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;
 HSPLlibcore/io/BlockGuardOs;->ftruncate(Ljava/io/FileDescriptor;J)V
 HSPLlibcore/io/BlockGuardOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
 HSPLlibcore/io/BlockGuardOs;->isInetDomain(I)Z
@@ -8422,7 +8515,7 @@
 HSPLlibcore/io/BlockGuardOs;->lseek(Ljava/io/FileDescriptor;JI)J
 HSPLlibcore/io/BlockGuardOs;->lstat(Ljava/lang/String;)Landroid/system/StructStat;
 HSPLlibcore/io/BlockGuardOs;->mkdir(Ljava/lang/String;I)V
-HSPLlibcore/io/BlockGuardOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor;+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLlibcore/io/BlockGuardOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor;
 HSPLlibcore/io/BlockGuardOs;->poll([Landroid/system/StructPollfd;I)I
 HSPLlibcore/io/BlockGuardOs;->posix_fallocate(Ljava/io/FileDescriptor;JJ)V
 HSPLlibcore/io/BlockGuardOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I
@@ -8434,10 +8527,10 @@
 HSPLlibcore/io/BlockGuardOs;->sendto(Ljava/io/FileDescriptor;[BIIILjava/net/InetAddress;I)I
 HSPLlibcore/io/BlockGuardOs;->socket(III)Ljava/io/FileDescriptor;
 HSPLlibcore/io/BlockGuardOs;->socketpair(IIILjava/io/FileDescriptor;Ljava/io/FileDescriptor;)V
-HSPLlibcore/io/BlockGuardOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/BlockGuardOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;
 HSPLlibcore/io/BlockGuardOs;->statvfs(Ljava/lang/String;)Landroid/system/StructStatVfs;
 HSPLlibcore/io/BlockGuardOs;->tagSocket(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;
-HSPLlibcore/io/BlockGuardOs;->write(Ljava/io/FileDescriptor;[BII)I+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/BlockGuardOs;->write(Ljava/io/FileDescriptor;[BII)I
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection$1;-><init>(Llibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;Ljava/io/InputStream;)V
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection$1;->close()V
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;-><init>(Llibcore/io/ClassPathURLStreamHandler;Ljava/net/URL;)V
@@ -8474,13 +8567,13 @@
 HSPLlibcore/io/ForwardingOs;->getsockname(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
 HSPLlibcore/io/ForwardingOs;->getsockoptInt(Ljava/io/FileDescriptor;II)I
 HSPLlibcore/io/ForwardingOs;->getsockoptLinger(Ljava/io/FileDescriptor;II)Landroid/system/StructLinger;
-HSPLlibcore/io/ForwardingOs;->gettid()I
+HSPLlibcore/io/ForwardingOs;->gettid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->getuid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
 HSPLlibcore/io/ForwardingOs;->if_nametoindex(Ljava/lang/String;)I
 HSPLlibcore/io/ForwardingOs;->ioctlInt(Ljava/io/FileDescriptor;I)I
 HSPLlibcore/io/ForwardingOs;->listen(Ljava/io/FileDescriptor;I)V
-HSPLlibcore/io/ForwardingOs;->lseek(Ljava/io/FileDescriptor;JI)J+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
+HSPLlibcore/io/ForwardingOs;->lseek(Ljava/io/FileDescriptor;JI)J
 HSPLlibcore/io/ForwardingOs;->lstat(Ljava/lang/String;)Landroid/system/StructStat;
 HSPLlibcore/io/ForwardingOs;->mkdir(Ljava/lang/String;I)V
 HSPLlibcore/io/ForwardingOs;->mmap(JJIILjava/io/FileDescriptor;J)J
@@ -8488,7 +8581,7 @@
 HSPLlibcore/io/ForwardingOs;->pipe2(I)[Ljava/io/FileDescriptor;
 HSPLlibcore/io/ForwardingOs;->poll([Landroid/system/StructPollfd;I)I
 HSPLlibcore/io/ForwardingOs;->posix_fallocate(Ljava/io/FileDescriptor;JJ)V
-HSPLlibcore/io/ForwardingOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
+HSPLlibcore/io/ForwardingOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I
 HSPLlibcore/io/ForwardingOs;->read(Ljava/io/FileDescriptor;[BII)I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
 HSPLlibcore/io/ForwardingOs;->readlink(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/io/ForwardingOs;->recvfrom(Ljava/io/FileDescriptor;[BIIILjava/net/InetSocketAddress;)I
@@ -8504,7 +8597,7 @@
 HSPLlibcore/io/ForwardingOs;->shutdown(Ljava/io/FileDescriptor;I)V
 HSPLlibcore/io/ForwardingOs;->socket(III)Ljava/io/FileDescriptor;
 HSPLlibcore/io/ForwardingOs;->socketpair(IIILjava/io/FileDescriptor;Ljava/io/FileDescriptor;)V
-HSPLlibcore/io/ForwardingOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
+HSPLlibcore/io/ForwardingOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;
 HSPLlibcore/io/ForwardingOs;->statvfs(Ljava/lang/String;)Landroid/system/StructStatVfs;
 HSPLlibcore/io/ForwardingOs;->strerror(I)Ljava/lang/String;
 HSPLlibcore/io/ForwardingOs;->sysconf(I)J
@@ -8512,7 +8605,7 @@
 HSPLlibcore/io/IoBridge;->bind(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V
 HSPLlibcore/io/IoBridge;->booleanFromInt(I)Z
 HSPLlibcore/io/IoBridge;->booleanToInt(Z)I
-HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;
+HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V
 HSPLlibcore/io/IoBridge;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V
 HSPLlibcore/io/IoBridge;->connectErrno(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V
 HSPLlibcore/io/IoBridge;->createMessageForException(Ljava/io/FileDescriptor;Ljava/net/InetAddress;IILjava/lang/Exception;)Ljava/lang/String;
@@ -8520,7 +8613,7 @@
 HSPLlibcore/io/IoBridge;->getSocketOption(Ljava/io/FileDescriptor;I)Ljava/lang/Object;
 HSPLlibcore/io/IoBridge;->getSocketOptionErrno(Ljava/io/FileDescriptor;I)Ljava/lang/Object;
 HSPLlibcore/io/IoBridge;->isConnected(Ljava/io/FileDescriptor;Ljava/net/InetAddress;III)Z
-HSPLlibcore/io/IoBridge;->open(Ljava/lang/String;I)Ljava/io/FileDescriptor;+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLlibcore/io/IoBridge;->open(Ljava/lang/String;I)Ljava/io/FileDescriptor;
 HSPLlibcore/io/IoBridge;->poll(Ljava/io/FileDescriptor;II)V
 HSPLlibcore/io/IoBridge;->postRecvfrom(ZLjava/net/DatagramPacket;Ljava/net/InetSocketAddress;I)I
 HSPLlibcore/io/IoBridge;->read(Ljava/io/FileDescriptor;[BII)I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
@@ -8542,7 +8635,7 @@
 HSPLlibcore/io/IoUtils;->generateFdOwnerId(Ljava/lang/Object;)J
 HSPLlibcore/io/IoUtils;->isParcelFileDescriptor(Ljava/lang/Object;)Z
 HSPLlibcore/io/IoUtils;->setBlocking(Ljava/io/FileDescriptor;Z)V
-HSPLlibcore/io/IoUtils;->setFdOwner(Ljava/io/FileDescriptor;Ljava/lang/Object;)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;
+HSPLlibcore/io/IoUtils;->setFdOwner(Ljava/io/FileDescriptor;Ljava/lang/Object;)V
 HSPLlibcore/io/Libcore;->compareAndSetOs(Llibcore/io/Os;Llibcore/io/Os;)Z
 HSPLlibcore/io/Libcore;->getOs()Llibcore/io/Os;
 HSPLlibcore/io/Linux;->pread(Ljava/io/FileDescriptor;[BIIJ)I
@@ -8597,7 +8690,7 @@
 HSPLlibcore/reflect/GenericSignatureParser;->parseFieldTypeSignature()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseForClass(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
 HSPLlibcore/reflect/GenericSignatureParser;->parseForConstructor(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V
-HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
+HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->parseForMethod(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V
 HSPLlibcore/reflect/GenericSignatureParser;->parseFormalTypeParameter()Llibcore/reflect/TypeVariableImpl;
 HSPLlibcore/reflect/GenericSignatureParser;->parseMethodTypeSignature([Ljava/lang/Class;)V
@@ -8620,6 +8713,7 @@
 HSPLlibcore/reflect/ListOfVariables;->add(Ljava/lang/reflect/TypeVariable;)V
 HSPLlibcore/reflect/ListOfVariables;->getArray()[Ljava/lang/reflect/TypeVariable;
 HSPLlibcore/reflect/ParameterizedTypeImpl;-><init>(Llibcore/reflect/ParameterizedTypeImpl;Ljava/lang/String;Llibcore/reflect/ListOfTypes;Ljava/lang/ClassLoader;)V
+HSPLlibcore/reflect/ParameterizedTypeImpl;->equals(Ljava/lang/Object;)Z
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getOwnerType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/Class;
@@ -8673,18 +8767,18 @@
 HSPLlibcore/util/NativeAllocationRegistry;->createNonmalloced(Ljava/lang/ClassLoader;JJ)Llibcore/util/NativeAllocationRegistry;
 HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
 HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(Ljava/lang/Object;J)Ljava/lang/Runnable;+]Llibcore/util/NativeAllocationRegistry$CleanerThunk;Llibcore/util/NativeAllocationRegistry$CleanerThunk;
-HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
+HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V
 HSPLlibcore/util/SneakyThrow;->sneakyThrow(Ljava/lang/Throwable;)V
 HSPLlibcore/util/SneakyThrow;->sneakyThrow_(Ljava/lang/Throwable;)V
 HSPLlibcore/util/XmlObjectFactory;->newXmlPullParser()Lorg/xmlpull/v1/XmlPullParser;
-HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;
+HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V
 HSPLlibcore/util/ZoneInfo;->clone()Ljava/lang/Object;
 HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;)Llibcore/util/ZoneInfo;
-HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Ljava/lang/Integer;Ljava/lang/Integer;
+HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;
 HSPLlibcore/util/ZoneInfo;->getDSTSavings()I
-HSPLlibcore/util/ZoneInfo;->getOffset(J)I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
+HSPLlibcore/util/ZoneInfo;->getOffset(J)I
 HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I
-HSPLlibcore/util/ZoneInfo;->getRawOffset()I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
+HSPLlibcore/util/ZoneInfo;->getRawOffset()I
 HSPLlibcore/util/ZoneInfo;->hasSameRules(Ljava/util/TimeZone;)Z
 HSPLlibcore/util/ZoneInfo;->hashCode()I
 HSPLlibcore/util/ZoneInfo;->inDaylightTime(Ljava/util/Date;)Z
@@ -8708,6 +8802,9 @@
 HSPLorg/apache/harmony/xml/ExpatReader;-><init>()V
 HSPLorg/apache/harmony/xml/ExpatReader;->parse(Lorg/xml/sax/InputSource;)V
 HSPLorg/apache/harmony/xml/ExpatReader;->setContentHandler(Lorg/xml/sax/ContentHandler;)V
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->getNodeType()S
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->getOwnerElement()Lorg/w3c/dom/Element;
+HSPLorg/apache/harmony/xml/dom/AttrImpl;->setValue(Ljava/lang/String;)V
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;Ljava/lang/String;)V
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getData()Ljava/lang/String;
 HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getNodeValue()Ljava/lang/String;
@@ -8741,7 +8838,7 @@
 HSPLorg/apache/harmony/xml/dom/LeafNodeImpl;->isParentOf(Lorg/w3c/dom/Node;)Z
 HSPLorg/apache/harmony/xml/dom/NodeImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;)V
 HSPLorg/apache/harmony/xml/dom/NodeImpl;->getTextContent()Ljava/lang/String;
-HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V+]Lorg/apache/harmony/xml/dom/NodeImpl;Lorg/apache/harmony/xml/dom/AttrImpl;,Lorg/apache/harmony/xml/dom/ElementImpl;]Ljava/lang/String;Ljava/lang/String;
+HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;-><init>()V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;->add(Lorg/apache/harmony/xml/dom/NodeImpl;)V
 HSPLorg/apache/harmony/xml/dom/NodeListImpl;->getLength()I
@@ -8752,8 +8849,8 @@
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;->newDocumentBuilder()Ljavax/xml/parsers/DocumentBuilder;
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><clinit>()V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><init>()V
-HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl;
-HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl;,Lorg/apache/harmony/xml/dom/DocumentImpl;]Lorg/w3c/dom/Element;Lorg/apache/harmony/xml/dom/ElementImpl;]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Lorg/w3c/dom/Attr;Lorg/apache/harmony/xml/dom/AttrImpl;]Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/apache/harmony/xml/dom/DocumentImpl;
+HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V
+HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document;
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setCoalescing(Z)V
 HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setIgnoreComments(Z)V
@@ -8848,7 +8945,7 @@
 HSPLorg/json/JSONStringer;->value(Ljava/lang/Object;)Lorg/json/JSONStringer;
 HSPLorg/json/JSONTokener;-><init>(Ljava/lang/String;)V
 HSPLorg/json/JSONTokener;->nextCleanInternal()I
-HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;
+HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLorg/json/JSONTokener;->nextToInternal(Ljava/lang/String;)Ljava/lang/String;
 HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;
 HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;
@@ -8891,7 +8988,7 @@
 HSPLsun/misc/ASCIICaseInsensitiveComparator;->toLower(I)I
 HSPLsun/misc/Cleaner;-><init>(Ljava/lang/Object;Ljava/lang/Runnable;)V
 HSPLsun/misc/Cleaner;->add(Lsun/misc/Cleaner;)Lsun/misc/Cleaner;
-HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Llibcore/util/NativeAllocationRegistry$CleanerThunk;,Lsun/nio/ch/FileChannelImpl$Unmapper;
+HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Llibcore/util/NativeAllocationRegistry$CleanerThunk;
 HSPLsun/misc/Cleaner;->create(Ljava/lang/Object;Ljava/lang/Runnable;)Lsun/misc/Cleaner;
 HSPLsun/misc/Cleaner;->remove(Lsun/misc/Cleaner;)Z
 HSPLsun/misc/CompoundEnumeration;-><init>([Ljava/util/Enumeration;)V
@@ -8961,7 +9058,7 @@
 HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZLjava/lang/Object;)Ljava/nio/channels/FileChannel;
 HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)Ljava/nio/channels/FileChannel;
 HSPLsun/nio/ch/FileChannelImpl;->position()J
-HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel;+]Lsun/nio/ch/NativeThreadSet;Lsun/nio/ch/NativeThreadSet;]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel;+]Lsun/nio/ch/NativeThreadSet;Lsun/nio/ch/NativeThreadSet;]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLsun/nio/ch/FileChannelImpl;->read(Ljava/nio/ByteBuffer;)I
 HSPLsun/nio/ch/FileChannelImpl;->release(Lsun/nio/ch/FileLockImpl;)V
 HSPLsun/nio/ch/FileChannelImpl;->size()J
@@ -9160,6 +9257,8 @@
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;-><init>(Lsun/nio/fs/UnixDirectoryStream;Ljava/nio/file/DirectoryStream;)V
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->hasNext()Z
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->isSelfOrParent([B)Z
+HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/lang/Object;
+HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->readNextEntry()Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixDirectoryStream;->-$$Nest$fgetdp(Lsun/nio/fs/UnixDirectoryStream;)J
 HSPLsun/nio/fs/UnixDirectoryStream;-><init>(Lsun/nio/fs/UnixPath;JLjava/nio/file/DirectoryStream$Filter;)V
@@ -9172,6 +9271,7 @@
 HSPLsun/nio/fs/UnixDirectoryStream;->writeLock()Ljava/util/concurrent/locks/Lock;
 HSPLsun/nio/fs/UnixException;-><init>(I)V
 HSPLsun/nio/fs/UnixException;->errno()I
+HSPLsun/nio/fs/UnixException;->fillInStackTrace()Ljava/lang/Throwable;
 HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;)V
 HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixPath;)V
 HSPLsun/nio/fs/UnixException;->translateToIOException(Ljava/lang/String;Ljava/lang/String;)Ljava/io/IOException;
@@ -9182,6 +9282,7 @@
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->creationTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isDirectory()Z
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isRegularFile()Z
+HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isSymbolicLink()Z
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastAccessTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastModifiedTime()Ljava/nio/file/attribute/FileTime;
 HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->size()J
@@ -9232,7 +9333,7 @@
 HSPLsun/nio/fs/UnixPath;->getPathForExceptionMessage()Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->initOffsets()V
 HSPLsun/nio/fs/UnixPath;->isEmpty()Z
-HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->normalizeAndCheck(Ljava/lang/String;)Ljava/lang/String;
 HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Ljava/nio/file/Path;
 HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Lsun/nio/fs/UnixPath;
@@ -9521,7 +9622,7 @@
 HSPLsun/security/util/DerInputStream;->mark(I)V
 HSPLsun/security/util/DerInputStream;->peekByte()I
 HSPLsun/security/util/DerInputStream;->readVector(I)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/util/Vector;Ljava/util/Vector;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
+HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->reset()V
 HSPLsun/security/util/DerInputStream;->subStream(IZ)Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerInputStream;->toByteArray()[B
@@ -9623,7 +9724,7 @@
 HSPLsun/security/x509/AVA;->parseString(Ljava/io/Reader;IILjava/lang/StringBuilder;)Lsun/security/util/DerValue;
 HSPLsun/security/x509/AVA;->readChar(Ljava/io/Reader;Ljava/lang/String;)I
 HSPLsun/security/x509/AVA;->toKeyword(ILjava/util/Map;)Ljava/lang/String;
-HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/util/DerValue;Lsun/security/util/DerValue;
+HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;
 HSPLsun/security/x509/AVA;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/AVAKeyword;->getOID(Ljava/lang/String;ILjava/util/Map;)Lsun/security/util/ObjectIdentifier;
@@ -9802,7 +9903,7 @@
 HSPLsun/util/calendar/BaseCalendar$Date;->hit(J)Z
 HSPLsun/util/calendar/BaseCalendar$Date;->setCache(IJI)V
 HSPLsun/util/calendar/BaseCalendar;-><init>()V
-HSPLsun/util/calendar/BaseCalendar;->getCalendarDateFromFixedDate(Lsun/util/calendar/CalendarDate;J)V+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
+HSPLsun/util/calendar/BaseCalendar;->getCalendarDateFromFixedDate(Lsun/util/calendar/CalendarDate;J)V
 HSPLsun/util/calendar/BaseCalendar;->getDayOfWeekFromFixedDate(J)I
 HSPLsun/util/calendar/BaseCalendar;->getDayOfYear(III)J
 HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J
@@ -9878,20 +9979,19 @@
 HSPLsun/util/locale/BaseLocale$Cache;->createObject(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetlang(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetregn(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetscrt(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetvart(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference;
-HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$mgetBaseLocale(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
-HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z+]Ljava/lang/ref/SoftReference;Ljava/lang/ref/SoftReference;
+HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale$Key-IA;)V
+HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z
+HSPLsun/util/locale/BaseLocale$Key;->getBaseLocale()Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale$Key;->hashCode()I
+HSPLsun/util/locale/BaseLocale$Key;->hashCode(Lsun/util/locale/BaseLocale;)I
 HSPLsun/util/locale/BaseLocale$Key;->normalize(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key;
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lsun/util/locale/BaseLocale-IA;)V
+HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
+HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale-IA;)V
 HSPLsun/util/locale/BaseLocale;->cleanCache()V
 HSPLsun/util/locale/BaseLocale;->equals(Ljava/lang/Object;)Z
-HSPLsun/util/locale/BaseLocale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/BaseLocale;+]Lsun/util/locale/BaseLocale$Cache;Lsun/util/locale/BaseLocale$Cache;
+HSPLsun/util/locale/BaseLocale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/BaseLocale;
 HSPLsun/util/locale/BaseLocale;->getLanguage()Ljava/lang/String;
 HSPLsun/util/locale/BaseLocale;->getRegion()Ljava/lang/String;
 HSPLsun/util/locale/BaseLocale;->getScript()Ljava/lang/String;
@@ -9936,8 +10036,8 @@
 HSPLsun/util/locale/LanguageTag;->parseVariants(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z
 HSPLsun/util/locale/LocaleObjectCache$CacheEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V
 HSPLsun/util/locale/LocaleObjectCache$CacheEntry;->getKey()Ljava/lang/Object;
-HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue;
-HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Ljava/util/Locale$Cache;,Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry;
+HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V
+HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/util/locale/LocaleObjectCache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/util/locale/LocaleUtils;->caseIgnoreMatch(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLsun/util/locale/LocaleUtils;->isAlpha(C)Z
@@ -9967,6 +10067,7 @@
 HSPLsun/util/locale/StringTokenIterator;->next()Ljava/lang/String;
 HSPLsun/util/locale/StringTokenIterator;->nextDelimiter(I)I
 HSPLsun/util/locale/StringTokenIterator;->setStart(I)Lsun/util/locale/StringTokenIterator;
+HSPLsun/util/locale/provider/CalendarDataUtility;->retrieveFirstDayOfWeek(Ljava/util/Locale;I)I
 HSPLsun/util/logging/LoggingSupport$2;-><init>()V
 HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/Object;
 HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/String;
@@ -10044,6 +10145,7 @@
 Lcom/android/okhttp/HttpUrl;
 Lcom/android/okhttp/HttpsHandler;
 Lcom/android/okhttp/Interceptor$Chain;
+Lcom/android/okhttp/MediaType;
 Lcom/android/okhttp/OkCacheContainer;
 Lcom/android/okhttp/OkHttpClient$1;
 Lcom/android/okhttp/OkHttpClient;
@@ -10388,6 +10490,7 @@
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/ClassUtil;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil$2;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE$Util;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE;
@@ -10469,13 +10572,16 @@
 Ldalvik/system/SocketTagger;
 Ldalvik/system/VMDebug;
 Ldalvik/system/VMRuntime$HiddenApiUsageLogger;
+Ldalvik/system/VMRuntime$SdkVersionContainer;
 Ldalvik/system/VMRuntime;
 Ldalvik/system/VMStack;
+Ldalvik/system/ZipPathValidator$1;
+Ldalvik/system/ZipPathValidator$Callback;
+Ldalvik/system/ZipPathValidator;
 Ldalvik/system/ZygoteHooks;
 Ljava/awt/font/NumericShaper;
 Ljava/awt/font/TextAttribute;
 Ljava/io/Bits;
-Ljava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0;
 Ljava/io/BufferedInputStream;
 Ljava/io/BufferedOutputStream;
 Ljava/io/BufferedReader;
@@ -10593,6 +10699,8 @@
 Ljava/io/WriteAbortedException;
 Ljava/io/Writer;
 Ljava/lang/AbstractMethodError;
+Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda0;
+Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda1;
 Ljava/lang/AbstractStringBuilder;
 Ljava/lang/AndroidHardcodedSystemProperties;
 Ljava/lang/Appendable;
@@ -10653,8 +10761,6 @@
 Ljava/lang/InheritableThreadLocal;
 Ljava/lang/InstantiationError;
 Ljava/lang/InstantiationException;
-Ljava/lang/Integer$$ExternalSyntheticBackport0;
-Ljava/lang/Integer$$ExternalSyntheticBackport1;
 Ljava/lang/Integer$IntegerCache;
 Ljava/lang/Integer;
 Ljava/lang/InternalError;
@@ -10700,9 +10806,18 @@
 Ljava/lang/SecurityManager;
 Ljava/lang/Short$ShortCache;
 Ljava/lang/Short;
+Ljava/lang/StackFrameInfo;
 Ljava/lang/StackOverflowError;
+Ljava/lang/StackStreamFactory$AbstractStackWalker;
+Ljava/lang/StackStreamFactory;
 Ljava/lang/StackTraceElement;
+Ljava/lang/StackWalker$StackFrame;
+Ljava/lang/StackWalker;
 Ljava/lang/StrictMath;
+Ljava/lang/String$$ExternalSyntheticLambda0;
+Ljava/lang/String$$ExternalSyntheticLambda1;
+Ljava/lang/String$$ExternalSyntheticLambda2;
+Ljava/lang/String$$ExternalSyntheticLambda3;
 Ljava/lang/String$CaseInsensitiveComparator-IA;
 Ljava/lang/String$CaseInsensitiveComparator;
 Ljava/lang/String;
@@ -10710,8 +10825,13 @@
 Ljava/lang/StringBuilder;
 Ljava/lang/StringFactory;
 Ljava/lang/StringIndexOutOfBoundsException;
+Ljava/lang/StringLatin1$CharsSpliterator;
+Ljava/lang/StringLatin1$LinesSpliterator;
+Ljava/lang/StringLatin1;
 Ljava/lang/StringUTF16$CharsSpliterator;
+Ljava/lang/StringUTF16$CharsSpliteratorForString;
 Ljava/lang/StringUTF16$CodePointsSpliterator;
+Ljava/lang/StringUTF16$CodePointsSpliteratorForString;
 Ljava/lang/StringUTF16;
 Ljava/lang/System$PropertiesWithNonOverrideableDefaults;
 Ljava/lang/System;
@@ -10742,6 +10862,7 @@
 Ljava/lang/UNIXProcess$ProcessReaperThreadFactory;
 Ljava/lang/UNIXProcess;
 Ljava/lang/UnsatisfiedLinkError;
+Ljava/lang/UnsupportedClassVersionError;
 Ljava/lang/UnsupportedOperationException;
 Ljava/lang/VMClassLoader;
 Ljava/lang/VerifyError;
@@ -10819,6 +10940,7 @@
 Ljava/lang/invoke/Transformers$ReferenceArrayElementSetter;
 Ljava/lang/invoke/Transformers$ReferenceIdentity;
 Ljava/lang/invoke/Transformers$Spreader;
+Ljava/lang/invoke/Transformers$TableSwitch;
 Ljava/lang/invoke/Transformers$Transformer;
 Ljava/lang/invoke/Transformers$TryFinally;
 Ljava/lang/invoke/Transformers$VarargsCollector;
@@ -10956,6 +11078,7 @@
 Ljava/net/Proxy;
 Ljava/net/ProxySelector;
 Ljava/net/ResponseCache;
+Ljava/net/ServerSocket$1;
 Ljava/net/ServerSocket;
 Ljava/net/Socket$1;
 Ljava/net/Socket$2;
@@ -10989,6 +11112,7 @@
 Ljava/net/UnknownServiceException;
 Ljava/nio/Bits;
 Ljava/nio/Buffer;
+Ljava/nio/BufferMismatch;
 Ljava/nio/BufferOverflowException;
 Ljava/nio/BufferUnderflowException;
 Ljava/nio/ByteBuffer;
@@ -11069,8 +11193,6 @@
 Ljava/nio/charset/CharsetDecoder;
 Ljava/nio/charset/CharsetEncoder;
 Ljava/nio/charset/CoderMalfunctionError;
-Ljava/nio/charset/CoderResult$1;
-Ljava/nio/charset/CoderResult$2;
 Ljava/nio/charset/CoderResult$Cache;
 Ljava/nio/charset/CoderResult;
 Ljava/nio/charset/CodingErrorAction;
@@ -11091,6 +11213,8 @@
 Ljava/nio/file/FileSystems$DefaultFileSystemHolder$1;
 Ljava/nio/file/FileSystems$DefaultFileSystemHolder;
 Ljava/nio/file/FileSystems;
+Ljava/nio/file/FileVisitResult;
+Ljava/nio/file/FileVisitor;
 Ljava/nio/file/Files$AcceptAllFilter;
 Ljava/nio/file/Files;
 Ljava/nio/file/InvalidPathException;
@@ -11102,6 +11226,7 @@
 Ljava/nio/file/Paths;
 Ljava/nio/file/ProviderMismatchException;
 Ljava/nio/file/SecureDirectoryStream;
+Ljava/nio/file/SimpleFileVisitor;
 Ljava/nio/file/StandardCopyOption;
 Ljava/nio/file/StandardOpenOption;
 Ljava/nio/file/Watchable;
@@ -11320,6 +11445,7 @@
 Ljava/time/Duration;
 Ljava/time/Instant$1;
 Ljava/time/Instant;
+Ljava/time/InstantSource;
 Ljava/time/LocalDate$1;
 Ljava/time/LocalDate;
 Ljava/time/LocalDateTime;
@@ -11349,7 +11475,6 @@
 Ljava/time/format/DateTimeFormatterBuilder$$ExternalSyntheticLambda0;
 Ljava/time/format/DateTimeFormatterBuilder$1;
 Ljava/time/format/DateTimeFormatterBuilder$2;
-Ljava/time/format/DateTimeFormatterBuilder$3;
 Ljava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$CompositePrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$DateTimePrinterParser;
@@ -11437,14 +11562,15 @@
 Ljava/util/AbstractQueue;
 Ljava/util/AbstractSequentialList;
 Ljava/util/AbstractSet;
+Ljava/util/ArrayDeque$$ExternalSyntheticLambda1;
 Ljava/util/ArrayDeque$DeqIterator;
 Ljava/util/ArrayDeque$DescendingIterator;
 Ljava/util/ArrayDeque;
 Ljava/util/ArrayList$ArrayListSpliterator;
-Ljava/util/ArrayList$Itr-IA;
 Ljava/util/ArrayList$Itr;
 Ljava/util/ArrayList$ListItr;
 Ljava/util/ArrayList$SubList$1;
+Ljava/util/ArrayList$SubList$2;
 Ljava/util/ArrayList$SubList;
 Ljava/util/ArrayList;
 Ljava/util/ArrayPrefixHelpers$CumulateTask;
@@ -11459,18 +11585,12 @@
 Ljava/util/Arrays$ArrayList;
 Ljava/util/Arrays$NaturalOrder;
 Ljava/util/Arrays;
-Ljava/util/ArraysParallelSortHelpers$FJByte$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJChar$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJDouble$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJFloat$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJInt$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJLong$Sorter;
 Ljava/util/ArraysParallelSortHelpers$FJObject$Sorter;
-Ljava/util/ArraysParallelSortHelpers$FJShort$Sorter;
 Ljava/util/Base64$Decoder;
 Ljava/util/Base64$Encoder;
 Ljava/util/Base64;
 Ljava/util/BitSet;
+Ljava/util/Calendar$$ExternalSyntheticLambda0;
 Ljava/util/Calendar$Builder;
 Ljava/util/Calendar;
 Ljava/util/Collection;
@@ -11545,6 +11665,7 @@
 Ljava/util/Date;
 Ljava/util/Deque;
 Ljava/util/Dictionary;
+Ljava/util/DualPivotQuicksort$Sorter;
 Ljava/util/DualPivotQuicksort;
 Ljava/util/DuplicateFormatFlagsException;
 Ljava/util/EmptyStackException;
@@ -11619,13 +11740,13 @@
 Ljava/util/ImmutableCollections$List12;
 Ljava/util/ImmutableCollections$ListItr;
 Ljava/util/ImmutableCollections$ListN;
-Ljava/util/ImmutableCollections$Map0;
 Ljava/util/ImmutableCollections$Map1;
+Ljava/util/ImmutableCollections$MapN$1;
+Ljava/util/ImmutableCollections$MapN$MapNIterator;
 Ljava/util/ImmutableCollections$MapN;
-Ljava/util/ImmutableCollections$Set0;
-Ljava/util/ImmutableCollections$Set1;
-Ljava/util/ImmutableCollections$Set2;
+Ljava/util/ImmutableCollections$Set12;
 Ljava/util/ImmutableCollections$SetN;
+Ljava/util/ImmutableCollections$SubList;
 Ljava/util/ImmutableCollections;
 Ljava/util/InputMismatchException;
 Ljava/util/Iterator;
@@ -11654,6 +11775,10 @@
 Ljava/util/Locale$Cache;
 Ljava/util/Locale$Category;
 Ljava/util/Locale$FilteringMode;
+Ljava/util/Locale$IsoCountryCode$1;
+Ljava/util/Locale$IsoCountryCode$2;
+Ljava/util/Locale$IsoCountryCode$3;
+Ljava/util/Locale$IsoCountryCode;
 Ljava/util/Locale$LanguageRange;
 Ljava/util/Locale$LocaleKey;
 Ljava/util/Locale$NoImagePreloadHolder;
@@ -11690,14 +11815,15 @@
 Ljava/util/ResourceBundle$BundleReference;
 Ljava/util/ResourceBundle$CacheKey;
 Ljava/util/ResourceBundle$CacheKeyReference;
+Ljava/util/ResourceBundle$Control$$ExternalSyntheticLambda0;
 Ljava/util/ResourceBundle$Control$1;
 Ljava/util/ResourceBundle$Control$CandidateListCache;
 Ljava/util/ResourceBundle$Control;
-Ljava/util/ResourceBundle$LoaderReference;
+Ljava/util/ResourceBundle$KeyElementReference;
 Ljava/util/ResourceBundle$RBClassLoader;
 Ljava/util/ResourceBundle$SingleFormatControl;
 Ljava/util/ResourceBundle;
-Ljava/util/Scanner$1;
+Ljava/util/Scanner$PatternLRUCache;
 Ljava/util/Scanner;
 Ljava/util/ServiceConfigurationError;
 Ljava/util/ServiceLoader$1;
@@ -11879,12 +12005,13 @@
 Ljava/util/concurrent/Executors$RunnableAdapter;
 Ljava/util/concurrent/Executors;
 Ljava/util/concurrent/ForkJoinPool$1;
+Ljava/util/concurrent/ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$DefaultForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory;
 Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;
 Ljava/util/concurrent/ForkJoinPool$WorkQueue;
 Ljava/util/concurrent/ForkJoinPool;
-Ljava/util/concurrent/ForkJoinTask$ExceptionNode;
+Ljava/util/concurrent/ForkJoinTask$Aux;
 Ljava/util/concurrent/ForkJoinTask;
 Ljava/util/concurrent/ForkJoinWorkerThread;
 Ljava/util/concurrent/Future;
@@ -11946,8 +12073,11 @@
 Ljava/util/concurrent/atomic/Striped64$Cell;
 Ljava/util/concurrent/atomic/Striped64;
 Ljava/util/concurrent/locks/AbstractOwnableSynchronizer;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
+Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;
 Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;
 Ljava/util/concurrent/locks/Condition;
 Ljava/util/concurrent/locks/Lock;
@@ -11994,9 +12124,13 @@
 Ljava/util/function/IntUnaryOperator;
 Ljava/util/function/LongBinaryOperator;
 Ljava/util/function/LongConsumer;
+Ljava/util/function/LongFunction;
 Ljava/util/function/LongPredicate;
 Ljava/util/function/LongSupplier;
 Ljava/util/function/LongUnaryOperator;
+Ljava/util/function/ObjDoubleConsumer;
+Ljava/util/function/ObjIntConsumer;
+Ljava/util/function/ObjLongConsumer;
 Ljava/util/function/Predicate;
 Ljava/util/function/Supplier;
 Ljava/util/function/ToDoubleBiFunction;
@@ -12073,6 +12207,7 @@
 Ljava/util/stream/Collector$Characteristics;
 Ljava/util/stream/Collector;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda0;
+Ljava/util/stream/Collectors$$ExternalSyntheticLambda13;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda15;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda1;
 Ljava/util/stream/Collectors$$ExternalSyntheticLambda20;
@@ -12262,6 +12397,7 @@
 Ljava/util/zip/Adler32;
 Ljava/util/zip/CRC32;
 Ljava/util/zip/CheckedInputStream;
+Ljava/util/zip/Checksum$1;
 Ljava/util/zip/Checksum;
 Ljava/util/zip/DataFormatException;
 Ljava/util/zip/Deflater;
@@ -12415,7 +12551,9 @@
 Ljdk/internal/math/FormattedFloatingDecimal;
 Ljdk/internal/misc/JavaObjectInputStreamAccess;
 Ljdk/internal/misc/SharedSecrets;
+Ljdk/internal/misc/TerminatingThreadLocal;
 Ljdk/internal/misc/Unsafe;
+Ljdk/internal/misc/UnsafeConstants;
 Ljdk/internal/misc/VM;
 Ljdk/internal/reflect/Reflection;
 Ljdk/internal/util/ArraysSupport;
@@ -12427,7 +12565,6 @@
 Llibcore/content/type/MimeMap;
 Llibcore/icu/CollationKeyICU;
 Llibcore/icu/DateIntervalFormat;
-Llibcore/icu/DateUtilsBridge;
 Llibcore/icu/DecimalFormatData;
 Llibcore/icu/ICU;
 Llibcore/icu/LocaleData;
@@ -12590,7 +12727,6 @@
 Lsun/misc/JavaIOFileDescriptorAccess;
 Lsun/misc/LRUCache;
 Lsun/misc/SharedSecrets;
-Lsun/misc/Unsafe$$ExternalSyntheticBackportWithForwarding0;
 Lsun/misc/Unsafe;
 Lsun/misc/VM;
 Lsun/misc/Version;
@@ -12792,6 +12928,7 @@
 Lsun/security/util/MemoryCache$SoftCacheEntry;
 Lsun/security/util/MemoryCache;
 Lsun/security/util/ObjectIdentifier;
+Lsun/security/util/PropertyExpander;
 Lsun/security/util/Resources;
 Lsun/security/util/ResourcesMgr$1;
 Lsun/security/util/ResourcesMgr;
@@ -12869,6 +13006,7 @@
 Lsun/util/calendar/BaseCalendar$Date;
 Lsun/util/calendar/BaseCalendar;
 Lsun/util/calendar/CalendarDate;
+Lsun/util/calendar/CalendarSystem$GregorianHolder;
 Lsun/util/calendar/CalendarSystem;
 Lsun/util/calendar/CalendarUtils;
 Lsun/util/calendar/Era;
@@ -12894,6 +13032,7 @@
 Lsun/util/locale/ParseStatus;
 Lsun/util/locale/StringTokenIterator;
 Lsun/util/locale/UnicodeLocaleExtension;
+Lsun/util/locale/provider/CalendarDataUtility;
 Lsun/util/logging/LoggingProxy;
 Lsun/util/logging/LoggingSupport$1;
 Lsun/util/logging/LoggingSupport$2;
@@ -12954,6 +13093,7 @@
 [Ljava/lang/Float;
 [Ljava/lang/Integer;
 [Ljava/lang/Long;
+[Ljava/lang/Number;
 [Ljava/lang/Object;
 [Ljava/lang/Package;
 [Ljava/lang/Runnable;
@@ -12992,6 +13132,7 @@
 [Ljava/net/StandardProtocolFamily;
 [Ljava/nio/ByteBuffer;
 [Ljava/nio/channels/SelectionKey;
+[Ljava/nio/charset/CoderResult;
 [Ljava/nio/file/AccessMode;
 [Ljava/nio/file/CopyOption;
 [Ljava/nio/file/LinkOption;
@@ -13040,13 +13181,13 @@
 [Ljava/util/Comparators$NaturalOrderComparator;
 [Ljava/util/Enumeration;
 [Ljava/util/Formatter$Flags;
-[Ljava/util/Formatter$FormatString;
 [Ljava/util/HashMap$Node;
 [Ljava/util/HashMap;
 [Ljava/util/Hashtable$HashtableEntry;
 [Ljava/util/List;
 [Ljava/util/Locale$Category;
 [Ljava/util/Locale$FilteringMode;
+[Ljava/util/Locale$IsoCountryCode;
 [Ljava/util/Locale;
 [Ljava/util/Map$Entry;
 [Ljava/util/TimerTask;
@@ -13056,7 +13197,6 @@
 [Ljava/util/concurrent/ConcurrentHashMap$Node;
 [Ljava/util/concurrent/ConcurrentHashMap$Segment;
 [Ljava/util/concurrent/ForkJoinPool$WorkQueue;
-[Ljava/util/concurrent/ForkJoinTask$ExceptionNode;
 [Ljava/util/concurrent/ForkJoinTask;
 [Ljava/util/concurrent/RunnableScheduledFuture;
 [Ljava/util/concurrent/TimeUnit;
diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes
index 4d22e22..8e47bd5 100644
--- a/build/boot/preloaded-classes
+++ b/build/boot/preloaded-classes
@@ -88,6 +88,7 @@
 com.android.okhttp.HttpUrl
 com.android.okhttp.HttpsHandler
 com.android.okhttp.Interceptor$Chain
+com.android.okhttp.MediaType
 com.android.okhttp.OkCacheContainer
 com.android.okhttp.OkHttpClient$1
 com.android.okhttp.OkHttpClient
@@ -119,8 +120,15 @@
 com.android.okhttp.internal.Util$1
 com.android.okhttp.internal.Util
 com.android.okhttp.internal.Version
+com.android.okhttp.internal.framed.FrameWriter
 com.android.okhttp.internal.framed.FramedConnection$Builder
+com.android.okhttp.internal.framed.FramedConnection$Listener$1
+com.android.okhttp.internal.framed.FramedConnection$Listener
 com.android.okhttp.internal.framed.FramedConnection
+com.android.okhttp.internal.framed.Header
+com.android.okhttp.internal.framed.PushObserver$1
+com.android.okhttp.internal.framed.PushObserver
+com.android.okhttp.internal.framed.Settings
 com.android.okhttp.internal.http.AuthenticatorAdapter
 com.android.okhttp.internal.http.CacheRequest
 com.android.okhttp.internal.http.CacheStrategy$Factory
@@ -227,13 +235,17 @@
 com.android.org.bouncycastle.asn1.ASN1TaggedObject
 com.android.org.bouncycastle.asn1.ASN1TaggedObjectParser
 com.android.org.bouncycastle.asn1.ASN1UTCTime
+com.android.org.bouncycastle.asn1.BERApplicationSpecific
 com.android.org.bouncycastle.asn1.BERApplicationSpecificParser
 com.android.org.bouncycastle.asn1.BEROctetString
 com.android.org.bouncycastle.asn1.BEROctetStringParser
+com.android.org.bouncycastle.asn1.BERSequence
 com.android.org.bouncycastle.asn1.BERSequenceParser
+com.android.org.bouncycastle.asn1.BERSet
 com.android.org.bouncycastle.asn1.BERSetParser
 com.android.org.bouncycastle.asn1.BERTaggedObjectParser
 com.android.org.bouncycastle.asn1.BERTags
+com.android.org.bouncycastle.asn1.ConstructedOctetStream
 com.android.org.bouncycastle.asn1.DERBMPString
 com.android.org.bouncycastle.asn1.DERBitString
 com.android.org.bouncycastle.asn1.DERExternalParser
@@ -260,6 +272,7 @@
 com.android.org.bouncycastle.asn1.DLExternal
 com.android.org.bouncycastle.asn1.DLFactory
 com.android.org.bouncycastle.asn1.DLSequence
+com.android.org.bouncycastle.asn1.DLSet
 com.android.org.bouncycastle.asn1.DefiniteLengthInputStream
 com.android.org.bouncycastle.asn1.InMemoryRepresentable
 com.android.org.bouncycastle.asn1.IndefiniteLengthInputStream
@@ -501,13 +514,16 @@
 dalvik.system.SocketTagger
 dalvik.system.VMDebug
 dalvik.system.VMRuntime$HiddenApiUsageLogger
+dalvik.system.VMRuntime$SdkVersionContainer
 dalvik.system.VMRuntime
 dalvik.system.VMStack
+dalvik.system.ZipPathValidator$1
+dalvik.system.ZipPathValidator$Callback
+dalvik.system.ZipPathValidator
 dalvik.system.ZygoteHooks
 java.awt.font.NumericShaper
 java.awt.font.TextAttribute
 java.io.Bits
-java.io.BufferedInputStream$$ExternalSyntheticBackportWithForwarding0
 java.io.BufferedInputStream
 java.io.BufferedOutputStream
 java.io.BufferedReader
@@ -516,6 +532,7 @@
 java.io.ByteArrayOutputStream
 java.io.CharArrayReader
 java.io.CharArrayWriter
+java.io.CharConversionException
 java.io.Closeable
 java.io.Console
 java.io.DataInput
@@ -601,6 +618,8 @@
 java.io.OptionalDataException
 java.io.OutputStream
 java.io.OutputStreamWriter
+java.io.PipedInputStream
+java.io.PipedOutputStream
 java.io.PrintStream
 java.io.PrintWriter
 java.io.PushbackInputStream
@@ -622,6 +641,8 @@
 java.io.WriteAbortedException
 java.io.Writer
 java.lang.AbstractMethodError
+java.lang.AbstractStringBuilder$$ExternalSyntheticLambda0
+java.lang.AbstractStringBuilder$$ExternalSyntheticLambda1
 java.lang.AbstractStringBuilder
 java.lang.AndroidHardcodedSystemProperties
 java.lang.Appendable
@@ -682,8 +703,6 @@
 java.lang.InheritableThreadLocal
 java.lang.InstantiationError
 java.lang.InstantiationException
-java.lang.Integer$$ExternalSyntheticBackport0
-java.lang.Integer$$ExternalSyntheticBackport1
 java.lang.Integer$IntegerCache
 java.lang.Integer
 java.lang.InternalError
@@ -706,10 +725,11 @@
 java.lang.Object
 java.lang.OutOfMemoryError
 java.lang.Package
-java.lang.Process$PipeInputStream
 java.lang.Process
 java.lang.ProcessBuilder$NullInputStream
 java.lang.ProcessBuilder$NullOutputStream
+java.lang.ProcessBuilder$Redirect$1
+java.lang.ProcessBuilder$Redirect$2
 java.lang.ProcessBuilder$Redirect
 java.lang.ProcessBuilder
 java.lang.ProcessEnvironment$ExternalData
@@ -717,13 +737,6 @@
 java.lang.ProcessEnvironment$Value
 java.lang.ProcessEnvironment$Variable
 java.lang.ProcessEnvironment
-java.lang.ProcessHandleImpl$ExitCompletion
-java.lang.ProcessHandleImpl$Info
-java.lang.ProcessHandleImpl
-java.lang.ProcessImpl$DeferredCloseInputStream
-java.lang.ProcessImpl$DeferredCloseProcessPipeInputStream
-java.lang.ProcessImpl$ProcessPipeInputStream
-java.lang.ProcessImpl$ProcessPipeOutputStream
 java.lang.ProcessImpl
 java.lang.Readable
 java.lang.ReflectiveOperationException
@@ -735,9 +748,18 @@
 java.lang.SecurityManager
 java.lang.Short$ShortCache
 java.lang.Short
+java.lang.StackFrameInfo
 java.lang.StackOverflowError
+java.lang.StackStreamFactory$AbstractStackWalker
+java.lang.StackStreamFactory
 java.lang.StackTraceElement
+java.lang.StackWalker$StackFrame
+java.lang.StackWalker
 java.lang.StrictMath
+java.lang.String$$ExternalSyntheticLambda0
+java.lang.String$$ExternalSyntheticLambda1
+java.lang.String$$ExternalSyntheticLambda2
+java.lang.String$$ExternalSyntheticLambda3
 java.lang.String$CaseInsensitiveComparator-IA
 java.lang.String$CaseInsensitiveComparator
 java.lang.String
@@ -745,8 +767,13 @@
 java.lang.StringBuilder
 java.lang.StringFactory
 java.lang.StringIndexOutOfBoundsException
+java.lang.StringLatin1$CharsSpliterator
+java.lang.StringLatin1$LinesSpliterator
+java.lang.StringLatin1
 java.lang.StringUTF16$CharsSpliterator
+java.lang.StringUTF16$CharsSpliteratorForString
 java.lang.StringUTF16$CodePointsSpliterator
+java.lang.StringUTF16$CodePointsSpliteratorForString
 java.lang.StringUTF16
 java.lang.System$PropertiesWithNonOverrideableDefaults
 java.lang.System
@@ -854,6 +881,7 @@
 java.lang.invoke.Transformers$ReferenceArrayElementSetter
 java.lang.invoke.Transformers$ReferenceIdentity
 java.lang.invoke.Transformers$Spreader
+java.lang.invoke.Transformers$TableSwitch
 java.lang.invoke.Transformers$Transformer
 java.lang.invoke.Transformers$TryFinally
 java.lang.invoke.Transformers$VarargsCollector
@@ -914,6 +942,7 @@
 java.lang.reflect.WildcardType
 java.math.BigDecimal$1
 java.math.BigDecimal$LongOverflow
+java.math.BigDecimal$StringBuilderHelper
 java.math.BigDecimal
 java.math.BigInteger$UnsafeHolder
 java.math.BigInteger
@@ -1103,14 +1132,13 @@
 java.nio.charset.CharsetDecoder
 java.nio.charset.CharsetEncoder
 java.nio.charset.CoderMalfunctionError
-java.nio.charset.CoderResult$1
-java.nio.charset.CoderResult$2
 java.nio.charset.CoderResult$Cache
 java.nio.charset.CoderResult
 java.nio.charset.CodingErrorAction
 java.nio.charset.IllegalCharsetNameException
 java.nio.charset.StandardCharsets
 java.nio.charset.UnsupportedCharsetException
+java.nio.charset.spi.CharsetProvider
 java.nio.file.AccessDeniedException
 java.nio.file.AccessMode
 java.nio.file.CopyMoveHelper
@@ -1124,6 +1152,8 @@
 java.nio.file.FileSystems$DefaultFileSystemHolder$1
 java.nio.file.FileSystems$DefaultFileSystemHolder
 java.nio.file.FileSystems
+java.nio.file.FileVisitResult
+java.nio.file.FileVisitor
 java.nio.file.Files$AcceptAllFilter
 java.nio.file.Files
 java.nio.file.InvalidPathException
@@ -1135,6 +1165,7 @@
 java.nio.file.Paths
 java.nio.file.ProviderMismatchException
 java.nio.file.SecureDirectoryStream
+java.nio.file.SimpleFileVisitor
 java.nio.file.StandardCopyOption
 java.nio.file.StandardOpenOption
 java.nio.file.Watchable
@@ -1176,12 +1207,14 @@
 java.security.KeyPairGenerator
 java.security.KeyPairGeneratorSpi
 java.security.KeyStore$1
+java.security.KeyStore$CallbackHandlerProtection
 java.security.KeyStore$Entry
 java.security.KeyStore$LoadStoreParameter
 java.security.KeyStore$PasswordProtection
 java.security.KeyStore$PrivateKeyEntry
 java.security.KeyStore$ProtectionParameter
 java.security.KeyStore$SecretKeyEntry
+java.security.KeyStore$SimpleLoadStoreParameter
 java.security.KeyStore$TrustedCertificateEntry
 java.security.KeyStore
 java.security.KeyStoreException
@@ -1380,7 +1413,6 @@
 java.time.format.DateTimeFormatterBuilder$$ExternalSyntheticLambda0
 java.time.format.DateTimeFormatterBuilder$1
 java.time.format.DateTimeFormatterBuilder$2
-java.time.format.DateTimeFormatterBuilder$3
 java.time.format.DateTimeFormatterBuilder$CharLiteralPrinterParser
 java.time.format.DateTimeFormatterBuilder$CompositePrinterParser
 java.time.format.DateTimeFormatterBuilder$DateTimePrinterParser
@@ -1389,6 +1421,7 @@
 java.time.format.DateTimeFormatterBuilder$NumberPrinterParser
 java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser
 java.time.format.DateTimeFormatterBuilder$PadPrinterParserDecorator
+java.time.format.DateTimeFormatterBuilder$PrefixTree$CI
 java.time.format.DateTimeFormatterBuilder$PrefixTree
 java.time.format.DateTimeFormatterBuilder$SettingsParser
 java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser
@@ -1471,10 +1504,10 @@
 java.util.ArrayDeque$DescendingIterator
 java.util.ArrayDeque
 java.util.ArrayList$ArrayListSpliterator
-java.util.ArrayList$Itr-IA
 java.util.ArrayList$Itr
 java.util.ArrayList$ListItr
 java.util.ArrayList$SubList$1
+java.util.ArrayList$SubList$2
 java.util.ArrayList$SubList
 java.util.ArrayList
 java.util.ArrayPrefixHelpers$CumulateTask
@@ -1489,14 +1522,7 @@
 java.util.Arrays$ArrayList
 java.util.Arrays$NaturalOrder
 java.util.Arrays
-java.util.ArraysParallelSortHelpers$FJByte$Sorter
-java.util.ArraysParallelSortHelpers$FJChar$Sorter
-java.util.ArraysParallelSortHelpers$FJDouble$Sorter
-java.util.ArraysParallelSortHelpers$FJFloat$Sorter
-java.util.ArraysParallelSortHelpers$FJInt$Sorter
-java.util.ArraysParallelSortHelpers$FJLong$Sorter
 java.util.ArraysParallelSortHelpers$FJObject$Sorter
-java.util.ArraysParallelSortHelpers$FJShort$Sorter
 java.util.Base64$Decoder
 java.util.Base64$Encoder
 java.util.Base64
@@ -1647,13 +1673,13 @@
 java.util.ImmutableCollections$AbstractImmutableMap
 java.util.ImmutableCollections$AbstractImmutableSet
 java.util.ImmutableCollections$List12
+java.util.ImmutableCollections$ListItr
 java.util.ImmutableCollections$ListN
-java.util.ImmutableCollections$Map0
 java.util.ImmutableCollections$Map1
+java.util.ImmutableCollections$MapN$1
+java.util.ImmutableCollections$MapN$MapNIterator
 java.util.ImmutableCollections$MapN
-java.util.ImmutableCollections$Set0
-java.util.ImmutableCollections$Set1
-java.util.ImmutableCollections$Set2
+java.util.ImmutableCollections$Set12
 java.util.ImmutableCollections$SetN
 java.util.ImmutableCollections
 java.util.InputMismatchException
@@ -1671,6 +1697,7 @@
 java.util.LinkedHashMap$LinkedValues
 java.util.LinkedHashMap
 java.util.LinkedHashSet
+java.util.LinkedList$DescendingIterator
 java.util.LinkedList$ListItr
 java.util.LinkedList$Node
 java.util.LinkedList
@@ -1682,6 +1709,10 @@
 java.util.Locale$Cache
 java.util.Locale$Category
 java.util.Locale$FilteringMode
+java.util.Locale$IsoCountryCode$1
+java.util.Locale$IsoCountryCode$2
+java.util.Locale$IsoCountryCode$3
+java.util.Locale$IsoCountryCode
 java.util.Locale$LanguageRange
 java.util.Locale$LocaleKey
 java.util.Locale$NoImagePreloadHolder
@@ -1721,10 +1752,9 @@
 java.util.ResourceBundle$Control$1
 java.util.ResourceBundle$Control$CandidateListCache
 java.util.ResourceBundle$Control
-java.util.ResourceBundle$LoaderReference
+java.util.ResourceBundle$RBClassLoader
 java.util.ResourceBundle$SingleFormatControl
 java.util.ResourceBundle
-java.util.Scanner$1
 java.util.Scanner
 java.util.ServiceConfigurationError
 java.util.ServiceLoader$1
@@ -1779,6 +1809,7 @@
 java.util.TreeMap$Values
 java.util.TreeMap
 java.util.TreeSet
+java.util.Tripwire$$ExternalSyntheticLambda0
 java.util.Tripwire
 java.util.UUID$Holder
 java.util.UUID
@@ -1807,6 +1838,8 @@
 java.util.concurrent.Callable
 java.util.concurrent.CancellationException
 java.util.concurrent.CompletableFuture$AltResult
+java.util.concurrent.CompletableFuture$AsyncRun
+java.util.concurrent.CompletableFuture$AsyncSupply
 java.util.concurrent.CompletableFuture$AsynchronousCompletionTask
 java.util.concurrent.CompletableFuture$Completion
 java.util.concurrent.CompletableFuture$Signaller
@@ -1869,6 +1902,7 @@
 java.util.concurrent.ConcurrentHashMap
 java.util.concurrent.ConcurrentLinkedDeque$Node
 java.util.concurrent.ConcurrentLinkedDeque
+java.util.concurrent.ConcurrentLinkedQueue$$ExternalSyntheticLambda0
 java.util.concurrent.ConcurrentLinkedQueue$Itr
 java.util.concurrent.ConcurrentLinkedQueue$Node
 java.util.concurrent.ConcurrentLinkedQueue
@@ -1907,7 +1941,6 @@
 java.util.concurrent.ForkJoinPool$ManagedBlocker
 java.util.concurrent.ForkJoinPool$WorkQueue
 java.util.concurrent.ForkJoinPool
-java.util.concurrent.ForkJoinTask$ExceptionNode
 java.util.concurrent.ForkJoinTask
 java.util.concurrent.ForkJoinWorkerThread
 java.util.concurrent.Future
@@ -1935,6 +1968,7 @@
 java.util.concurrent.Semaphore$NonfairSync
 java.util.concurrent.Semaphore$Sync
 java.util.concurrent.Semaphore
+java.util.concurrent.SynchronousQueue$TransferQueue$QNode
 java.util.concurrent.SynchronousQueue$TransferQueue
 java.util.concurrent.SynchronousQueue$TransferStack$SNode
 java.util.concurrent.SynchronousQueue$TransferStack
@@ -2015,6 +2049,7 @@
 java.util.function.IntUnaryOperator
 java.util.function.LongBinaryOperator
 java.util.function.LongConsumer
+java.util.function.LongPredicate
 java.util.function.LongSupplier
 java.util.function.LongUnaryOperator
 java.util.function.Predicate
@@ -2038,6 +2073,7 @@
 java.util.jar.JarVerifier
 java.util.jar.Manifest$FastInputStream
 java.util.jar.Manifest
+java.util.logging.ConsoleHandler
 java.util.logging.ErrorManager
 java.util.logging.FileHandler$1
 java.util.logging.FileHandler$InitializationErrorManager
@@ -2157,6 +2193,8 @@
 java.util.stream.IntPipeline$$ExternalSyntheticLambda8
 java.util.stream.IntPipeline$4$1
 java.util.stream.IntPipeline$4
+java.util.stream.IntPipeline$9$1
+java.util.stream.IntPipeline$9
 java.util.stream.IntPipeline$Head
 java.util.stream.IntPipeline$StatelessOp
 java.util.stream.IntPipeline
@@ -2229,6 +2267,7 @@
 java.util.stream.ReferencePipeline$5
 java.util.stream.ReferencePipeline$6$1
 java.util.stream.ReferencePipeline$6
+java.util.stream.ReferencePipeline$7$1
 java.util.stream.ReferencePipeline$7
 java.util.stream.ReferencePipeline$Head
 java.util.stream.ReferencePipeline$StatefulOp
@@ -2273,10 +2312,12 @@
 java.util.stream.Streams
 java.util.stream.TerminalOp
 java.util.stream.TerminalSink
+java.util.stream.Tripwire$$ExternalSyntheticLambda0
 java.util.stream.Tripwire
 java.util.zip.Adler32
 java.util.zip.CRC32
 java.util.zip.CheckedInputStream
+java.util.zip.Checksum$1
 java.util.zip.Checksum
 java.util.zip.DataFormatException
 java.util.zip.Deflater
@@ -2309,6 +2350,7 @@
 javax.crypto.Cipher$SpiAndProviderUpdater
 javax.crypto.Cipher$Transform
 javax.crypto.Cipher
+javax.crypto.CipherInputStream
 javax.crypto.CipherOutputStream
 javax.crypto.CipherSpi
 javax.crypto.CryptoPermissions
@@ -2394,6 +2436,9 @@
 javax.net.ssl.X509KeyManager
 javax.net.ssl.X509TrustManager
 javax.security.auth.Destroyable
+javax.security.auth.callback.Callback
+javax.security.auth.callback.CallbackHandler
+javax.security.auth.callback.PasswordCallback
 javax.security.auth.callback.UnsupportedCallbackException
 javax.security.auth.x500.X500Principal
 javax.security.cert.Certificate
@@ -2404,6 +2449,7 @@
 javax.xml.datatype.DatatypeConstants$Field
 javax.xml.datatype.DatatypeConstants
 javax.xml.datatype.Duration
+javax.xml.namespace.QName
 javax.xml.parsers.DocumentBuilder
 javax.xml.parsers.DocumentBuilderFactory
 javax.xml.parsers.ParserConfigurationException
@@ -2418,6 +2464,8 @@
 jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer
 jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer
 jdk.internal.math.FloatingDecimal
+jdk.internal.math.FormattedFloatingDecimal$1
+jdk.internal.math.FormattedFloatingDecimal$2
 jdk.internal.math.FormattedFloatingDecimal$Form
 jdk.internal.math.FormattedFloatingDecimal
 jdk.internal.misc.JavaObjectInputStreamAccess
@@ -2435,7 +2483,6 @@
 libcore.content.type.MimeMap
 libcore.icu.CollationKeyICU
 libcore.icu.DateIntervalFormat
-libcore.icu.DateUtilsBridge
 libcore.icu.DecimalFormatData
 libcore.icu.ICU
 libcore.icu.LocaleData
@@ -2508,17 +2555,24 @@
 org.apache.harmony.xml.ExpatException
 org.apache.harmony.xml.ExpatParser$CurrentAttributes
 org.apache.harmony.xml.ExpatParser$ExpatLocator
+org.apache.harmony.xml.ExpatParser$ParseException
 org.apache.harmony.xml.ExpatParser
 org.apache.harmony.xml.ExpatReader
+org.apache.harmony.xml.dom.AttrImpl
+org.apache.harmony.xml.dom.CDATASectionImpl
 org.apache.harmony.xml.dom.CharacterDataImpl
+org.apache.harmony.xml.dom.CommentImpl
 org.apache.harmony.xml.dom.DOMImplementationImpl
 org.apache.harmony.xml.dom.DocumentImpl
+org.apache.harmony.xml.dom.DocumentTypeImpl
 org.apache.harmony.xml.dom.ElementImpl
+org.apache.harmony.xml.dom.EntityReferenceImpl
 org.apache.harmony.xml.dom.InnerNodeImpl
 org.apache.harmony.xml.dom.LeafNodeImpl
 org.apache.harmony.xml.dom.NodeImpl$1
 org.apache.harmony.xml.dom.NodeImpl
 org.apache.harmony.xml.dom.NodeListImpl
+org.apache.harmony.xml.dom.ProcessingInstructionImpl
 org.apache.harmony.xml.dom.TextImpl
 org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl
 org.apache.harmony.xml.parsers.DocumentBuilderImpl
@@ -2532,15 +2586,20 @@
 org.json.JSONStringer$Scope
 org.json.JSONStringer
 org.json.JSONTokener
+org.w3c.dom.Attr
+org.w3c.dom.CDATASection
 org.w3c.dom.CharacterData
+org.w3c.dom.Comment
 org.w3c.dom.DOMException
 org.w3c.dom.DOMImplementation
 org.w3c.dom.Document
 org.w3c.dom.DocumentFragment
 org.w3c.dom.DocumentType
 org.w3c.dom.Element
+org.w3c.dom.EntityReference
 org.w3c.dom.Node
 org.w3c.dom.NodeList
+org.w3c.dom.ProcessingInstruction
 org.w3c.dom.Text
 org.w3c.dom.TypeInfo
 org.xml.sax.AttributeList
@@ -2556,6 +2615,7 @@
 org.xml.sax.SAXException
 org.xml.sax.SAXNotRecognizedException
 org.xml.sax.SAXNotSupportedException
+org.xml.sax.SAXParseException
 org.xml.sax.XMLFilter
 org.xml.sax.XMLReader
 org.xml.sax.ext.DeclHandler
@@ -2564,6 +2624,7 @@
 org.xml.sax.ext.LexicalHandler
 org.xml.sax.helpers.AttributesImpl
 org.xml.sax.helpers.DefaultHandler
+org.xml.sax.helpers.LocatorImpl
 org.xml.sax.helpers.NamespaceSupport
 org.xml.sax.helpers.XMLFilterImpl
 org.xmlpull.v1.XmlPullParser
@@ -2584,7 +2645,6 @@
 sun.misc.JavaIOFileDescriptorAccess
 sun.misc.LRUCache
 sun.misc.SharedSecrets
-sun.misc.Unsafe$$ExternalSyntheticBackportWithForwarding0
 sun.misc.Unsafe
 sun.misc.VM
 sun.misc.Version
@@ -2752,6 +2812,7 @@
 sun.security.util.AbstractAlgorithmConstraints$1
 sun.security.util.AbstractAlgorithmConstraints
 sun.security.util.AlgorithmDecomposer
+sun.security.util.AnchorCertificates$1
 sun.security.util.AnchorCertificates
 sun.security.util.BitArray
 sun.security.util.ByteArrayLexOrder
@@ -2784,6 +2845,8 @@
 sun.security.util.MemoryCache$SoftCacheEntry
 sun.security.util.MemoryCache
 sun.security.util.ObjectIdentifier
+sun.security.util.Resources
+sun.security.util.ResourcesMgr$1
 sun.security.util.ResourcesMgr
 sun.security.util.SecurityConstants
 sun.security.util.SignatureFileVerifier
@@ -2859,6 +2922,7 @@
 sun.util.calendar.BaseCalendar$Date
 sun.util.calendar.BaseCalendar
 sun.util.calendar.CalendarDate
+sun.util.calendar.CalendarSystem$GregorianHolder
 sun.util.calendar.CalendarSystem
 sun.util.calendar.CalendarUtils
 sun.util.calendar.Era
@@ -2884,6 +2948,7 @@
 sun.util.locale.ParseStatus
 sun.util.locale.StringTokenIterator
 sun.util.locale.UnicodeLocaleExtension
+sun.util.locale.provider.CalendarDataUtility
 sun.util.logging.LoggingProxy
 sun.util.logging.LoggingSupport$1
 sun.util.logging.LoggingSupport$2
@@ -2901,21 +2966,26 @@
 [I
 [J
 [Landroid.system.StructCapUserData;
+[Landroid.system.StructIfaddrs;
 [Landroid.system.StructPollfd;
 [Lcom.android.okhttp.CipherSuite;
 [Lcom.android.okhttp.ConnectionSpec;
 [Lcom.android.okhttp.HttpUrl$Builder$ParseResult;
 [Lcom.android.okhttp.Protocol;
 [Lcom.android.okhttp.TlsVersion;
+[Lcom.android.okhttp.okio.ByteString;
 [Lcom.android.org.bouncycastle.asn1.ASN1Encodable;
+[Lcom.android.org.bouncycastle.asn1.ASN1Enumerated;
 [Lcom.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
 [Lcom.android.org.bouncycastle.asn1.ASN1OctetString;
+[Lcom.android.org.bouncycastle.asn1.ASN1Primitive;
 [Lcom.android.org.bouncycastle.crypto.params.DHParameters;
 [Lcom.android.org.bouncycastle.crypto.params.DSAParameters;
 [Lcom.android.org.bouncycastle.jcajce.provider.asymmetric.x509.PEMUtil$Boundaries;
 [Lcom.android.org.kxml2.io.KXmlParser$ValueContext;
 [Ldalvik.system.DexPathList$Element;
 [Ldalvik.system.DexPathList$NativeLibraryElement;
+[Ljava.io.Closeable;
 [Ljava.io.File$PathStatus;
 [Ljava.io.File;
 [Ljava.io.FileDescriptor;
@@ -2925,6 +2995,7 @@
 [Ljava.io.ObjectStreamClass$MemberSignature;
 [Ljava.io.ObjectStreamField;
 [Ljava.io.Serializable;
+[Ljava.lang.Boolean;
 [Ljava.lang.Byte;
 [Ljava.lang.CharSequence;
 [Ljava.lang.Character$UnicodeBlock;
@@ -2933,10 +3004,12 @@
 [Ljava.lang.ClassLoader;
 [Ljava.lang.Comparable;
 [Ljava.lang.Daemons$Daemon;
+[Ljava.lang.Double;
 [Ljava.lang.Enum;
 [Ljava.lang.Float;
 [Ljava.lang.Integer;
 [Ljava.lang.Long;
+[Ljava.lang.Number;
 [Ljava.lang.Object;
 [Ljava.lang.Package;
 [Ljava.lang.Runnable;
@@ -2989,7 +3062,9 @@
 [Ljava.security.ProtectionDomain;
 [Ljava.security.Provider;
 [Ljava.security.cert.CRLReason;
+[Ljava.security.cert.CertPathValidatorException$BasicReason;
 [Ljava.security.cert.Certificate;
+[Ljava.security.cert.PKIXReason;
 [Ljava.security.cert.PKIXRevocationChecker$Option;
 [Ljava.security.cert.X509CRL;
 [Ljava.security.cert.X509Certificate;
@@ -3021,13 +3096,13 @@
 [Ljava.util.Comparators$NaturalOrderComparator;
 [Ljava.util.Enumeration;
 [Ljava.util.Formatter$Flags;
-[Ljava.util.Formatter$FormatString;
 [Ljava.util.HashMap$Node;
 [Ljava.util.HashMap;
 [Ljava.util.Hashtable$HashtableEntry;
 [Ljava.util.List;
 [Ljava.util.Locale$Category;
 [Ljava.util.Locale$FilteringMode;
+[Ljava.util.Locale$IsoCountryCode;
 [Ljava.util.Locale;
 [Ljava.util.Map$Entry;
 [Ljava.util.TimerTask;
@@ -3037,7 +3112,6 @@
 [Ljava.util.concurrent.ConcurrentHashMap$Node;
 [Ljava.util.concurrent.ConcurrentHashMap$Segment;
 [Ljava.util.concurrent.ForkJoinPool$WorkQueue;
-[Ljava.util.concurrent.ForkJoinTask$ExceptionNode;
 [Ljava.util.concurrent.ForkJoinTask;
 [Ljava.util.concurrent.RunnableScheduledFuture;
 [Ljava.util.concurrent.TimeUnit;
@@ -3056,9 +3130,11 @@
 [Ljavax.net.ssl.SSLEngineResult$HandshakeStatus;
 [Ljavax.net.ssl.SSLEngineResult$Status;
 [Ljavax.net.ssl.TrustManager;
+[Ljavax.security.auth.callback.Callback;
 [Ljavax.security.auth.x500.X500Principal;
 [Ljavax.security.cert.X509Certificate;
 [Ljdk.internal.math.FDBigInteger;
+[Ljdk.internal.math.FormattedFloatingDecimal$Form;
 [Llibcore.io.ClassPathURLStreamHandler;
 [Llibcore.io.IoTracker$Mode;
 [Llibcore.reflect.AnnotationMember$DefaultValues;
@@ -3087,6 +3163,7 @@
 [Z
 [[B
 [[C
+[[D
 [[F
 [[I
 [[J
diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h
index 936d290..c47efe1 100644
--- a/cmdline/detail/cmdline_parse_argument_detail.h
+++ b/cmdline/detail/cmdline_parse_argument_detail.h
@@ -485,6 +485,7 @@
 
       // Error case: Fail, telling the user what the allowed values were.
       std::vector<std::string> allowed_values;
+      allowed_values.reserve(argument_info_.names_.size());
       for (auto&& arg_name : argument_info_.names_) {
         allowed_values.push_back(arg_name);
       }
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 9ee532a..0099ba3 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -74,6 +74,7 @@
                                     bool use_fd = false) {
     std::unique_ptr<File> oat_file;
     std::vector<std::string> args;
+    args.reserve(dex_locations.size() + extra_args.size() + 6);
     // Add dex file args.
     for (const std::string& dex_location : dex_locations) {
       args.push_back("--dex-file=" + dex_location);
diff --git a/dex2oat/verifier_deps_test.cc b/dex2oat/verifier_deps_test.cc
index 6a2deba..00593f5 100644
--- a/dex2oat/verifier_deps_test.cc
+++ b/dex2oat/verifier_deps_test.cc
@@ -502,6 +502,7 @@
   std::vector<std::unique_ptr<const DexFile>> first_dex_files = OpenTestDexFiles("VerifierDeps");
   std::vector<std::unique_ptr<const DexFile>> second_dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files;
+  dex_files.reserve(first_dex_files.size() + second_dex_files.size());
   for (auto& dex_file : first_dex_files) {
     dex_files.push_back(dex_file.get());
   }
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index ed2ff72..e3310e9 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -169,7 +169,7 @@
   // Store value->key so that we can use the default sort from pair which
   // sorts by value first and then key
   std::vector<std::pair<V, K>> value_key_vector;
-
+  value_key_vector.reserve(map.size());
   for (const auto& kv_pair : map) {
     value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
   }
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index 1aba547..8e634ff 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -611,6 +611,7 @@
   result.stage = ForkAndExecResult::kLink;
 
   std::vector<const char*> c_args;
+  c_args.reserve(argv.size() + 1);
   for (const std::string& str : argv) {
     c_args.push_back(str.c_str());
   }
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index 8432be5..ee98c04 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -117,26 +117,27 @@
 };
 
 // Names come from PackageManagerServiceCompilerMapping.java
-#define REASON_NAME_LIST(V) \
-  V(kError, "error") \
-  V(kUnknown, "unknown") \
-  V(kFirstBoot, "first-boot") \
-  V(kBootAfterOTA, "boot-after-ota") \
-  V(kPostBoot, "post-boot") \
-  V(kInstall, "install") \
-  V(kInstallFast, "install-fast") \
-  V(kInstallBulk, "install-bulk") \
-  V(kInstallBulkSecondary, "install-bulk-secondary") \
-  V(kInstallBulkDowngraded, "install-bulk-downgraded") \
+#define REASON_NAME_LIST(V)                                               \
+  V(kError, "error")                                                      \
+  V(kUnknown, "unknown")                                                  \
+  V(kFirstBoot, "first-boot")                                             \
+  V(kBootAfterOTA, "boot-after-ota")                                      \
+  V(kPostBoot, "post-boot")                                               \
+  V(kInstall, "install")                                                  \
+  V(kInstallFast, "install-fast")                                         \
+  V(kInstallBulk, "install-bulk")                                         \
+  V(kInstallBulkSecondary, "install-bulk-secondary")                      \
+  V(kInstallBulkDowngraded, "install-bulk-downgraded")                    \
   V(kInstallBulkSecondaryDowngraded, "install-bulk-secondary-downgraded") \
-  V(kBgDexopt, "bg-dexopt") \
-  V(kABOTA, "ab-ota") \
-  V(kInactive, "inactive") \
-  V(kShared, "shared") \
-  V(kInstallWithDexMetadata, "install-with-dex-metadata") \
-  V(kPrebuilt, "prebuilt") \
-  V(kCmdLine, "cmdline") \
-  V(kVdex, "vdex")
+  V(kBgDexopt, "bg-dexopt")                                               \
+  V(kABOTA, "ab-ota")                                                     \
+  V(kInactive, "inactive")                                                \
+  V(kShared, "shared")                                                    \
+  V(kInstallWithDexMetadata, "install-with-dex-metadata")                 \
+  V(kPrebuilt, "prebuilt")                                                \
+  V(kCmdLine, "cmdline")                                                  \
+  V(kVdex, "vdex")                                                        \
+  V(kBootAfterMainlineUpdate, "boot-after-mainline-update")
 
 // We log compilation reasons as part of the metadata we report. Since elsewhere compilation reasons
 // are specified as a string, we define them as an enum here which indicates the reasons that we
diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc
index 2732088..6c4aa95 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -321,6 +321,11 @@
               CompilationReason::kPrebuilt);
 static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kCmdLine)) ==
               CompilationReason::kCmdLine);
+static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kVdex)) ==
+              CompilationReason::kVdex);
+static_assert(
+    CompilationReasonFromName(CompilationReasonName(CompilationReason::kBootAfterMainlineUpdate)) ==
+    CompilationReason::kBootAfterMainlineUpdate);
 
 }  // namespace metrics
 }  // namespace art
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index dff90b1..b17acf5 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -739,6 +739,8 @@
             CompilationReason::kError);
   ASSERT_EQ(CompilationReasonFromName("vdex"),
             CompilationReason::kVdex);
+  ASSERT_EQ(CompilationReasonFromName("boot-after-mainline-update"),
+            CompilationReason::kBootAfterMainlineUpdate);
 }
 
 TEST(CompilerReason, Name) {
@@ -780,6 +782,8 @@
             "error");
   ASSERT_EQ(CompilationReasonName(CompilationReason::kVdex),
             "vdex");
+  ASSERT_EQ(CompilationReasonName(CompilationReason::kBootAfterMainlineUpdate),
+            "boot-after-mainline-update");
 }
 }  // namespace metrics
 }  // namespace art
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 97f928e..a34e9e8 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -60,13 +60,60 @@
     ],
 }
 
+java_defaults {
+    name: "service-art-defaults",
+    defaults: [
+        "framework-system-server-module-defaults",
+    ],
+    sdk_version: "system_server_current",
+    min_sdk_version: "31",
+    srcs: [
+        "java/**/*.java",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "auto_value_annotations",
+        // TODO(b/256866172): Transitive dependency, for r8 only.
+        "framework-statsd.stubs.module_lib",
+        // TODO(b/256866172): Transitive dependency, for r8 only. This module
+        // always refers to the jar in prebuilts/sdk. We can't use
+        // "framework-connectivity.stubs.module_lib" here because it's not
+        // available on master-art.
+        "sdk_module-lib_current_framework-connectivity",
+    ],
+    static_libs: [
+        "art-statslog-art-java",
+        "artd-aidl-java",
+        "modules-utils-package-state",
+        "modules-utils-shell-command-handler",
+        "service-art-proto-java",
+    ],
+    plugins: [
+        "auto_value_plugin",
+    ],
+}
+
+// Used by tests to allow tests to mock the right classes.
+java_library {
+    name: "service-art-pre-jarjar",
+    defaults: ["service-art-defaults"],
+    installable: false,
+    visibility: [
+        "//visibility:override",
+        "//visibility:private",
+    ],
+}
+
 // Provides the API and implementation of the ART Service class that will be
 // loaded by the System Server.
 java_sdk_library {
     // This target is named 'service-art' to conform to the naming conventions
     // for JAR files in the System Server.
     name: "service-art",
-    defaults: ["framework-system-server-module-defaults"],
+    defaults: [
+        "service-art-defaults",
+        "framework-system-server-module-optimize-defaults",
+    ],
     permitted_packages: ["com.android.server.art"],
     visibility: [
         "//art:__subpackages__",
@@ -76,14 +123,49 @@
         "com.android.art",
         "com.android.art.debug",
     ],
+    jarjar_rules: "jarjar-rules.txt",
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
+
+java_library {
+    name: "service-art-proto-java",
+    proto: {
+        type: "lite",
+    },
+    srcs: [
+        "proto/**/*.proto",
+    ],
     sdk_version: "system_server_current",
     min_sdk_version: "31",
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+java_library {
+    name: "art-statslog-art-java",
     srcs: [
-        "java/**/*.java",
+        ":art-statslog-art-java-gen",
     ],
-    static_libs: [
+    libs: [
+        "framework-statsd.stubs.module_lib",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    sdk_version: "system_server_current",
+    min_sdk_version: "31",
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
+genrule {
+    name: "art-statslog-art-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module art --javaPackage com.android.server.art --javaClass ArtStatsLog",
+    out: ["java/com/android/server/art/ArtStatsLog.java"],
 }
 
 art_cc_defaults {
@@ -126,12 +208,31 @@
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
         "androidx.test.runner",
-        "mockito-target-minus-junit4",
-        "service-art.impl",
+        "artd-aidl-java",
+        "framework-annotations-lib",
+        // We need ExtendedMockito to mock static methods.
+        "mockito-target-extended-minus-junit4",
+        "modules-utils-package-state",
+        "service-art-pre-jarjar",
+        // Statically link against system server to allow us to mock system
+        // server APIs. This won't work on master-art, but it's fine because we
+        // don't run this test on master-art.
+        "services.core",
     ],
 
-    sdk_version: "system_server_current",
+    jni_libs: [
+        // The two libraries below are required by ExtendedMockito.
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    compile_multilib: "both",
+
+    // TODO: This module should move to sdk_version: "system_server_current" when possible,
+    //   as this will restrict the APIs available to just that expected system API. For now,
+    //   a compileOnly / runtimeOnly split for dependencies doesn't exist in the build system
+    //   and so it's not possible to enforce.
     min_sdk_version: "31",
 
     test_suites: ["general-tests"],
+    test_config: "ArtServiceTests.xml",
 }
diff --git a/libartservice/service/AndroidManifest.xml b/libartservice/service/AndroidManifest.xml
index 921bde9..1c13fc6 100644
--- a/libartservice/service/AndroidManifest.xml
+++ b/libartservice/service/AndroidManifest.xml
@@ -20,7 +20,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.art.tests">
 
-    <application android:label="ArtServiceTests">
+    <!-- android:debuggable is required by ExtendedMockito. -->
+    <application android:label="ArtServiceTests" android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/libartservice/service/ArtServiceTests.xml b/libartservice/service/ArtServiceTests.xml
new file mode 100644
index 0000000..7a47ca3
--- /dev/null
+++ b/libartservice/service/ArtServiceTests.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Config for ART Services test cases">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- This test needs access to system APIs for mainline modules. -->
+    <option name="hidden-api-checks" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ArtServiceTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.art.tests"/>
+    </test>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index c7844e0..31a56da 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -2,7 +2,184 @@
 package com.android.server.art {
 
   public final class ArtManagerLocal {
-    ctor public ArtManagerLocal();
+    ctor @Deprecated public ArtManagerLocal();
+    ctor public ArtManagerLocal(@NonNull android.content.Context);
+    method public void addDexoptDoneCallback(boolean, @NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback);
+    method public void cancelBackgroundDexoptJob();
+    method @NonNull public void clearAppProfiles(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method public void clearBatchDexoptStartCallback();
+    method public void clearScheduleBackgroundDexoptJobCallback();
+    method @NonNull public com.android.server.art.model.DeleteResult deleteDexoptArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams);
+    method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams, @NonNull android.os.CancellationSignal);
+    method public void dump(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot);
+    method public void dumpPackage(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, int);
+    method public int handleShellCommand(@NonNull android.os.Binder, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]);
+    method public void onBoot(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<com.android.server.art.model.OperationProgress>);
+    method public void printShellCommandHelp(@NonNull java.io.PrintWriter);
+    method public void removeDexoptDoneCallback(@NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback);
+    method public int scheduleBackgroundDexoptJob();
+    method public void setBatchDexoptStartCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback);
+    method public void setScheduleBackgroundDexoptJobCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback);
+    method @NonNull public android.os.ParcelFileDescriptor snapshotAppProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @Nullable String) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+    method @NonNull public android.os.ParcelFileDescriptor snapshotBootImageProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+    method public void startBackgroundDexoptJob();
+    method public void unscheduleBackgroundDexoptJob();
+  }
+
+  public static interface ArtManagerLocal.BatchDexoptStartCallback {
+    method public void onBatchDexoptStart(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull com.android.server.art.model.BatchDexoptParams.Builder, @NonNull android.os.CancellationSignal);
+  }
+
+  public static interface ArtManagerLocal.DexoptDoneCallback {
+    method public void onDexoptDone(@NonNull com.android.server.art.model.DexoptResult);
+  }
+
+  public static interface ArtManagerLocal.ScheduleBackgroundDexoptJobCallback {
+    method public void onOverrideJobInfo(@NonNull android.app.job.JobInfo.Builder);
+  }
+
+  public static class ArtManagerLocal.SnapshotProfileException extends java.lang.Exception {
+    ctor public ArtManagerLocal.SnapshotProfileException(@NonNull Throwable);
+  }
+
+  public class ArtModuleServiceInitializer {
+    method public static void setArtModuleServiceManager(@NonNull android.os.ArtModuleServiceManager);
+  }
+
+  public class DexUseManagerLocal {
+    method @NonNull public static com.android.server.art.DexUseManagerLocal createInstance(@NonNull android.content.Context);
+    method @NonNull public java.util.List<com.android.server.art.model.DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(@NonNull String);
+    method public void notifyDexContainersLoaded(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>);
+    method public void systemReady();
+  }
+
+  public class ReasonMapping {
+    field public static final String REASON_BG_DEXOPT = "bg-dexopt";
+    field public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update";
+    field public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota";
+    field public static final String REASON_CMDLINE = "cmdline";
+    field public static final String REASON_FIRST_BOOT = "first-boot";
+    field public static final String REASON_INACTIVE = "inactive";
+    field public static final String REASON_INSTALL = "install";
+    field public static final String REASON_INSTALL_BULK = "install-bulk";
+    field public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded";
+    field public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary";
+    field public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = "install-bulk-secondary-downgraded";
+    field public static final String REASON_INSTALL_FAST = "install-fast";
+  }
+
+}
+
+package com.android.server.art.model {
+
+  public class ArtFlags {
+    method public static int defaultGetStatusFlags();
+    field public static final int FLAG_FORCE = 16; // 0x10
+    field public static final int FLAG_FOR_PRIMARY_DEX = 1; // 0x1
+    field public static final int FLAG_FOR_SECONDARY_DEX = 2; // 0x2
+    field public static final int FLAG_FOR_SINGLE_SPLIT = 32; // 0x20
+    field public static final int FLAG_SHOULD_DOWNGRADE = 8; // 0x8
+    field public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 4; // 0x4
+    field public static final int FLAG_SKIP_IF_STORAGE_LOW = 64; // 0x40
+    field public static final int PRIORITY_BACKGROUND = 40; // 0x28
+    field public static final int PRIORITY_BOOT = 100; // 0x64
+    field public static final int PRIORITY_INTERACTIVE = 60; // 0x3c
+    field public static final int PRIORITY_INTERACTIVE_FAST = 80; // 0x50
+    field public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2; // 0x2
+    field public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1; // 0x1
+    field public static final int SCHEDULE_SUCCESS = 0; // 0x0
+  }
+
+  public abstract class BatchDexoptParams {
+    method @NonNull public abstract com.android.server.art.model.DexoptParams getDexoptParams();
+    method @NonNull public abstract java.util.List<java.lang.String> getPackages();
+  }
+
+  public static final class BatchDexoptParams.Builder {
+    method @NonNull public com.android.server.art.model.BatchDexoptParams build();
+    method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setDexoptParams(@NonNull com.android.server.art.model.DexoptParams);
+    method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setPackages(@NonNull java.util.List<java.lang.String>);
+  }
+
+  public abstract class DeleteResult {
+    method public abstract long getFreedBytes();
+  }
+
+  public abstract class DexContainerFileUseInfo {
+    method @NonNull public abstract String getDexContainerFile();
+    method @NonNull public abstract java.util.Set<java.lang.String> getLoadingPackages();
+    method @NonNull public abstract android.os.UserHandle getUserHandle();
+  }
+
+  public class DexoptParams {
+    method @NonNull public String getCompilerFilter();
+    method public int getFlags();
+    method public int getPriorityClass();
+    method @NonNull public String getReason();
+    method @Nullable public String getSplitName();
+    field public static final String COMPILER_FILTER_NOOP = "skip";
+  }
+
+  public static final class DexoptParams.Builder {
+    ctor public DexoptParams.Builder(@NonNull String);
+    ctor public DexoptParams.Builder(@NonNull String, int);
+    method @NonNull public com.android.server.art.model.DexoptParams build();
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setCompilerFilter(@NonNull String);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int, int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setPriorityClass(int);
+    method @NonNull public com.android.server.art.model.DexoptParams.Builder setSplitName(@Nullable String);
+  }
+
+  public abstract class DexoptResult {
+    method public int getFinalStatus();
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.PackageDexoptResult> getPackageDexoptResults();
+    method @NonNull public abstract String getReason();
+    method @NonNull public abstract String getRequestedCompilerFilter();
+    field public static final int DEXOPT_CANCELLED = 40; // 0x28
+    field public static final int DEXOPT_FAILED = 30; // 0x1e
+    field public static final int DEXOPT_PERFORMED = 20; // 0x14
+    field public static final int DEXOPT_SKIPPED = 10; // 0xa
+  }
+
+  public abstract static class DexoptResult.DexContainerFileDexoptResult {
+    method @NonNull public abstract String getAbi();
+    method @NonNull public abstract String getActualCompilerFilter();
+    method public abstract long getDex2oatCpuTimeMillis();
+    method public abstract long getDex2oatWallTimeMillis();
+    method @NonNull public abstract String getDexContainerFile();
+    method public abstract long getSizeBeforeBytes();
+    method public abstract long getSizeBytes();
+    method public abstract int getStatus();
+    method public abstract boolean isPrimaryAbi();
+  }
+
+  public abstract static class DexoptResult.PackageDexoptResult {
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult> getDexContainerFileDexoptResults();
+    method @NonNull public abstract String getPackageName();
+    method public int getStatus();
+    method public boolean hasUpdatedArtifacts();
+  }
+
+  public abstract class DexoptStatus {
+    method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses();
+  }
+
+  public abstract static class DexoptStatus.DexContainerFileDexoptStatus {
+    method @NonNull public abstract String getAbi();
+    method @NonNull public abstract String getCompilationReason();
+    method @NonNull public abstract String getCompilerFilter();
+    method @NonNull public abstract String getDexContainerFile();
+    method @NonNull public abstract String getLocationDebugString();
+    method public abstract boolean isPrimaryAbi();
+    method public abstract boolean isPrimaryDex();
+  }
+
+  public abstract class OperationProgress {
+    method public int getPercentage();
   }
 
 }
diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt
index c7d39e6..54ff0a1 100644
--- a/libartservice/service/jarjar-rules.txt
+++ b/libartservice/service/jarjar-rules.txt
@@ -1,2 +1,3 @@
 # Repackages static libraries to make them private to ART Services.
 rule com.android.modules.utils.** com.android.server.art.jarjar.@0
+rule com.google.protobuf.** com.android.server.art.jarjar.@0
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
new file mode 100644
index 0000000..f38000d
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.ProfilePath.PrebuiltProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryCurProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryRefProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryCurProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryRefProfilePath;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.ProfilePath.WritableProfilePath;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** @hide */
+public final class AidlUtils {
+    private AidlUtils() {}
+
+    @NonNull
+    public static ArtifactsPath buildArtifactsPath(
+            @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) {
+        var artifactsPath = new ArtifactsPath();
+        artifactsPath.dexPath = dexPath;
+        artifactsPath.isa = isa;
+        artifactsPath.isInDalvikCache = isInDalvikCache;
+        return artifactsPath;
+    }
+
+    @NonNull
+    public static FsPermission buildFsPermission(
+            int uid, int gid, boolean isOtherReadable, boolean isOtherExecutable) {
+        var fsPermission = new FsPermission();
+        fsPermission.uid = uid;
+        fsPermission.gid = gid;
+        fsPermission.isOtherReadable = isOtherReadable;
+        fsPermission.isOtherExecutable = isOtherExecutable;
+        return fsPermission;
+    }
+
+    @NonNull
+    public static FsPermission buildFsPermission(int uid, int gid, boolean isOtherReadable) {
+        return buildFsPermission(uid, gid, isOtherReadable, false /* isOtherExecutable */);
+    }
+
+    @NonNull
+    public static DexMetadataPath buildDexMetadataPath(@NonNull String dexPath) {
+        var dexMetadataPath = new DexMetadataPath();
+        dexMetadataPath.dexPath = dexPath;
+        return dexMetadataPath;
+    }
+
+    @NonNull
+    public static PermissionSettings buildPermissionSettings(@NonNull FsPermission dirFsPermission,
+            @NonNull FsPermission fileFsPermission, @Nullable SeContext seContext) {
+        var permissionSettings = new PermissionSettings();
+        permissionSettings.dirFsPermission = dirFsPermission;
+        permissionSettings.fileFsPermission = fileFsPermission;
+        permissionSettings.seContext = seContext;
+        return permissionSettings;
+    }
+
+    @NonNull
+    public static OutputArtifacts buildOutputArtifacts(@NonNull String dexPath, @NonNull String isa,
+            boolean isInDalvikCache, @NonNull PermissionSettings permissionSettings) {
+        var outputArtifacts = new OutputArtifacts();
+        outputArtifacts.artifactsPath = buildArtifactsPath(dexPath, isa, isInDalvikCache);
+        outputArtifacts.permissionSettings = permissionSettings;
+        return outputArtifacts;
+    }
+
+    @NonNull
+    public static PrimaryRefProfilePath buildPrimaryRefProfilePath(
+            @NonNull String packageName, @NonNull String profileName) {
+        var primaryRefProfilePath = new PrimaryRefProfilePath();
+        primaryRefProfilePath.packageName = packageName;
+        primaryRefProfilePath.profileName = profileName;
+        return primaryRefProfilePath;
+    }
+
+    @NonNull
+    public static SecondaryRefProfilePath buildSecondaryRefProfilePath(@NonNull String dexPath) {
+        var secondaryRefProfilePath = new SecondaryRefProfilePath();
+        secondaryRefProfilePath.dexPath = dexPath;
+        return secondaryRefProfilePath;
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrimaryRef(
+            @NonNull String packageName, @NonNull String profileName) {
+        return ProfilePath.primaryRefProfilePath(
+                buildPrimaryRefProfilePath(packageName, profileName));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrebuilt(@NonNull String dexPath) {
+        var prebuiltProfilePath = new PrebuiltProfilePath();
+        prebuiltProfilePath.dexPath = dexPath;
+        return ProfilePath.prebuiltProfilePath(prebuiltProfilePath);
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForDm(@NonNull String dexPath) {
+        return ProfilePath.dexMetadataPath(buildDexMetadataPath(dexPath));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrimaryCur(
+            int userId, @NonNull String packageName, @NonNull String profileName) {
+        var primaryCurProfilePath = new PrimaryCurProfilePath();
+        primaryCurProfilePath.userId = userId;
+        primaryCurProfilePath.packageName = packageName;
+        primaryCurProfilePath.profileName = profileName;
+        return ProfilePath.primaryCurProfilePath(primaryCurProfilePath);
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForSecondaryRef(@NonNull String dexPath) {
+        return ProfilePath.secondaryRefProfilePath(buildSecondaryRefProfilePath(dexPath));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForSecondaryCur(@NonNull String dexPath) {
+        var secondaryCurProfilePath = new SecondaryCurProfilePath();
+        secondaryCurProfilePath.dexPath = dexPath;
+        return ProfilePath.secondaryCurProfilePath(secondaryCurProfilePath);
+    }
+
+    @NonNull
+    private static OutputProfile buildOutputProfile(
+            @NonNull WritableProfilePath finalPath, int uid, int gid, boolean isPublic) {
+        var outputProfile = new OutputProfile();
+        outputProfile.profilePath = new TmpProfilePath();
+        outputProfile.profilePath.finalPath = finalPath;
+        outputProfile.profilePath.id = ""; // Will be filled by artd.
+        outputProfile.profilePath.tmpPath = ""; // Will be filled by artd.
+        outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic);
+        return outputProfile;
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForPrimary(@NonNull String packageName,
+            @NonNull String profileName, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(WritableProfilePath.forPrimary(
+                                          buildPrimaryRefProfilePath(packageName, profileName)),
+                uid, gid, isPublic);
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForSecondary(
+            @NonNull String dexPath, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(
+                WritableProfilePath.forSecondary(buildSecondaryRefProfilePath(dexPath)), uid, gid,
+                isPublic);
+    }
+
+    @NonNull
+    public static SeContext buildSeContext(@NonNull String seInfo, int uid) {
+        var seContext = new SeContext();
+        seContext.seInfo = seInfo;
+        seContext.uid = uid;
+        return seContext;
+    }
+
+    @NonNull
+    public static String toString(@NonNull PrimaryRefProfilePath profile) {
+        return String.format(
+                "[packageName = %s, profileName = %s]", profile.packageName, profile.profileName);
+    }
+
+    @NonNull
+    public static String toString(@NonNull SecondaryRefProfilePath profile) {
+        return String.format("[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull WritableProfilePath profile) {
+        switch (profile.getTag()) {
+            case WritableProfilePath.forPrimary:
+                return toString(profile.getForPrimary());
+            case WritableProfilePath.forSecondary:
+                return toString(profile.getForSecondary());
+            default:
+                throw new IllegalStateException(
+                        "Unknown WritableProfilePath tag " + profile.getTag());
+        }
+    }
+
+    @NonNull
+    public static String toString(@NonNull ProfilePath profile) {
+        switch (profile.getTag()) {
+            case ProfilePath.primaryRefProfilePath:
+                return toString(profile.getPrimaryRefProfilePath());
+            case ProfilePath.secondaryRefProfilePath:
+                return toString(profile.getSecondaryRefProfilePath());
+            default:
+                throw new UnsupportedOperationException(
+                        "Only reference profile paths are supported to be converted to string, got "
+                        + profile.getTag());
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 64aec7b..5066a9e 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,16 +16,1092 @@
 
 package com.android.server.art;
 
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.ReasonMapping.BatchDexoptReason;
+import static com.android.server.art.ReasonMapping.BootReason;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.GetStatusFlags;
+import static com.android.server.art.model.ArtFlags.ScheduleStatus;
+import static com.android.server.art.model.Config.Callback;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.R;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.job.JobInfo;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.BatchDexoptParams;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * This class provides a system API for functionality provided by the ART module.
  *
+ * Note: Although this class is the entry point of ART services, this class is not a {@link
+ * SystemService}, and it does not publish a binder. Instead, it is a module loaded by the
+ * system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry}
+ * specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal}
+ * for consistency.
+ *
  * @hide
  */
 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public final class ArtManagerLocal {
     private static final String TAG = "ArtService";
+    private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
+            "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
 
-    public ArtManagerLocal() {}
+    /** @hide */
+    @VisibleForTesting public static final long DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES = 500_000_000;
+
+    @NonNull private final Injector mInjector;
+
+    @Deprecated
+    public ArtManagerLocal() {
+        mInjector = new Injector(this, null /* context */);
+    }
+
+    /**
+     * Creates an instance.
+     *
+     * Only {@code SystemServer} should create an instance and register it in {@link
+     * LocalManagerRegistry}. Other API users should obtain the instance from {@link
+     * LocalManagerRegistry}.
+     *
+     * @param context the system server context
+     * @throws NullPointerException if required dependencies are missing
+     */
+    public ArtManagerLocal(@NonNull Context context) {
+        mInjector = new Injector(this, context);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public ArtManagerLocal(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /**
+     * Handles ART Service commands, which is a subset of `cmd package` commands.
+     *
+     * Note: This method is not an override of {@link Binder#handleShellCommand} because ART
+     * services does not publish a binder. Instead, it handles the commands forwarded by the
+     * `package` service. The semantics of the parameters are the same as {@link
+     * Binder#handleShellCommand}.
+     *
+     * @return zero on success, non-zero on internal error (e.g., I/O error)
+     * @throws SecurityException if the caller is not root
+     * @throws IllegalArgumentException if the arguments are illegal
+     * @see ArtShellCommand#printHelp(PrintWriter)
+     */
+    public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        return new ArtShellCommand(
+                this, mInjector.getPackageManagerLocal(), mInjector.getDexUseManager())
+                .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
+                        err.getFileDescriptor(), args);
+    }
+
+    /** Prints ART Service shell command help. */
+    public void printShellCommandHelp(@NonNull PrintWriter pw) {
+        ArtShellCommand.printHelp(pw);
+    }
+
+    /**
+     * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the
+     * ones for secondary dex files. This includes VDEX, ODEX, and ART files.
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @NonNull
+    public DeleteResult deleteDexoptArtifacts(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            long freedBytes = 0;
+
+            boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
+            for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                for (Abi abi : Utils.getAllAbis(pkgState)) {
+                    freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
+                            dexInfo.dexPath(), abi.isa(), isInDalvikCache));
+                }
+            }
+
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                    freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
+                            dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */));
+                }
+            }
+
+            return DeleteResult.create(freedBytes);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("An error occurred when calling artd", e);
+        }
+    }
+
+    /**
+     * Returns the dexopt status of a package.
+     *
+     * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}).
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @NonNull
+    public DexoptStatus getDexoptStatus(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        return getDexoptStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
+    }
+
+    /**
+     * Same as above, but allows to specify flags.
+     *
+     * @see #getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)
+     */
+    @NonNull
+    public DexoptStatus getDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @GetStatusFlags int flags) {
+        if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+                && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
+            throw new IllegalArgumentException("Nothing to check");
+        }
+
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
+
+            if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+                for (DetailedPrimaryDexInfo dexInfo :
+                        PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+                    if (!dexInfo.hasCode()) {
+                        continue;
+                    }
+                    for (Abi abi : Utils.getAllAbis(pkgState)) {
+                        try {
+                            GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                                    dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+                            statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                                    true /* isPrimaryDex */, abi.isPrimaryAbi(), abi.name(),
+                                    result.compilerFilter, result.compilationReason,
+                                    result.locationDebugString));
+                        } catch (ServiceSpecificException e) {
+                            statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                                    true /* isPrimaryDex */, abi.isPrimaryAbi(), abi.name(),
+                                    "error", "error", e.getMessage()));
+                        }
+                    }
+                }
+            }
+
+            if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+                for (SecondaryDexInfo dexInfo :
+                        mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                    for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                        try {
+                            GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                                    dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+                            statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                                    false /* isPrimaryDex */, abi.isPrimaryAbi(), abi.name(),
+                                    result.compilerFilter, result.compilationReason,
+                                    result.locationDebugString));
+                        } catch (ServiceSpecificException e) {
+                            statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                                    false /* isPrimaryDex */, abi.isPrimaryAbi(), abi.name(),
+                                    "error", "error", e.getMessage()));
+                        }
+                    }
+                }
+            }
+
+            return DexoptStatus.create(statuses);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("An error occurred when calling artd", e);
+        }
+    }
+
+    /**
+     * Clears the profiles of the given app that are collected locally, including the profiles for
+     * primary dex files and the ones for secondary dex files. More specifically, it clears
+     * reference profiles and current profiles. External profiles (e.g., cloud profiles) will be
+     * kept.
+     *
+     * @throws IllegalArgumentException if the package is not found or the flags are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @NonNull
+    public void clearAppProfiles(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                if (!dexInfo.hasCode()) {
+                    continue;
+                }
+                mInjector.getArtd().deleteProfile(
+                        PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+                for (ProfilePath profile : PrimaryDexUtils.getCurProfiles(
+                             mInjector.getUserManager(), pkgState, dexInfo)) {
+                    mInjector.getArtd().deleteProfile(profile);
+                }
+            }
+
+            // This only deletes the profiles of known secondary dex files. If there are unknown
+            // secondary dex files, their profiles will be deleted by `cleanup`.
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                mInjector.getArtd().deleteProfile(
+                        AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
+                mInjector.getArtd().deleteProfile(
+                        AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("An error occurred when calling artd", e);
+        }
+    }
+
+    /**
+     * Dexopts a package. The time this operation takes ranges from a few milliseconds to several
+     * minutes, depending on the params and the code size of the package.
+     *
+     * When this operation ends (either completed or cancelled), callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
+     *
+     * @throws IllegalArgumentException if the package is not found or the params are illegal
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    @NonNull
+    public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull DexoptParams params) {
+        var cancellationSignal = new CancellationSignal();
+        return dexoptPackage(snapshot, packageName, params, cancellationSignal);
+    }
+
+    /**
+     * Same as above, but supports cancellation.
+     *
+     * @see #dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, DexoptParams)
+     */
+    @NonNull
+    public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        return mInjector.getDexoptHelper().dexopt(
+                snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
+    }
+
+    /**
+     * Resets the dexopt state of the package as if the package is newly installed.
+     *
+     * More specifically, it clears reference profiles, current profiles, and any code compiled from
+     * those local profiles. If there is an external profile (e.g., a cloud profile), the code
+     * compiled from that profile will be kept.
+     *
+     * For secondary dex files, it also clears all dexopt artifacts.
+     *
+     * @hide
+     */
+    @NonNull
+    public DexoptResult resetDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull CancellationSignal cancellationSignal) {
+        // We must delete the artifacts for primary dex files beforehand rather than relying on
+        // `dexoptPackage` to replace them because:
+        // - If dexopt is not needed after the deletion, then we shouldn't run dexopt at all. For
+        //   example, when we have a DM file that contains a VDEX file but doesn't contain a cloud
+        //   profile, this happens. Note that this is more about correctness rather than
+        //   performance.
+        // - We don't want the existing artifacts to affect dexopt. For example, the existing VDEX
+        //   file should not be an input VDEX.
+        //
+        // We delete the artifacts for secondary dex files and `dexoptPackage` won't re-generate
+        // them because `dexoptPackage` for `REASON_INSTALL` is for primary dex only. This is
+        // intentional because secondary dex files are supposed to be unknown at install time.
+        deleteDexoptArtifacts(snapshot, packageName);
+        clearAppProfiles(snapshot, packageName);
+
+        // Re-generate artifacts for primary dex files if needed.
+        return dexoptPackage(snapshot, packageName,
+                new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(), cancellationSignal);
+    }
+
+    /**
+     * Runs batch dexopt for the given reason.
+     *
+     * This is called by ART Service automatically during boot / background dexopt.
+     *
+     * The list of packages and options are determined by {@code reason}, and can be overridden by
+     * {@link #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}.
+     *
+     * The dexopt is done in a thread pool. The number of packages being dexopted
+     * simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency}
+     * (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code
+     * dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads}
+     * (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of
+     * concurrent threads is the product of the two system properties. Note that the physical core
+     * usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of
+     * threads.
+     *
+     * When this operation ends (either completed or cancelled), callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
+     *
+     * If the storage is nearly low, and {@code reason} is {@link ReasonMapping#REASON_BG_DEXOPT},
+     * it may also downgrade some inactive packages to a less optimized compiler filter, specified
+     * by the system property {@code pm.dexopt.inactive} (typically "verify"), to free up some
+     * space. This feature is only enabled when the system property {@code
+     * pm.dexopt.downgrade_after_inactive_days} is set. The space threshold to trigger this feature
+     * is the Storage Manager's low space threshold plus {@link
+     * #DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES}. The concurrency can be configured by system property
+     * {@code pm.dexopt.bg-dexopt.concurrency}. The packages in the list provided by
+     * {@link BatchDexoptStartCallback} for {@link ReasonMapping#REASON_BG_DEXOPT} are never
+     * downgraded.
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @param reason determines the default list of packages and options
+     * @param cancellationSignal provides the ability to cancel this operation
+     * @param processCallbackExecutor the executor to call {@code progressCallback}
+     * @param progressCallback called repeatedly whenever there is an update on the progress
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error), or the callback set by {@link
+     *         #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} provides invalid
+     *         params.
+     *
+     * @hide
+     */
+    @NonNull
+    public DexoptResult dexoptPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull @BatchDexoptReason String reason,
+            @NonNull CancellationSignal cancellationSignal,
+            @Nullable @CallbackExecutor Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        List<String> defaultPackages =
+                Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
+        DexoptParams defaultDexoptParams = new DexoptParams.Builder(reason).build();
+        var builder = new BatchDexoptParams.Builder(defaultPackages, defaultDexoptParams);
+        Callback<BatchDexoptStartCallback, Void> callback =
+                mInjector.getConfig().getBatchDexoptStartCallback();
+        if (callback != null) {
+            Utils.executeAndWait(callback.executor(), () -> {
+                callback.get().onBatchDexoptStart(
+                        snapshot, reason, defaultPackages, builder, cancellationSignal);
+            });
+        }
+        BatchDexoptParams params = builder.build();
+        Utils.check(params.getDexoptParams().getReason().equals(reason));
+
+        ExecutorService dexoptExecutor =
+                Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason));
+        try {
+            if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
+                maybeDowngradePackages(snapshot,
+                        new HashSet<>(params.getPackages()) /* excludedPackages */,
+                        cancellationSignal, dexoptExecutor);
+            }
+            Log.i(TAG, "Dexopting packages");
+            return mInjector.getDexoptHelper().dexopt(snapshot, params.getPackages(),
+                    params.getDexoptParams(), cancellationSignal, dexoptExecutor,
+                    progressCallbackExecutor, progressCallback);
+        } finally {
+            dexoptExecutor.shutdown();
+        }
+    }
+
+    /**
+     * Overrides the default params for {@link #dexoptPackages}. This method is thread-safe.
+     *
+     * This method gives users the opportunity to change the behavior of {@link #dexoptPackages},
+     * which is called by ART Service automatically during boot / background dexopt.
+     *
+     * If this method is not called, the default list of packages and options determined by {@code
+     * reason} will be used.
+     */
+    public void setBatchDexoptStartCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull BatchDexoptStartCallback callback) {
+        mInjector.getConfig().setBatchDexoptStartCallback(executor, callback);
+    }
+
+    /**
+     * Clears the callback set by {@link
+     * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}. This method is
+     * thread-safe.
+     */
+    public void clearBatchDexoptStartCallback() {
+        mInjector.getConfig().clearBatchDexoptStartCallback();
+    }
+
+    /**
+     * Schedules a background dexopt job. Does nothing if the job is already scheduled.
+     *
+     * Use this method if you want the system to automatically determine the best time to run
+     * dexopt.
+     *
+     * The job will be run by the job scheduler. The job scheduling configuration can be overridden
+     * by {@link
+     * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. By
+     * default, it runs periodically (at most once a day) when all the following constraints are
+     * meet.
+     *
+     * <ul>
+     *   <li>The device is idling. (see {@link JobInfo.Builder#setRequiresDeviceIdle(boolean)})
+     *   <li>The device is charging. (see {@link JobInfo.Builder#setRequiresCharging(boolean)})
+     *   <li>The battery level is not low.
+     *     (see {@link JobInfo.Builder#setRequiresBatteryNotLow(boolean)})
+     * </ul>
+     *
+     * When the job is running, it may be cancelled by the job scheduler immediately whenever one of
+     * the constraints above is no longer met or cancelled by the {@link
+     * #cancelBackgroundDexoptJob()} API. The job scheduler retries it in the next <i>maintenance
+     * window</i>. For information about <i>maintenance window</i>, see
+     * https://developer.android.com/training/monitoring-device-state/doze-standby.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior of the job.
+     *
+     * When the job ends (either completed or cancelled), the result is sent to the callbacks added
+     * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
+     * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+     */
+    public @ScheduleStatus int scheduleBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob().schedule();
+    }
+
+    /**
+     * Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}.
+     * Does nothing if the job is not scheduled.
+     *
+     * Use this method if you no longer want the system to automatically run dexopt.
+     *
+     * If the job is already started by the job scheduler and is running, it will be cancelled
+     * immediately, and the result sent to the callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
+     * DexoptResult#DEXOPT_CANCELLED}. Note that a job started by {@link
+     * #startBackgroundDexoptJob()} will not be cancelled by this method.
+     */
+    public void unscheduleBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().unschedule();
+    }
+
+    /**
+     * Overrides the configuration of the background dexopt job. This method is thread-safe.
+     */
+    public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+        mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback);
+    }
+
+    /**
+     * Clears the callback set by {@link
+     * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. This
+     * method is thread-safe.
+     */
+    public void clearScheduleBackgroundDexoptJobCallback() {
+        mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback();
+    }
+
+    /**
+     * Manually starts a background dexopt job. Does nothing if a job is already started by this
+     * method or by the job scheduler. This method is not blocking.
+     *
+     * Unlike the job started by job scheduler, the job started by this method does not respect
+     * constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be
+     * cancelled when they aren't met.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior of the job.
+     *
+     * When the job ends (either completed or cancelled), the result is sent to the callbacks added
+     * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the
+     * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+     */
+    public void startBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().start();
+    }
+
+    /**
+     * Cancels the running background dexopt job started by the job scheduler or by {@link
+     * #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not
+     * blocking.
+     *
+     * The result sent to the callbacks added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link
+     * DexoptResult#DEXOPT_CANCELLED}.
+     */
+    public void cancelBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().cancel();
+    }
+
+    /**
+     * Adds a global listener that listens to any result of dexopting package(s), no matter run
+     * manually or automatically. Calling this method multiple times with different callbacks is
+     * allowed. Callbacks are executed in the same order as the one in which they were added. This
+     * method is thread-safe.
+     *
+     * @param onlyIncludeUpdates if true, the results passed to the callback will only contain
+     *         packages that have any update, and the callback won't be called with results that
+     *         don't have any update.
+     * @throws IllegalStateException if the same callback instance is already added
+     */
+    public void addDexoptDoneCallback(boolean onlyIncludeUpdates,
+            @NonNull @CallbackExecutor Executor executor, @NonNull DexoptDoneCallback callback) {
+        mInjector.getConfig().addDexoptDoneCallback(onlyIncludeUpdates, executor, callback);
+    }
+
+    /**
+     * Removes the listener added by {@link
+     * #addDexoptDoneCallback(Executor, DexoptDoneCallback)}. Does nothing if the
+     * callback was not added. This method is thread-safe.
+     */
+    public void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) {
+        mInjector.getConfig().removeDexoptDoneCallback(callback);
+    }
+
+    /**
+     * Snapshots the profile of the given app split. The profile snapshot is the aggregation of all
+     * existing profiles of the app split (all current user profiles and the reference profile).
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @param packageName the name of the app that owns the profile
+     * @param splitName see {@link AndroidPackageSplit#getName()}
+     * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+     *         caller is responsible for closing it. Note that the content may be empty.
+     * @throws IllegalArgumentException if the package or the split is not found
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     * @throws SnapshotProfileException if the operation encounters an error that the caller should
+     *         handle (e.g., an I/O error, a sub-process crash).
+     */
+    @NonNull
+    public ParcelFileDescriptor snapshotAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName) throws SnapshotProfileException {
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
+    }
+
+    /**
+     * Same as above, but outputs in text format.
+     *
+     * @hide
+     */
+    @NonNull
+    public ParcelFileDescriptor dumpAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName, boolean dumpClassesAndMethods)
+            throws SnapshotProfileException {
+        var options = new MergeProfileOptions();
+        options.dumpOnly = !dumpClassesAndMethods;
+        options.dumpClassesAndMethods = dumpClassesAndMethods;
+        return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options);
+    }
+
+    @NonNull
+    private ParcelFileDescriptor snapshotOrDumpAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName, @NonNull MergeProfileOptions options)
+            throws SnapshotProfileException {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
+
+        List<ProfilePath> profiles = new ArrayList<>();
+        profiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+        profiles.addAll(
+                PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
+
+        OutputProfile output = PrimaryDexUtils.buildOutputProfile(
+                pkgState, dexInfo, Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
+
+        return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options);
+    }
+
+    /**
+     * Snapshots the boot image profile
+     * (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot
+     * is the aggregation of all existing profiles on the device (all current user profiles and
+     * reference profiles) of all apps and the system server filtered by applicable classpaths.
+     *
+     * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+     * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+     *         caller is responsible for closing it. Note that the content may be empty.
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     * @throws SnapshotProfileException if the operation encounters an error that the caller should
+     *         handle (e.g., an I/O error, a sub-process crash).
+     */
+    @NonNull
+    public ParcelFileDescriptor snapshotBootImageProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        List<ProfilePath> profiles = new ArrayList<>();
+
+        // System server profiles.
+        profiles.add(AidlUtils.buildProfilePathForPrimaryRef(
+                Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
+        for (UserHandle handle :
+                mInjector.getUserManager().getUserHandles(true /* excludeDying */)) {
+            profiles.add(AidlUtils.buildProfilePathForPrimaryCur(handle.getIdentifier(),
+                    Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY));
+        }
+
+        // App profiles.
+        snapshot.getPackageStates().forEach((packageName, appPkgState) -> {
+            // Hibernating apps can still provide useful profile contents, so skip the hibernation
+            // check.
+            if (Utils.canDexoptPackage(appPkgState, null /* appHibernationManager */)) {
+                AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState);
+                for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) {
+                    if (!appDexInfo.hasCode()) {
+                        continue;
+                    }
+                    profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo));
+                    profiles.addAll(PrimaryDexUtils.getCurProfiles(
+                            mInjector.getUserManager(), appPkgState, appDexInfo));
+                }
+            }
+        });
+
+        OutputProfile output = AidlUtils.buildOutputProfileForPrimary(Utils.PLATFORM_PACKAGE_NAME,
+                PrimaryDexUtils.PROFILE_PRIMARY, Process.SYSTEM_UID, Process.SYSTEM_UID,
+                false /* isPublic */);
+
+        List<String> dexPaths = Arrays.stream(CLASSPATHS_FOR_BOOT_IMAGE_PROFILE)
+                                        .map(envVar -> Constants.getenv(envVar))
+                                        .filter(classpath -> !TextUtils.isEmpty(classpath))
+                                        .flatMap(classpath -> Arrays.stream(classpath.split(":")))
+                                        .collect(Collectors.toList());
+
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = true;
+        return mergeProfilesAndGetFd(profiles, output, dexPaths, options);
+    }
+
+    /**
+     * Notifies ART Service that this is a boot that falls into one of the categories listed in
+     * {@link BootReason}. The current behavior is that ART Service goes through all recently used
+     * packages and dexopts those that are not dexopted. This might change in the future.
+     *
+     * This method is blocking. It takes about 30 seconds to a few minutes. During execution, {@code
+     * progressCallback} is repeatedly called whenever there is an update on the progress.
+     *
+     * See {@link #dexoptPackages} for how to customize the behavior.
+     */
+    public void onBoot(@NonNull @BootReason String bootReason,
+            @Nullable @CallbackExecutor Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
+            dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor,
+                    progressCallback);
+        }
+    }
+
+    /**
+     * Dumps the dexopt state of all packages in text format for debugging purposes.
+     *
+     * There are no stability guarantees for the output format.
+     *
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    public void dump(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        new DumpHelper(this).dump(pw, snapshot);
+    }
+
+    /**
+     * Dumps the dexopt state of the given package in text format for debugging purposes.
+     *
+     * There are no stability guarantees for the output format.
+     *
+     * @throws IllegalArgumentException if the package is not found
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     */
+    public void dumpPackage(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        new DumpHelper(this).dumpPackage(
+                pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
+    }
+
+    /**
+     * Should be used by {@link BackgroundDexoptJobService} ONLY.
+     *
+     * @hide
+     */
+    @NonNull
+    BackgroundDexoptJob getBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob();
+    }
+
+    private void maybeDowngradePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Executor executor) {
+        if (shouldDowngrade()) {
+            List<String> packages = getDefaultPackages(snapshot, ReasonMapping.REASON_INACTIVE)
+                                            .stream()
+                                            .filter(pkg -> !excludedPackages.contains(pkg))
+                                            .collect(Collectors.toList());
+            if (!packages.isEmpty()) {
+                Log.i(TAG, "Storage is low. Downgrading inactive packages");
+                mInjector.getDexoptHelper().dexopt(snapshot, packages,
+                        new DexoptParams.Builder(ReasonMapping.REASON_INACTIVE).build(),
+                        cancellationSignal, executor, null /* processCallbackExecutor */,
+                        null /* progressCallback */);
+            } else {
+                Log.i(TAG,
+                        "Storage is low, but downgrading is disabled or there's nothing to "
+                                + "downgrade");
+            }
+        }
+    }
+
+    private boolean shouldDowngrade() {
+        try {
+            return mInjector.getStorageManager().getAllocatableBytes(StorageManager.UUID_DEFAULT)
+                    < DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES;
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+            return false;
+        }
+    }
+
+    /** Returns the list of packages to process for the given reason. */
+    @NonNull
+    private List<String> getDefaultPackages(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String reason) {
+        // We probably won't have an app hibernation manager in the boot time compilation, because
+        // ArtManagerLocal.onBoot needs to run early to ensure apps are compiled before the system
+        // server fires them up. This means the boot time compilation will ignore the hibernation
+        // states of the packages.
+        //
+        // TODO(b/265782156): When hibernated packages get compiled this way, the file GC will
+        // delete them again in the next background dexopt run. That means they are likely to get
+        // recreated again in the next boot dexopt (i.e. for OTA or Mainline update).
+        var appHibernationManager = mInjector.getAppHibernationManager();
+        if (reason != ReasonMapping.REASON_FIRST_BOOT
+                && reason != ReasonMapping.REASON_BOOT_AFTER_OTA
+                && reason != ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE) {
+            // Check that it's present for other compilation reasons, to ensure we don't regress
+            // silently.
+            Objects.requireNonNull(appHibernationManager);
+        }
+
+        // Filter out hibernating packages even if the reason is REASON_INACTIVE. This is because
+        // artifacts for hibernating packages are already deleted.
+        Stream<PackageState> packages = snapshot.getPackageStates().values().stream().filter(
+                pkgState -> Utils.canDexoptPackage(pkgState, appHibernationManager));
+
+        switch (reason) {
+            case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                packages = packages.filter(
+                        pkgState -> mInjector.isSystemUiPackage(pkgState.getPackageName()));
+                break;
+            case ReasonMapping.REASON_INACTIVE:
+                packages = filterAndSortByLastActiveTime(
+                        packages, false /* keepRecent */, false /* descending */);
+                break;
+            default:
+                // Actually, the sorting is only needed for background dexopt, but we do it for all
+                // cases for simplicity.
+                packages = filterAndSortByLastActiveTime(
+                        packages, true /* keepRecent */, true /* descending */);
+        }
+
+        return packages.map(PackageState::getPackageName).collect(Collectors.toList());
+    }
+
+    @NonNull
+    private Stream<PackageState> filterAndSortByLastActiveTime(
+            @NonNull Stream<PackageState> packages, boolean keepRecent, boolean descending) {
+        // "pm.dexopt.downgrade_after_inactive_days" is repurposed to also determine whether to
+        // dexopt a package.
+        long inactiveMs = TimeUnit.DAYS.toMillis(SystemProperties.getInt(
+                "pm.dexopt.downgrade_after_inactive_days", Integer.MAX_VALUE /* def */));
+        long currentTimeMs = mInjector.getCurrentTimeMillis();
+        long thresholdTimeMs = currentTimeMs - inactiveMs;
+        return packages
+                .map(pkgState
+                        -> Pair.create(pkgState,
+                                Utils.getPackageLastActiveTime(pkgState,
+                                        mInjector.getDexUseManager(), mInjector.getUserManager())))
+                .filter(keepRecent ? (pair -> pair.second > thresholdTimeMs)
+                                   : (pair -> pair.second <= thresholdTimeMs))
+                .sorted(descending ? Comparator.comparingLong(pair -> - pair.second)
+                                   : Comparator.comparingLong(pair -> pair.second))
+                .map(pair -> pair.first);
+    }
+
+    @NonNull
+    private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles,
+            @NonNull OutputProfile output, @NonNull List<String> dexPaths,
+            @NonNull MergeProfileOptions options) throws SnapshotProfileException {
+        try {
+            boolean hasContent = false;
+            try {
+                hasContent = mInjector.getArtd().mergeProfiles(
+                        profiles, null /* referenceProfile */, output, dexPaths, options);
+            } catch (ServiceSpecificException e) {
+                throw new SnapshotProfileException(e);
+            }
+
+            String path = hasContent ? output.profilePath.tmpPath : "/dev/null";
+            ParcelFileDescriptor fd;
+            try {
+                fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
+            } catch (FileNotFoundException e) {
+                throw new IllegalStateException(
+                        String.format("Failed to open profile snapshot '%s'", path), e);
+            }
+
+            if (hasContent) {
+                // This is done on the open file so that only the FD keeps a reference to its
+                // contents.
+                mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
+            }
+
+            return fd;
+        } catch (RemoteException e) {
+            throw new IllegalStateException("An error occurred when calling artd", e);
+        }
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public interface BatchDexoptStartCallback {
+        /**
+         * Mutates {@code builder} to override the default params for {@link #dexoptPackages}. It
+         * must ignore unknown reasons because more reasons may be added in the future.
+         *
+         * This is called before the start of any automatic package dexopt (i.e., not
+         * including package dexopt initiated by the {@link #dexoptPackage} API call).
+         *
+         * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
+         * list of packages to dexopt.
+         *
+         * If {@code builder.setDexoptParams} is not called, the default params built from {@code
+         * new DexoptParams.Builder(reason)} will to used as the params for dexopting each
+         * package.
+         *
+         * Additionally, {@code cancellationSignal.cancel()} can be called to cancel this operation.
+         * If this operation is initiated by the job scheduler and the {@code reason} is {@link
+         * ReasonMapping#REASON_BG_DEXOPT}, the job will be retried in the next <i>maintenance
+         * window</i>. For information about <i>maintenance window</i>, see
+         * https://developer.android.com/training/monitoring-device-state/doze-standby.
+         *
+         * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
+         * when {@link #dexoptPackages} is called.
+         */
+        void onBatchDexoptStart(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+                @NonNull @BatchDexoptReason String reason, @NonNull List<String> defaultPackages,
+                @NonNull BatchDexoptParams.Builder builder,
+                @NonNull CancellationSignal cancellationSignal);
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public interface ScheduleBackgroundDexoptJobCallback {
+        /**
+         * Mutates {@code builder} to override the configuration of the background dexopt job.
+         *
+         * The default configuration described in {@link
+         * ArtManagerLocal#scheduleBackgroundDexoptJob()} is passed to the callback as the {@code
+         * builder} argument.
+         *
+         * Setting {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)} is not allowed. Doing
+         * so will result in {@link IllegalStateException} when {@link
+         * #scheduleBackgroundDexoptJob()} is called. ART Service has its own storage check, which
+         * skips package dexopt when the storage is low. The storage check is enabled by
+         * default for background dexopt jobs. {@link
+         * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} can be used to disable
+         * the storage check by clearing the {@link ArtFlags#FLAG_SKIP_IF_STORAGE_LOW} flag.
+         */
+        void onOverrideJobInfo(@NonNull JobInfo.Builder builder);
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public interface DexoptDoneCallback {
+        void onDexoptDone(@NonNull DexoptResult result);
+    }
+
+    /**
+     * Represents an error that happens when snapshotting profiles.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public static class SnapshotProfileException extends Exception {
+        public SnapshotProfileException(@NonNull Throwable cause) {
+            super(cause);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @Nullable private final Context mContext;
+        @Nullable private final PackageManagerLocal mPackageManagerLocal;
+        @Nullable private final Config mConfig;
+        @Nullable private final BackgroundDexoptJob mBgDexoptJob;
+
+        Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
+            mContext = context;
+            if (context != null) {
+                // We only need them on Android U and above, where a context is passed.
+                mPackageManagerLocal = Objects.requireNonNull(
+                        LocalManagerRegistry.getManager(PackageManagerLocal.class));
+                mConfig = new Config();
+                mBgDexoptJob = new BackgroundDexoptJob(context, artManagerLocal, mConfig);
+
+                // Call the getters for the dependencies that aren't optional, to ensure correct
+                // initialization order.
+                getDexoptHelper();
+                getUserManager();
+                getDexUseManager();
+                getStorageManager();
+                ArtModuleServiceInitializer.getArtModuleServiceManager();
+            } else {
+                mPackageManagerLocal = null;
+                mConfig = null;
+                mBgDexoptJob = null;
+            }
+        }
+
+        @NonNull
+        public Context getContext() {
+            return Objects.requireNonNull(mContext);
+        }
+
+        @NonNull
+        public PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(mPackageManagerLocal);
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        @NonNull
+        public DexoptHelper getDexoptHelper() {
+            return new DexoptHelper(getContext(), getConfig());
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        /**
+         * Returns the registered AppHibernationManager instance.
+         *
+         * It may be null because ArtManagerLocal needs to be available early to compile packages at
+         * boot with {@link onBoot}, before the hibernation manager has been initialized. It should
+         * not be null for other dexopt calls.
+         */
+        @Nullable
+        public AppHibernationManager getAppHibernationManager() {
+            return mContext.getSystemService(AppHibernationManager.class);
+        }
+
+        @NonNull
+        public BackgroundDexoptJob getBackgroundDexoptJob() {
+            return Objects.requireNonNull(mBgDexoptJob);
+        }
+
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @NonNull
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return packageName.equals(mContext.getString(R.string.config_systemUi));
+        }
+
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+    }
 }
diff --git a/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
new file mode 100644
index 0000000..e6d789a
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.ArtModuleServiceManager;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * Class for performing registration for the ART mainline module.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtModuleServiceInitializer {
+    private ArtModuleServiceInitializer() {}
+
+    @NonNull private static Object sLock = new Object();
+    @GuardedBy("sLock") @Nullable private static ArtModuleServiceManager sArtModuleServiceManager;
+
+    /**
+     * Sets an instance of {@link ArtModuleServiceManager} that allows the ART mainline module to
+     * obtain ART binder services. This is called by the platform during the system server
+     * initialization.
+     */
+    public static void setArtModuleServiceManager(
+            @NonNull ArtModuleServiceManager artModuleServiceManager) {
+        synchronized (sLock) {
+            if (sArtModuleServiceManager != null) {
+                throw new IllegalStateException("ArtModuleServiceManager is already set");
+            }
+            sArtModuleServiceManager = artModuleServiceManager;
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    public static ArtModuleServiceManager getArtModuleServiceManager() {
+        synchronized (sLock) {
+            return Objects.requireNonNull(sArtModuleServiceManager);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
new file mode 100644
index 0000000..6fd1dc5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.ArtManagerLocal.SnapshotProfileException;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import libcore.io.Streams;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * This class handles ART shell commands.
+ *
+ * @hide
+ */
+public final class ArtShellCommand extends BasicShellCommandHandler {
+    private static final String TAG = "ArtShellCommand";
+
+    /** The default location for profile dumps. */
+    private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman";
+
+    private final ArtManagerLocal mArtManagerLocal;
+    private final PackageManagerLocal mPackageManagerLocal;
+    private final DexUseManagerLocal mDexUseManager;
+
+    @GuardedBy("sCancellationSignalMap")
+    @NonNull
+    private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
+
+    public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
+            @NonNull PackageManagerLocal packageManagerLocal,
+            @NonNull DexUseManagerLocal dexUseManager) {
+        mArtManagerLocal = artManagerLocal;
+        mPackageManagerLocal = packageManagerLocal;
+        mDexUseManager = dexUseManager;
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Override
+    public int onCommand(String cmd) {
+        PrintWriter pw = getOutPrintWriter();
+        try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) {
+            switch (cmd) {
+                case "compile":
+                case "reconcile-secondary-dex-files":
+                case "force-dex-opt":
+                case "bg-dexopt-job":
+                case "cancel-bg-dexopt-job":
+                case "delete-dexopt":
+                case "dump-profiles":
+                case "snapshot-profile":
+                    // TODO(b/263247832): Implement this.
+                    throw new UnsupportedOperationException();
+                case "art":
+                    return handleArtCommand(pw, snapshot);
+                default:
+                    // Can't happen. Only supported commands are forwarded to ART Service.
+                    throw new IllegalArgumentException(
+                            String.format("Unexpected command '%s' forwarded to ART Service", cmd));
+            }
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private int handleArtCommand(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        enforceRoot();
+        String subcmd = getNextArgRequired();
+        switch (subcmd) {
+            case "delete-dexopt-artifacts": {
+                DeleteResult result =
+                        mArtManagerLocal.deleteDexoptArtifacts(snapshot, getNextArgRequired());
+                pw.printf("Freed %d bytes\n", result.getFreedBytes());
+                return 0;
+            }
+            case "get-dexopt-status": {
+                DexoptStatus dexoptStatus = mArtManagerLocal.getDexoptStatus(
+                        snapshot, getNextArgRequired(), ArtFlags.defaultGetStatusFlags());
+                pw.println(dexoptStatus);
+                return 0;
+            }
+            case "dexopt-package": {
+                var paramsBuilder = new DexoptParams.Builder("cmdline");
+                String opt;
+                @DexoptFlags int scopeFlags = 0;
+                boolean forSingleSplit = false;
+                boolean reset = false;
+                while ((opt = getNextOption()) != null) {
+                    switch (opt) {
+                        case "-m":
+                            paramsBuilder.setCompilerFilter(getNextArgRequired());
+                            break;
+                        case "-f":
+                            paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
+                            break;
+                        case "--primary-dex":
+                            scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
+                            break;
+                        case "--secondary-dex":
+                            scopeFlags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
+                            break;
+                        case "--include-dependencies":
+                            scopeFlags |= ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+                            break;
+                        case "--split":
+                            String splitName = getNextArgRequired();
+                            forSingleSplit = true;
+                            paramsBuilder
+                                    .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT,
+                                            ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                                    .setSplitName(!splitName.isEmpty() ? splitName : null);
+                            break;
+                        case "--reset":
+                            reset = true;
+                            break;
+                        default:
+                            pw.println("Error: Unknown option: " + opt);
+                            return 1;
+                    }
+                }
+                if (forSingleSplit) {
+                    if (scopeFlags != 0) {
+                        pw.println("'--primary-dex', '--secondary-dex', and "
+                                + "'--include-dependencies' must not be set when '--split' is "
+                                + "set.");
+                        return 1;
+                    }
+                    scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX;
+                }
+                if (scopeFlags != 0) {
+                    paramsBuilder.setFlags(scopeFlags,
+                            ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                    | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+                }
+
+                DexoptResult result;
+                try (var signal = new WithCancellationSignal(pw)) {
+                    if (reset) {
+                        result = mArtManagerLocal.resetDexoptStatus(
+                                snapshot, getNextArgRequired(), signal.get());
+                    } else {
+                        result = mArtManagerLocal.dexoptPackage(snapshot, getNextArgRequired(),
+                                paramsBuilder.build(), signal.get());
+                    }
+                }
+                printDexoptResult(pw, result);
+                return 0;
+            }
+            case "dexopt-packages": {
+                DexoptResult result;
+                ExecutorService executor = Executors.newSingleThreadExecutor();
+                try (var signal = new WithCancellationSignal(pw)) {
+                    result = mArtManagerLocal.dexoptPackages(
+                            snapshot, getNextArgRequired(), signal.get(), executor, progress -> {
+                                pw.println(String.format(
+                                        "Dexopting apps: %d%%", progress.getPercentage()));
+                                pw.flush();
+                            });
+                    Utils.executeAndWait(executor, () -> printDexoptResult(pw, result));
+                } finally {
+                    executor.shutdown();
+                }
+                return 0;
+            }
+            case "cancel": {
+                String jobId = getNextArgRequired();
+                CancellationSignal signal;
+                synchronized (sCancellationSignalMap) {
+                    signal = sCancellationSignalMap.getOrDefault(jobId, null);
+                }
+                if (signal == null) {
+                    pw.println("Job not found");
+                    return 1;
+                }
+                signal.cancel();
+                pw.println("Job cancelled");
+                return 0;
+            }
+            case "dex-use-notify": {
+                mDexUseManager.notifyDexContainersLoaded(snapshot, getNextArgRequired(),
+                        Map.of(getNextArgRequired(), getNextArgRequired()));
+                return 0;
+            }
+            case "dump": {
+                String packageName = getNextArg();
+                if (packageName != null) {
+                    mArtManagerLocal.dumpPackage(pw, snapshot, packageName);
+                } else {
+                    mArtManagerLocal.dump(pw, snapshot);
+                }
+                return 0;
+            }
+            case "dex-use-dump": {
+                pw.println(mDexUseManager.dump());
+                return 0;
+            }
+            case "bg-dexopt-job": {
+                String opt = getNextOption();
+                if (opt == null) {
+                    mArtManagerLocal.startBackgroundDexoptJob();
+                    return 0;
+                }
+                switch (opt) {
+                    case "--cancel": {
+                        mArtManagerLocal.cancelBackgroundDexoptJob();
+                        return 0;
+                    }
+                    case "--enable": {
+                        // This operation requires the uid to be "system" (1000).
+                        long identityToken = Binder.clearCallingIdentity();
+                        try {
+                            mArtManagerLocal.scheduleBackgroundDexoptJob();
+                        } finally {
+                            Binder.restoreCallingIdentity(identityToken);
+                        }
+                        return 0;
+                    }
+                    case "--disable": {
+                        // This operation requires the uid to be "system" (1000).
+                        long identityToken = Binder.clearCallingIdentity();
+                        try {
+                            mArtManagerLocal.unscheduleBackgroundDexoptJob();
+                        } finally {
+                            Binder.restoreCallingIdentity(identityToken);
+                        }
+                        return 0;
+                    }
+                    default:
+                        pw.println("Error: Unknown option: " + opt);
+                        return 1;
+                }
+            }
+            case "snapshot-app-profile": {
+                String packageName = getNextArgRequired();
+                String splitName = getNextArg();
+                String outputRelativePath = String.format("%s%s.prof", packageName,
+                        splitName != null ? String.format("-split_%s.apk", splitName) : "");
+                ParcelFileDescriptor fd;
+                try {
+                    fd = mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName);
+                } catch (SnapshotProfileException e) {
+                    throw new RuntimeException(e);
+                }
+                writeProfileFdContentsToFile(fd, outputRelativePath);
+                return 0;
+            }
+            case "snapshot-boot-image-profile": {
+                String outputRelativePath = "android.prof";
+                ParcelFileDescriptor fd;
+                try {
+                    fd = mArtManagerLocal.snapshotBootImageProfile(snapshot);
+                } catch (SnapshotProfileException e) {
+                    throw new RuntimeException(e);
+                }
+                writeProfileFdContentsToFile(fd, outputRelativePath);
+                return 0;
+            }
+            case "dump-profiles": {
+                boolean dumpClassesAndMethods = false;
+                String opt;
+                while ((opt = getNextOption()) != null) {
+                    switch (opt) {
+                        case "--dump-classes-and-methods": {
+                            dumpClassesAndMethods = true;
+                            break;
+                        }
+                        default:
+                            pw.println("Error: Unknown option: " + opt);
+                            return 1;
+                    }
+                }
+                String packageName = getNextArgRequired();
+                PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+                AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+                for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+                    if (!dexInfo.hasCode()) {
+                        continue;
+                    }
+                    String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName());
+                    // The path is intentionally inconsistent with the one for
+                    // "snapshot-app-profile". The is to match the behavior of the legacy PM shell
+                    // command.
+                    String outputRelativePath =
+                            String.format("%s-%s.prof.txt", packageName, profileName);
+                    ParcelFileDescriptor fd;
+                    try {
+                        fd = mArtManagerLocal.dumpAppProfile(
+                                snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods);
+                    } catch (SnapshotProfileException e) {
+                        throw new RuntimeException(e);
+                    }
+                    writeProfileFdContentsToFile(fd, outputRelativePath);
+                }
+                return 0;
+            }
+            default:
+                pw.println(String.format("Unknown 'art' sub-command '%s'", subcmd));
+                pw.println("See 'cmd package help' for help");
+                return 1;
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        // No one should call this. The help text should be printed by the `onHelp` handler of `cmd
+        // package`.
+        throw new UnsupportedOperationException("Unexpected call to 'onHelp'");
+    }
+
+    public static void printHelp(@NonNull PrintWriter pw) {
+        // TODO(b/263247832): Write help text about root-level commands.
+        pw.println("art SUB_COMMAND [ARGS]...");
+        pw.println("  Run ART Service commands");
+        pw.println("  Note: The commands are used for internal debugging purposes only. There are");
+        pw.println("  no stability guarantees for them.");
+        pw.println();
+        pw.println("  Supported sub-commands:");
+        pw.println("  delete-dexopt-artifacts PACKAGE_NAME");
+        pw.println("    Delete the dexopt artifacts of both primary dex files and secondary");
+        pw.println("    dex files of a package.");
+        pw.println("  get-dexopt-status PACKAGE_NAME");
+        pw.println("    Print the dexopt status of both primary dex files and secondary dex");
+        pw.println("    files of a package.");
+        pw.println("  dexopt-package [-m COMPILER_FILTER] [-f] [--primary-dex]");
+        pw.println("      [--secondary-dex] [--include-dependencies] [--split SPLIT_NAME]");
+        pw.println("      PACKAGE_NAME");
+        pw.println("    Dexopt a package.");
+        pw.println("    If none of '--primary-dex', '--secondary-dex', and");
+        pw.println("    '--include-dependencies' is set, the command dexopts all of them.");
+        pw.println("    The command prints a job ID, which can be used to cancel the job using");
+        pw.println("    the 'cancel' command.");
+        pw.println("    Options:");
+        pw.println("      -m Set the compiler filter.");
+        pw.println("      -f Force compilation.");
+        pw.println("      --primary-dex Dexopt primary dex files.");
+        pw.println("      --secondary-dex Dexopt secondary dex files.");
+        pw.println("      --include-dependencies Include dependencies.");
+        pw.println("      --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an");
+        pw.println("        empty string, only dexopt the base APK. When this option is set,");
+        pw.println("        '--primary-dex', '--secondary-dex', and '--include-dependencies' must");
+        pw.println("        not be set.");
+        pw.println("      --reset Reset the dexopt state of the package as if the package");
+        pw.println("        is newly installed.");
+        pw.println("        More specifically, it clears reference profiles, current profiles,");
+        pw.println("        and any code compiled from those local profiles. If there is an");
+        pw.println("        external profile (e.g., a cloud profile), the code compiled from that");
+        pw.println("        profile will be kept.");
+        pw.println("        For secondary dex files, it also clears all dexopt artifacts.");
+        pw.println("        When this flag is set, all the other flags are ignored.");
+        pw.println("  dexopt-packages REASON");
+        pw.println("    Run batch dexopt for the given reason.");
+        pw.println("    The command prints a job ID, which can be used to cancel the job using");
+        pw.println("    the 'cancel' command.");
+        pw.println("  cancel JOB_ID");
+        pw.println("    Cancel a job.");
+        pw.println("  dex-use-notify PACKAGE_NAME DEX_PATH CLASS_LOADER_CONTEXT");
+        pw.println("    Notify that a dex file is loaded with the given class loader context by");
+        pw.println("    the given package.");
+        pw.println("  dump [PACKAGE_NAME]");
+        pw.println("    Dumps the dexopt state in text format to stdout.");
+        pw.println("    If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
+        pw.println("    is for the given package.");
+        pw.println("  dex-use-dump");
+        pw.println("    Print all dex use information in textproto format.");
+        pw.println("  bg-dexopt-job [--cancel | --disable | --enable]");
+        pw.println("    Control the background dexopt job.");
+        pw.println("    Without flags, it starts a background dexopt job immediately. It does");
+        pw.println("      nothing if a job is already started either automatically by the system");
+        pw.println("      or through this command. This command is not blocking.");
+        pw.println("    Options:");
+        pw.println("      --cancel Cancel any currently running background dexopt job");
+        pw.println("        immediately. This cancels jobs started either automatically by the");
+        pw.println("        system or through this command. This command is not blocking.");
+        pw.println("      --disable: Disable the background dexopt job from being started by the");
+        pw.println("        job scheduler. If a job is already started by the job scheduler and");
+        pw.println("        is running, it will be cancelled immediately. Does not affect");
+        pw.println("        jobs started through this command or by the system in other ways.");
+        pw.println("        This state will be lost when the system_server process exits.");
+        pw.println("      --enable: Enable the background dexopt job to be started by the job");
+        pw.println("        scheduler again, if previously disabled by --disable.");
+        pw.println("  snapshot-app-profile PACKAGE_NAME [SPLIT_NAME]");
+        pw.println("    Snapshot the profile of the given app and save it to");
+        pw.println("    '" + PROFILE_DEBUG_LOCATION + "'.");
+        pw.println("    If SPLIT_NAME is empty, the command is for the base APK, and the output");
+        pw.println("    filename is 'PACKAGE_NAME.prof'. Otherwise, the command is for the given");
+        pw.println("    split, and the output filename is");
+        pw.println("    'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'.");
+        pw.println("  snapshot-boot-image-profile");
+        pw.println("    Snapshot the boot image profile and save it to");
+        pw.println("    '" + PROFILE_DEBUG_LOCATION + "/android.prof'.");
+        pw.println("  dump-profiles [--dump-classes-and-methods] PACKAGE_NAME");
+        pw.println("    Dump the profiles of the given app in text format and save the outputs to");
+        pw.println("    '" + PROFILE_DEBUG_LOCATION + "'.");
+        pw.println("    The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'");
+        pw.println("    The profile of a split APK is dumped to");
+        pw.println("    'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'");
+    }
+
+    private void enforceRoot() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.ROOT_UID) {
+            throw new SecurityException("ART service shell commands need root access");
+        }
+    }
+
+    @NonNull
+    private String dexoptResultStatusToString(@DexoptResultStatus int status) {
+        switch (status) {
+            case DexoptResult.DEXOPT_SKIPPED:
+                return "SKIPPED";
+            case DexoptResult.DEXOPT_PERFORMED:
+                return "PERFORMED";
+            case DexoptResult.DEXOPT_FAILED:
+                return "FAILED";
+            case DexoptResult.DEXOPT_CANCELLED:
+                return "CANCELLED";
+        }
+        throw new IllegalArgumentException("Unknown dexopt status " + status);
+    }
+
+    private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result) {
+        pw.println(dexoptResultStatusToString(result.getFinalStatus()));
+        for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) {
+            pw.printf("[%s]\n", packageResult.getPackageName());
+            for (DexContainerFileDexoptResult fileResult :
+                    packageResult.getDexContainerFileDexoptResults()) {
+                pw.println(fileResult);
+            }
+        }
+    }
+
+    private void writeProfileFdContentsToFile(
+            @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) {
+        try {
+            StructStat st = Os.stat(PROFILE_DEBUG_LOCATION);
+            if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID
+                    || (st.st_mode & 0007) != 0) {
+                throw new RuntimeException(
+                        String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o",
+                                PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode));
+            }
+        } catch (ErrnoException e) {
+            throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e);
+        }
+        Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath);
+        try (InputStream inputStream = new AutoCloseInputStream(fd);
+                FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) {
+            // The system server doesn't have the permission to chown the file to "shell", so we
+            // make it readable by everyone and put it in a directory that is only accessible by
+            // "shell", which is created by system/core/rootdir/init.rc. The permissions are
+            // verified by the code above.
+            Os.fchmod(outputStream.getFD(), 0644);
+            Streams.copy(inputStream, outputStream);
+        } catch (IOException | ErrnoException e) {
+            Utils.deleteIfExistsSafe(outputPath);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class WithCancellationSignal implements AutoCloseable {
+        @NonNull private final CancellationSignal mSignal = new CancellationSignal();
+        @NonNull private final String mJobId;
+
+        public WithCancellationSignal(@NonNull PrintWriter pw) {
+            mJobId = UUID.randomUUID().toString();
+            pw.printf("Job ID: %s\n", mJobId);
+            pw.flush();
+
+            synchronized (sCancellationSignalMap) {
+                sCancellationSignalMap.put(mJobId, mSignal);
+            }
+        }
+
+        @NonNull
+        public CancellationSignal get() {
+            return mSignal;
+        }
+
+        public void close() {
+            synchronized (sCancellationSignalMap) {
+                sCancellationSignalMap.remove(mJobId);
+            }
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
new file mode 100644
index 0000000..9f03107
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback;
+import static com.android.server.art.model.ArtFlags.ScheduleStatus;
+import static com.android.server.art.model.Config.Callback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.PackageManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/** @hide */
+public class BackgroundDexoptJob {
+    private static final String TAG = "BackgroundDexoptJob";
+
+    /**
+     * "android" is the package name for a <service> declared in
+     * frameworks/base/core/res/AndroidManifest.xml
+     */
+    private static final String JOB_PKG_NAME = Utils.PLATFORM_PACKAGE_NAME;
+    /** An arbitrary number. Must be unique among all jobs owned by the system uid. */
+    private static final int JOB_ID = 27873780;
+
+    @VisibleForTesting public static final long JOB_INTERVAL_MS = TimeUnit.DAYS.toMillis(1);
+
+    @NonNull private final Injector mInjector;
+
+    @GuardedBy("this") @Nullable private CompletableFuture<Result> mRunningJob = null;
+    @GuardedBy("this") @Nullable private CancellationSignal mCancellationSignal = null;
+    @GuardedBy("this") @NonNull private Optional<Integer> mLastStopReason = Optional.empty();
+
+    public BackgroundDexoptJob(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal,
+            @NonNull Config config) {
+        this(new Injector(context, artManagerLocal, config));
+    }
+
+    @VisibleForTesting
+    public BackgroundDexoptJob(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /** Handles {@link BackgroundDexoptJobService#onStartJob(JobParameters)}. */
+    public boolean onStartJob(
+            @NonNull BackgroundDexoptJobService jobService, @NonNull JobParameters params) {
+        start().thenAcceptAsync(result -> {
+            writeStats(result);
+            // This is a periodic job, where the interval is specified in the `JobInfo`. "true"
+            // means to execute again during a future idle maintenance window in the same
+            // interval, while "false" means not to execute again during a future idle maintenance
+            // window in the same interval but to execute again in the next interval.
+            // This call will be ignored if `onStopJob` is called.
+            boolean wantsReschedule = result instanceof CompletedResult
+                    && ((CompletedResult) result).dexoptResult().getFinalStatus()
+                            == DexoptResult.DEXOPT_CANCELLED;
+            jobService.jobFinished(params, wantsReschedule);
+        });
+        // "true" means the job will continue running until `jobFinished` is called.
+        return true;
+    }
+
+    /** Handles {@link BackgroundDexoptJobService#onStopJob(JobParameters)}. */
+    public boolean onStopJob(@NonNull JobParameters params) {
+        synchronized (this) {
+            mLastStopReason = Optional.of(params.getStopReason());
+        }
+        cancel();
+        // "true" means to execute again during a future idle maintenance window in the same
+        // interval.
+        return true;
+    }
+
+    /** Handles {@link ArtManagerLocal#scheduleBackgroundDexoptJob()}. */
+    public @ScheduleStatus int schedule() {
+        if (this != BackgroundDexoptJobService.getJob()) {
+            throw new IllegalStateException("This job cannot be scheduled");
+        }
+
+        if (SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false /* def */)) {
+            Log.i(TAG, "Job is disabled by system property 'pm.dexopt.disable_bg_dexopt'");
+            return ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP;
+        }
+
+        JobInfo.Builder builder =
+                new JobInfo
+                        .Builder(JOB_ID,
+                                new ComponentName(
+                                        JOB_PKG_NAME, BackgroundDexoptJobService.class.getName()))
+                        .setPeriodic(JOB_INTERVAL_MS)
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setRequiresBatteryNotLow(true);
+
+        Callback<ScheduleBackgroundDexoptJobCallback, Void> callback =
+                mInjector.getConfig().getScheduleBackgroundDexoptJobCallback();
+        if (callback != null) {
+            Utils.executeAndWait(
+                    callback.executor(), () -> { callback.get().onOverrideJobInfo(builder); });
+        }
+
+        JobInfo info = builder.build();
+        if (info.isRequireStorageNotLow()) {
+            // See the javadoc of
+            // `ArtManagerLocal.ScheduleBackgroundDexoptJobCallback.onOverrideJobInfo` for details.
+            throw new IllegalStateException("'setRequiresStorageNotLow' must not be set");
+        }
+
+        return mInjector.getJobScheduler().schedule(info) == JobScheduler.RESULT_SUCCESS
+                ? ArtFlags.SCHEDULE_SUCCESS
+                : ArtFlags.SCHEDULE_JOB_SCHEDULER_FAILURE;
+    }
+
+    /** Handles {@link ArtManagerLocal#unscheduleBackgroundDexoptJob()}. */
+    public void unschedule() {
+        if (this != BackgroundDexoptJobService.getJob()) {
+            throw new IllegalStateException("This job cannot be unscheduled");
+        }
+
+        mInjector.getJobScheduler().cancel(JOB_ID);
+    }
+
+    @NonNull
+    public synchronized CompletableFuture<Result> start() {
+        if (mRunningJob != null) {
+            Log.i(TAG, "Job is already running");
+            return mRunningJob;
+        }
+
+        mCancellationSignal = new CancellationSignal();
+        mLastStopReason = Optional.empty();
+        mRunningJob = new CompletableFuture().supplyAsync(() -> {
+            Log.i(TAG, "Job started");
+            try {
+                return run(mCancellationSignal);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Fatal error", e);
+                return new FatalErrorResult();
+            } finally {
+                Log.i(TAG, "Job finished");
+                synchronized (this) {
+                    mRunningJob = null;
+                    mCancellationSignal = null;
+                }
+            }
+        });
+        return mRunningJob;
+    }
+
+    public synchronized void cancel() {
+        if (mRunningJob == null) {
+            Log.i(TAG, "Job is not running");
+            return;
+        }
+
+        mCancellationSignal.cancel();
+        Log.i(TAG, "Job cancelled");
+    }
+
+    @NonNull
+    private CompletedResult run(@NonNull CancellationSignal cancellationSignal) {
+        // TODO(b/254013427): Cleanup dex use info.
+        // TODO(b/254013425): Cleanup unused secondary dex file artifacts.
+        long startTimeMs = SystemClock.uptimeMillis();
+        DexoptResult dexoptResult;
+        try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
+            dexoptResult = mInjector.getArtManagerLocal().dexoptPackages(snapshot,
+                    ReasonMapping.REASON_BG_DEXOPT, cancellationSignal,
+                    null /* processCallbackExecutor */, null /* processCallback */);
+        }
+        return CompletedResult.create(dexoptResult, SystemClock.uptimeMillis() - startTimeMs);
+    }
+
+    private void writeStats(@NonNull Result result) {
+        Optional<Integer> stopReason;
+        synchronized (this) {
+            stopReason = mLastStopReason;
+        }
+        if (result instanceof CompletedResult) {
+            var completedResult = (CompletedResult) result;
+            ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+                    getStatusForStats(completedResult, stopReason),
+                    stopReason.orElse(JobParameters.STOP_REASON_UNDEFINED),
+                    completedResult.durationMs(), 0 /* deprecated */);
+        } else if (result instanceof FatalErrorResult) {
+            ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR,
+                    JobParameters.STOP_REASON_UNDEFINED, 0 /* durationMs */, 0 /* deprecated */);
+        }
+    }
+
+    private int getStatusForStats(@NonNull CompletedResult result, Optional<Integer> stopReason) {
+        if (result.dexoptResult().getFinalStatus() == DexoptResult.DEXOPT_CANCELLED) {
+            if (stopReason.isPresent()) {
+                return ArtStatsLog
+                        .BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION;
+            } else {
+                return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_API;
+            }
+        }
+
+        boolean isSkippedDueToStorageLow =
+                result.dexoptResult()
+                        .getPackageDexoptResults()
+                        .stream()
+                        .flatMap(packageResult
+                                -> packageResult.getDexContainerFileDexoptResults().stream())
+                        .anyMatch(fileResult -> fileResult.isSkippedDueToStorageLow());
+        if (isSkippedDueToStorageLow) {
+            return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_NO_SPACE_LEFT;
+        }
+
+        return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED;
+    }
+
+    static abstract class Result {}
+    static class FatalErrorResult extends Result {}
+
+    @AutoValue
+    static abstract class CompletedResult extends Result {
+        abstract @NonNull DexoptResult dexoptResult();
+        abstract long durationMs();
+
+        @NonNull
+        static CompletedResult create(@NonNull DexoptResult dexoptResult, long durationMs) {
+            return new AutoValue_BackgroundDexoptJob_CompletedResult(dexoptResult, durationMs);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+        @NonNull private final ArtManagerLocal mArtManagerLocal;
+        @NonNull private final Config mConfig;
+
+        Injector(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal,
+                @NonNull Config config) {
+            mContext = context;
+            mArtManagerLocal = artManagerLocal;
+            mConfig = config;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            getPackageManagerLocal();
+            getJobScheduler();
+        }
+
+        @NonNull
+        public ArtManagerLocal getArtManagerLocal() {
+            return mArtManagerLocal;
+        }
+
+        @NonNull
+        public PackageManagerLocal getPackageManagerLocal() {
+            return LocalManagerRegistry.getManager(PackageManagerLocal.class);
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        @NonNull
+        public JobScheduler getJobScheduler() {
+            return mContext.getSystemService(JobScheduler.class);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java
new file mode 100644
index 0000000..d6932b1
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * Entry point for the callback from the job scheduler. This class is instantiated by the system
+ * automatically.
+ *
+ * @hide
+ */
+public class BackgroundDexoptJobService extends JobService {
+    @Override
+    public boolean onStartJob(@NonNull JobParameters params) {
+        return getJob().onStartJob(this, params);
+    }
+
+    @Override
+    public boolean onStopJob(@NonNull JobParameters params) {
+        return getJob().onStopJob(params);
+    }
+
+    @NonNull
+    static BackgroundDexoptJob getJob() {
+        return LocalManagerRegistry.getManager(ArtManagerLocal.class).getBackgroundDexoptJob();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Constants.java b/libartservice/service/java/com/android/server/art/Constants.java
new file mode 100644
index 0000000..2d2d757
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Constants.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.system.Os;
+
+/**
+ * A mockable wrapper class for device-specific constants.
+ *
+ * @hide
+ */
+public class Constants {
+    private Constants() {}
+
+    /** Returns the ABI that the device prefers. */
+    @NonNull
+    public static String getPreferredAbi() {
+        return Build.SUPPORTED_ABIS[0];
+    }
+
+    /** Returns the 64 bit ABI that is native to the device. */
+    @Nullable
+    public static String getNative64BitAbi() {
+        // The value comes from "ro.product.cpu.abilist64" and we assume that the first element is
+        // the native one.
+        return Build.SUPPORTED_64_BIT_ABIS.length > 0 ? Build.SUPPORTED_64_BIT_ABIS[0] : null;
+    }
+
+    /** Returns the 32 bit ABI that is native to the device. */
+    @Nullable
+    public static String getNative32BitAbi() {
+        // The value comes from "ro.product.cpu.abilist32" and we assume that the first element is
+        // the native one.
+        return Build.SUPPORTED_32_BIT_ABIS.length > 0 ? Build.SUPPORTED_32_BIT_ABIS[0] : null;
+    }
+
+    @Nullable
+    public static String getenv(@NonNull String name) {
+        return Os.getenv(name);
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Debouncer.java b/libartservice/service/java/com/android/server/art/Debouncer.java
new file mode 100644
index 0000000..61aea81
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Debouncer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * A class that executes commands with a minimum interval.
+ *
+ * @hide
+ */
+public class Debouncer {
+    @NonNull private Supplier<ScheduledExecutorService> mScheduledExecutorFactory;
+    private final long mIntervalMs;
+    @Nullable private ScheduledFuture<?> mCurrentTask = null;
+
+    public Debouncer(
+            long intervalMs, @NonNull Supplier<ScheduledExecutorService> scheduledExecutorFactory) {
+        mScheduledExecutorFactory = scheduledExecutorFactory;
+        mIntervalMs = intervalMs;
+    }
+
+    /**
+     * Runs the given command after the interval has passed. If another command comes in during
+     * this interval, the previous one will never run.
+     */
+    synchronized public void maybeRunAsync(@NonNull Runnable command) {
+        if (mCurrentTask != null) {
+            mCurrentTask.cancel(false /* mayInterruptIfRunning */);
+        }
+        ScheduledExecutorService executor = mScheduledExecutorFactory.get();
+        mCurrentTask = executor.schedule(command, mIntervalMs, TimeUnit.MILLISECONDS);
+        executor.shutdown();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
new file mode 100644
index 0000000..acc9a16
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -0,0 +1,906 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Immutable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexContainerFileUseInfo;
+import com.android.server.art.proto.DexUseProto;
+import com.android.server.art.proto.Int32Value;
+import com.android.server.art.proto.PackageDexUseProto;
+import com.android.server.art.proto.PrimaryDexUseProto;
+import com.android.server.art.proto.PrimaryDexUseRecordProto;
+import com.android.server.art.proto.SecondaryDexUseProto;
+import com.android.server.art.proto.SecondaryDexUseRecordProto;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+/**
+ * A singleton class that maintains the information about dex uses. This class is thread-safe.
+ *
+ * This class collects data sent directly by apps, and hence the data should be trusted as little as
+ * possible.
+ *
+ * To avoid overwriting data, {@link #load()} must be called exactly once, during initialization.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class DexUseManagerLocal {
+    private static final String TAG = "DexUseManagerLocal";
+    private static final String FILENAME = "/data/system/package-dex-usage.pb";
+
+    /**
+     * The minimum interval between disk writes.
+     *
+     * In practice, the interval will be much longer because we use a debouncer to postpone the disk
+     * write to the end of a series of changes. Note that in theory we could postpone the disk write
+     * indefinitely, and therefore we could lose data if the device isn't shut down in the normal
+     * way, but that's fine because the data isn't crucial and is recoverable.
+     *
+     * @hide
+     */
+    @VisibleForTesting public static final long INTERVAL_MS = 15_000;
+
+    private static final Object sLock = new Object();
+    @GuardedBy("sLock") @Nullable private static DexUseManagerLocal sInstance = null;
+
+    @NonNull private final Injector mInjector;
+    @NonNull private final Debouncer mDebouncer;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock") @NonNull private DexUse mDexUse; // Initialized by `load`.
+    @GuardedBy("mLock") private int mRevision = 0;
+    @GuardedBy("mLock") private int mLastCommittedRevision = 0;
+
+    /**
+     * Creates the singleton instance.
+     *
+     * Only {@code SystemServer} should create the instance and register it in {@link
+     * LocalManagerRegistry}. Other API users should obtain the instance from {@link
+     * LocalManagerRegistry}.
+     *
+     * In practice, it must be created and registered in {@link LocalManagerRegistry} before {@code
+     * PackageManagerService} starts because {@code PackageManagerService} needs it as soon as it
+     * starts. It's safe to create an instance early because it doesn't depend on anything else.
+     *
+     * @param context the system server context
+     * @throws IllegalStateException if the instance is already created
+     * @throws NullPointerException if required dependencies are missing
+     */
+    @NonNull
+    public static DexUseManagerLocal createInstance(@NonNull Context context) {
+        synchronized (sLock) {
+            if (sInstance != null) {
+                throw new IllegalStateException("DexUseManagerLocal is already created");
+            }
+            sInstance = new DexUseManagerLocal(context);
+            return sInstance;
+        }
+    }
+
+    private DexUseManagerLocal(@NonNull Context context) {
+        this(new Injector(context));
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public DexUseManagerLocal(@NonNull Injector injector) {
+        mInjector = injector;
+        mDebouncer = new Debouncer(INTERVAL_MS, mInjector::createScheduledExecutor);
+        load();
+    }
+
+    /** Notifies dex use manager that {@link Context#registerReceiver} is ready for use. */
+    public void systemReady() {
+        // Save the data when the device is being shut down. The receiver is blocking, with a
+        // 10s timeout.
+        mInjector.getContext().registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                context.unregisterReceiver(this);
+                save();
+            }
+        }, new IntentFilter(Intent.ACTION_SHUTDOWN));
+    }
+
+    /**
+     * Returns the information about the use of all secondary dex files owned by the given package,
+     * or an empty list if the package does not own any secondary dex file or it does not exist.
+     */
+    @NonNull
+    public List<DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfo(packageName)
+                .stream()
+                .map(info
+                        -> DexContainerFileUseInfo.create(info.dexPath(), info.userHandle(),
+                                info.loaders()
+                                        .stream()
+                                        .map(loader -> loader.loadingPackageName())
+                                        .collect(Collectors.toSet())))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns all entities that load the given primary dex file owned by the given package.
+     *
+     * @hide
+     */
+    @NonNull
+    public Set<DexLoader> getPrimaryDexLoaders(
+            @NonNull String packageName, @NonNull String dexPath) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return Set.of();
+            }
+            PrimaryDexUse primaryDexUse = packageDexUse.mPrimaryDexUseByDexFile.get(dexPath);
+            if (primaryDexUse == null) {
+                return Set.of();
+            }
+            return Set.copyOf(primaryDexUse.mRecordByLoader.keySet());
+        }
+    }
+
+    /**
+     * Returns whether a primary dex file owned by the given package is used by other apps.
+     *
+     * @hide
+     */
+    public boolean isPrimaryDexUsedByOtherApps(
+            @NonNull String packageName, @NonNull String dexPath) {
+        return isUsedByOtherApps(getPrimaryDexLoaders(packageName, dexPath), packageName);
+    }
+
+    /**
+     * Returns the basic information about all secondary dex files owned by the given package. This
+     * method doesn't take dex file visibility into account, so it can only be used for debugging
+     * purpose, such as dumpsys.
+     *
+     * @see #getFilteredDetailedSecondaryDexInfo(String)
+     * @hide
+     */
+    public @NonNull List<? extends SecondaryDexInfo> getSecondaryDexInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfoImpl(packageName, false /* checkDexFile */);
+    }
+
+    /**
+     * Same as above, but requires disk IO, and returns the detailed information, including dex file
+     * visibility, filtered by dex file existence and visibility.
+     *
+     * @hide
+     */
+    public @NonNull List<DetailedSecondaryDexInfo> getFilteredDetailedSecondaryDexInfo(
+            @NonNull String packageName) {
+        return getSecondaryDexInfoImpl(packageName, true /* checkDexFile */);
+    }
+
+    /**
+     * Returns the last time the package was used, or 0 if the package has never been used.
+     *
+     * @hide
+     */
+    public long getPackageLastUsedAtMs(@NonNull String packageName) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return 0;
+            }
+            long primaryLastUsedAtMs =
+                    packageDexUse.mPrimaryDexUseByDexFile.values()
+                            .stream()
+                            .flatMap(primaryDexUse
+                                    -> primaryDexUse.mRecordByLoader.values().stream())
+                            .map(record -> record.mLastUsedAtMs)
+                            .max(Long::compare)
+                            .orElse(0l);
+            long secondaryLastUsedAtMs =
+                    packageDexUse.mSecondaryDexUseByDexFile.values()
+                            .stream()
+                            .flatMap(secondaryDexUse
+                                    -> secondaryDexUse.mRecordByLoader.values().stream())
+                            .map(record -> record.mLastUsedAtMs)
+                            .max(Long::compare)
+                            .orElse(0l);
+            return Math.max(primaryLastUsedAtMs, secondaryLastUsedAtMs);
+        }
+    }
+
+    /**
+     * @param checkDexFile if true, check the existence and visibility of the dex files, and filter
+     *         the results accordingly. Note that the value of the {@link
+     *         DetailedSecondaryDexInfo#isDexFilePublic()} field is undefined if this argument is
+     *         false.
+     */
+    private @NonNull List<DetailedSecondaryDexInfo> getSecondaryDexInfoImpl(
+            @NonNull String packageName, boolean checkDexFile) {
+        synchronized (mLock) {
+            PackageDexUse packageDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
+            if (packageDexUse == null) {
+                return List.of();
+            }
+            var results = new ArrayList<DetailedSecondaryDexInfo>();
+            for (var entry : packageDexUse.mSecondaryDexUseByDexFile.entrySet()) {
+                String dexPath = entry.getKey();
+                SecondaryDexUse secondaryDexUse = entry.getValue();
+
+                @FileVisibility
+                int visibility = checkDexFile ? getDexFileVisibility(dexPath)
+                                              : FileVisibility.OTHER_READABLE;
+                if (visibility == FileVisibility.NOT_FOUND) {
+                    continue;
+                }
+
+                Map<DexLoader, SecondaryDexUseRecord> filteredRecordByLoader;
+                if (visibility == FileVisibility.OTHER_READABLE) {
+                    filteredRecordByLoader = secondaryDexUse.mRecordByLoader;
+                } else {
+                    // Only keep the entry that belongs to the same app.
+                    DexLoader sameApp = DexLoader.create(packageName, false /* isolatedProcess */);
+                    SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.get(sameApp);
+                    filteredRecordByLoader = record != null ? Map.of(sameApp, record) : Map.of();
+                }
+                if (filteredRecordByLoader.isEmpty()) {
+                    continue;
+                }
+                List<String> distinctClcList =
+                        filteredRecordByLoader.values()
+                                .stream()
+                                .map(record -> Utils.assertNonEmpty(record.mClassLoaderContext))
+                                .filter(clc
+                                        -> !clc.equals(
+                                                SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT))
+                                .distinct()
+                                .collect(Collectors.toList());
+                String clc;
+                if (distinctClcList.size() == 0) {
+                    clc = SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT;
+                } else if (distinctClcList.size() == 1) {
+                    clc = distinctClcList.get(0);
+                } else {
+                    // If there are more than one class loader contexts, we can't dexopt the dex
+                    // file.
+                    clc = SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS;
+                }
+                // Although we filter out unsupported CLCs above, `distinctAbiNames` and `loaders`
+                // still need to take apps with unsupported CLCs into account because the vdex file
+                // is still usable to them.
+                Set<String> distinctAbiNames =
+                        filteredRecordByLoader.values()
+                                .stream()
+                                .map(record -> Utils.assertNonEmpty(record.mAbiName))
+                                .collect(Collectors.toSet());
+                Set<DexLoader> loaders = Set.copyOf(filteredRecordByLoader.keySet());
+                results.add(DetailedSecondaryDexInfo.create(dexPath,
+                        Objects.requireNonNull(secondaryDexUse.mUserHandle), clc, distinctAbiNames,
+                        loaders, isUsedByOtherApps(loaders, packageName),
+                        visibility == FileVisibility.OTHER_READABLE));
+            }
+            return Collections.unmodifiableList(results);
+        }
+    }
+
+    /**
+     * Notifies ART Service that a list of dex container files have been loaded.
+     *
+     * ART Service uses this information to:
+     * <ul>
+     *   <li>Determine whether an app is used by another app
+     *   <li>Record which secondary dex container files to dexopt and how to dexopt them
+     * </ul>
+     *
+     * @param loadingPackageName the name of the package who performs the load. ART Service assumes
+     *         that this argument has been validated that it exists in the snapshot and matches the
+     *         calling UID
+     * @param classLoaderContextByDexContainerFile a map from dex container files' absolute paths to
+     *         the string representations of the class loader contexts used to load them
+     * @throws IllegalArgumentException if {@code classLoaderContextByDexContainerFile} contains
+     *         invalid entries
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void notifyDexContainersLoaded(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName,
+            @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+        // "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle
+        // this case because it doesn't compile system server and system server isn't allowed to
+        // load artifacts produced by ART Services.
+        if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
+            return;
+        }
+
+        validateInputs(snapshot, loadingPackageName, classLoaderContextByDexContainerFile);
+
+        // TODO(jiakaiz): Investigate whether it should also be considered as isolated process if
+        // `Process.isSdkSandboxUid` returns true.
+        boolean isolatedProcess = Process.isIsolatedUid(Binder.getCallingUid());
+        long lastUsedAtMs = mInjector.getCurrentTimeMillis();
+
+        for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+            String dexPath = Utils.assertNonEmpty(entry.getKey());
+            String classLoaderContext = Utils.assertNonEmpty(entry.getValue());
+            String owningPackageName = findOwningPackage(snapshot, loadingPackageName, dexPath,
+                    DexUseManagerLocal::isOwningPackageForPrimaryDex);
+            if (owningPackageName != null) {
+                addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+                        lastUsedAtMs);
+                continue;
+            }
+            owningPackageName = findOwningPackage(snapshot, loadingPackageName, dexPath,
+                    DexUseManagerLocal::isOwningPackageForSecondaryDex);
+            if (owningPackageName != null) {
+                PackageState loadingPkgState =
+                        Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+                // An app is always launched with its primary ABI.
+                Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
+                addSecondaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+                        classLoaderContext, abi.name(), lastUsedAtMs);
+                continue;
+            }
+            // It is expected that a dex file isn't owned by any package. For example, the dex file
+            // could be a shared library jar.
+        }
+    }
+
+    @Nullable
+    private static String findOwningPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName, @NonNull String dexPath,
+            @NonNull BiFunction<PackageState, String, Boolean> predicate) {
+        // Most likely, the package is loading its own dex file, so we check this first as an
+        // optimization.
+        PackageState loadingPkgState = Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+        if (predicate.apply(loadingPkgState, dexPath)) {
+            return loadingPkgState.getPackageName();
+        }
+
+        return snapshot.getPackageStates()
+                .values()
+                .stream()
+                .filter(packageState -> predicate.apply(packageState, dexPath))
+                .map(PackageState::getPackageName)
+                .findFirst()
+                .orElse(null);
+    }
+
+    private static boolean isOwningPackageForPrimaryDex(
+            @NonNull PackageState pkgState, @NonNull String dexPath) {
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        return PrimaryDexUtils.getDexInfo(pkg).stream().anyMatch(
+                dexInfo -> dexInfo.dexPath().equals(dexPath));
+    }
+
+    private static boolean isOwningPackageForSecondaryDex(
+            @NonNull PackageState pkgState, @NonNull String dexPath) {
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        UUID storageUuid = pkg.getStorageUuid();
+        UserHandle handle = Binder.getCallingUserHandle();
+
+        File ceDir = Environment.getDataCePackageDirectoryForUser(
+                storageUuid, handle, pkgState.getPackageName());
+        if (Paths.get(dexPath).startsWith(ceDir.toPath())) {
+            return true;
+        }
+
+        File deDir = Environment.getDataDePackageDirectoryForUser(
+                storageUuid, handle, pkgState.getPackageName());
+        if (Paths.get(dexPath).startsWith(deDir.toPath())) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private void addPrimaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+            @NonNull String loadingPackageName, boolean isolatedProcess, long lastUsedAtMs) {
+        synchronized (mLock) {
+            PrimaryDexUseRecord record =
+                    mDexUse.mPackageDexUseByOwningPackageName
+                            .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
+                            .mPrimaryDexUseByDexFile
+                            .computeIfAbsent(dexPath, k -> new PrimaryDexUse())
+                            .mRecordByLoader.computeIfAbsent(
+                                    DexLoader.create(loadingPackageName, isolatedProcess),
+                                    k -> new PrimaryDexUseRecord());
+            record.mLastUsedAtMs = lastUsedAtMs;
+            mRevision++;
+        }
+        maybeSaveAsync();
+    }
+
+    private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+            @NonNull String loadingPackageName, boolean isolatedProcess,
+            @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) {
+        synchronized (mLock) {
+            SecondaryDexUse secondaryDexUse =
+                    mDexUse.mPackageDexUseByOwningPackageName
+                            .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
+                            .mSecondaryDexUseByDexFile.computeIfAbsent(
+                                    dexPath, k -> new SecondaryDexUse());
+            secondaryDexUse.mUserHandle = Binder.getCallingUserHandle();
+            SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.computeIfAbsent(
+                    DexLoader.create(loadingPackageName, isolatedProcess),
+                    k -> new SecondaryDexUseRecord());
+            record.mClassLoaderContext = classLoaderContext;
+            record.mAbiName = abiName;
+            record.mLastUsedAtMs = lastUsedAtMs;
+            mRevision++;
+        }
+        maybeSaveAsync();
+    }
+
+    /** @hide */
+    public @NonNull String dump() {
+        var builder = DexUseProto.newBuilder();
+        synchronized (mLock) {
+            mDexUse.toProto(builder);
+        }
+        return builder.build().toString();
+    }
+
+    private void save() {
+        var builder = DexUseProto.newBuilder();
+        int thisRevision;
+        synchronized (mLock) {
+            if (mRevision <= mLastCommittedRevision) {
+                return;
+            }
+            mDexUse.toProto(builder);
+            thisRevision = mRevision;
+        }
+        var file = new File(mInjector.getFilename());
+        File tempFile = null;
+        try {
+            tempFile = File.createTempFile(file.getName(), null /* suffix */, file.getParentFile());
+            try (OutputStream out = new FileOutputStream(tempFile.getPath())) {
+                builder.build().writeTo(out);
+            }
+            synchronized (mLock) {
+                // Check revision again in case `mLastCommittedRevision` has changed since the check
+                // above, to avoid ABA race.
+                if (thisRevision > mLastCommittedRevision) {
+                    Files.move(tempFile.toPath(), file.toPath(),
+                            StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+                    mLastCommittedRevision = thisRevision;
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to save dex use data", e);
+        } finally {
+            Utils.deleteIfExistsSafe(tempFile);
+        }
+    }
+
+    private void maybeSaveAsync() {
+        mDebouncer.maybeRunAsync(this::save);
+    }
+
+    /** This should only be called during initialization. */
+    private void load() {
+        DexUseProto proto = null;
+        try (InputStream in = new FileInputStream(mInjector.getFilename())) {
+            proto = DexUseProto.parseFrom(in);
+        } catch (IOException e) {
+            // Nothing else we can do but to start from scratch.
+            Log.e(TAG, "Failed to load dex use data", e);
+        }
+        synchronized (mLock) {
+            if (mDexUse != null) {
+                throw new IllegalStateException("Load has already been attempted");
+            }
+            mDexUse = new DexUse();
+            if (proto != null) {
+                mDexUse.fromProto(proto);
+            }
+        }
+    }
+
+    private static boolean isUsedByOtherApps(
+            @NonNull Set<DexLoader> loaders, @NonNull String owningPackageName) {
+        return loaders.stream().anyMatch(loader -> isLoaderOtherApp(loader, owningPackageName));
+    }
+
+    /**
+     * Returns true if {@code loader} is considered as "other app" (i.e., its process UID is
+     * different from the UID of the package represented by {@code owningPackageName}).
+     *
+     * @hide
+     */
+    public static boolean isLoaderOtherApp(
+            @NonNull DexLoader loader, @NonNull String owningPackageName) {
+        // If the dex file is loaded by an isolated process of the same app, it can also be
+        // considered as "used by other apps" because isolated processes are sandboxed and can only
+        // read world readable files, so they need the dexopt artifacts to be world readable. An
+        // example of such a package is webview.
+        return !loader.loadingPackageName().equals(owningPackageName) || loader.isolatedProcess();
+    }
+
+    private static void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String loadingPackageName,
+            @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+        if (classLoaderContextByDexContainerFile.isEmpty()) {
+            throw new IllegalArgumentException("Nothing to record");
+        }
+
+        for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+            Utils.assertNonEmpty(entry.getKey());
+            if (!Paths.get(entry.getKey()).isAbsolute()) {
+                throw new IllegalArgumentException(String.format(
+                        "Dex container file path must be absolute, got '%s'", entry.getKey()));
+            }
+            Utils.assertNonEmpty(entry.getValue());
+        }
+
+        // TODO(b/253570365): Make the validation more strict.
+    }
+
+    private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
+        try {
+            return mInjector.getArtd().getDexFileVisibility(dexPath);
+        } catch (ServiceSpecificException | RemoteException e) {
+            Log.e(TAG, "Failed to get visibility of " + dexPath, e);
+            return FileVisibility.NOT_FOUND;
+        }
+    }
+
+    /**
+     * Basic information about a secondary dex file (an APK or JAR file that an app adds to its
+     * own data directory and loads dynamically).
+     *
+     * @hide
+     */
+    @Immutable
+    public abstract static class SecondaryDexInfo {
+        // Special encoding used to denote a foreign ClassLoader was found when trying to encode
+        // class loader contexts for each classpath element in a ClassLoader.
+        // Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
+        // `art/runtime/class_loader_context.h`.
+        public static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+                "=UnsupportedClassLoaderContext=";
+
+        // Special encoding used to denote that a dex file is loaded by different packages with
+        // different ClassLoader's. Only for display purpose (e.g., in dumpsys). This value is not
+        // written to the file, and so far only used here.
+        @VisibleForTesting
+        public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts=";
+
+        /** The absolute path to the dex file within the user's app data directory. */
+        public abstract @NonNull String dexPath();
+
+        /**
+         * The {@link UserHandle} that represents the human user who owns and loads the dex file. A
+         * secondary dex file belongs to a specific human user, and only that user can load it.
+         */
+        public abstract @NonNull UserHandle userHandle();
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with,
+         * or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}.
+         */
+        public abstract @NonNull String displayClassLoaderContext();
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with,
+         * or null if the class loader context is invalid.
+         */
+        public @Nullable String classLoaderContext() {
+            return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
+                            && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS)
+                    ? displayClassLoaderContext()
+                    : null;
+        }
+
+        /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */
+        public abstract @NonNull Set<String> abiNames();
+
+        /** The set of entities that load the dex file. Guaranteed to be non-empty. */
+        public abstract @NonNull Set<DexLoader> loaders();
+
+        /** Returns whether the dex file is used by apps other than the app that owns it. */
+        public abstract boolean isUsedByOtherApps();
+    }
+
+    /**
+     * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
+     * own data directory and loads dynamically). It contains the visibility of the dex file in
+     * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO.
+     *
+     * @hide
+     */
+    @Immutable
+    @AutoValue
+    public abstract static class DetailedSecondaryDexInfo
+            extends SecondaryDexInfo implements DetailedDexInfo {
+        static DetailedSecondaryDexInfo create(@NonNull String dexPath,
+                @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext,
+                @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders,
+                boolean isUsedByOtherApps, boolean isDexFilePublic) {
+            return new AutoValue_DexUseManagerLocal_DetailedSecondaryDexInfo(dexPath, userHandle,
+                    displayClassLoaderContext, Collections.unmodifiableSet(abiNames),
+                    Collections.unmodifiableSet(loaders), isUsedByOtherApps, isDexFilePublic);
+        }
+
+        /**
+         * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+         * (S_IROTH).
+         */
+        public abstract boolean isDexFilePublic();
+    }
+
+    private static class DexUse {
+        @NonNull Map<String, PackageDexUse> mPackageDexUseByOwningPackageName = new HashMap<>();
+
+        void toProto(@NonNull DexUseProto.Builder builder) {
+            for (var entry : mPackageDexUseByOwningPackageName.entrySet()) {
+                var packageBuilder =
+                        PackageDexUseProto.newBuilder().setOwningPackageName(entry.getKey());
+                entry.getValue().toProto(packageBuilder);
+                builder.addPackageDexUse(packageBuilder);
+            }
+        }
+
+        void fromProto(@NonNull DexUseProto proto) {
+            for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) {
+                var packageDexUse = new PackageDexUse();
+                packageDexUse.fromProto(packageProto);
+                mPackageDexUseByOwningPackageName.put(
+                        Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse);
+            }
+        }
+    }
+
+    private static class PackageDexUse {
+        /**
+         * The keys are absolute paths to primary dex files of the owning package (the base APK and
+         * split APKs).
+         */
+        @NonNull Map<String, PrimaryDexUse> mPrimaryDexUseByDexFile = new HashMap<>();
+
+        /**
+         * The keys are absolute paths to secondary dex files of the owning package (the APKs and
+         * JARs in CE and DE directories).
+         */
+        @NonNull Map<String, SecondaryDexUse> mSecondaryDexUseByDexFile = new HashMap<>();
+
+        void toProto(@NonNull PackageDexUseProto.Builder builder) {
+            for (var entry : mPrimaryDexUseByDexFile.entrySet()) {
+                var primaryBuilder = PrimaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+                entry.getValue().toProto(primaryBuilder);
+                builder.addPrimaryDexUse(primaryBuilder);
+            }
+            for (var entry : mSecondaryDexUseByDexFile.entrySet()) {
+                var secondaryBuilder = SecondaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+                entry.getValue().toProto(secondaryBuilder);
+                builder.addSecondaryDexUse(secondaryBuilder);
+            }
+        }
+
+        void fromProto(@NonNull PackageDexUseProto proto) {
+            for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) {
+                var primaryDexUse = new PrimaryDexUse();
+                primaryDexUse.fromProto(primaryProto);
+                mPrimaryDexUseByDexFile.put(
+                        Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse);
+            }
+            for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) {
+                var secondaryDexUse = new SecondaryDexUse();
+                secondaryDexUse.fromProto(secondaryProto);
+                mSecondaryDexUseByDexFile.put(
+                        Utils.assertNonEmpty(secondaryProto.getDexFile()), secondaryDexUse);
+            }
+        }
+    }
+
+    private static class PrimaryDexUse {
+        @NonNull Map<DexLoader, PrimaryDexUseRecord> mRecordByLoader = new HashMap<>();
+
+        void toProto(@NonNull PrimaryDexUseProto.Builder builder) {
+            for (var entry : mRecordByLoader.entrySet()) {
+                var recordBuilder =
+                        PrimaryDexUseRecordProto.newBuilder()
+                                .setLoadingPackageName(entry.getKey().loadingPackageName())
+                                .setIsolatedProcess(entry.getKey().isolatedProcess());
+                entry.getValue().toProto(recordBuilder);
+                builder.addRecord(recordBuilder);
+            }
+        }
+
+        void fromProto(@NonNull PrimaryDexUseProto proto) {
+            for (PrimaryDexUseRecordProto recordProto : proto.getRecordList()) {
+                var record = new PrimaryDexUseRecord();
+                record.fromProto(recordProto);
+                mRecordByLoader.put(
+                        DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+                                recordProto.getIsolatedProcess()),
+                        record);
+            }
+        }
+    }
+
+    private static class SecondaryDexUse {
+        @Nullable UserHandle mUserHandle = null;
+        @NonNull Map<DexLoader, SecondaryDexUseRecord> mRecordByLoader = new HashMap<>();
+
+        void toProto(@NonNull SecondaryDexUseProto.Builder builder) {
+            builder.setUserId(Int32Value.newBuilder().setValue(mUserHandle.getIdentifier()));
+            for (var entry : mRecordByLoader.entrySet()) {
+                var recordBuilder =
+                        SecondaryDexUseRecordProto.newBuilder()
+                                .setLoadingPackageName(entry.getKey().loadingPackageName())
+                                .setIsolatedProcess(entry.getKey().isolatedProcess());
+                entry.getValue().toProto(recordBuilder);
+                builder.addRecord(recordBuilder);
+            }
+        }
+
+        void fromProto(@NonNull SecondaryDexUseProto proto) {
+            Utils.check(proto.hasUserId());
+            mUserHandle = UserHandle.of(proto.getUserId().getValue());
+            for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) {
+                var record = new SecondaryDexUseRecord();
+                record.fromProto(recordProto);
+                mRecordByLoader.put(
+                        DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+                                recordProto.getIsolatedProcess()),
+                        record);
+            }
+        }
+    }
+
+    /**
+     * Represents an entity that loads a dex file.
+     *
+     * @hide
+     */
+    @Immutable
+    @AutoValue
+    public abstract static class DexLoader {
+        static DexLoader create(@NonNull String loadingPackageName, boolean isolatedProcess) {
+            return new AutoValue_DexUseManagerLocal_DexLoader(loadingPackageName, isolatedProcess);
+        }
+
+        abstract @NonNull String loadingPackageName();
+
+        /** @see Process#isIsolatedUid(int) */
+        abstract boolean isolatedProcess();
+    }
+
+    private static class PrimaryDexUseRecord {
+        @Nullable long mLastUsedAtMs = 0;
+
+        void toProto(@NonNull PrimaryDexUseRecordProto.Builder builder) {
+            builder.setLastUsedAtMs(mLastUsedAtMs);
+        }
+
+        void fromProto(@NonNull PrimaryDexUseRecordProto proto) {
+            mLastUsedAtMs = proto.getLastUsedAtMs();
+            Utils.check(mLastUsedAtMs > 0);
+        }
+    }
+
+    private static class SecondaryDexUseRecord {
+        // An app constructs their own class loader to load a secondary dex file, so only itself
+        // knows the class loader context. Therefore, we need to record the class loader context
+        // reported by the app.
+        @Nullable String mClassLoaderContext = null;
+        @Nullable String mAbiName = null;
+        @Nullable long mLastUsedAtMs = 0;
+
+        void toProto(@NonNull SecondaryDexUseRecordProto.Builder builder) {
+            builder.setClassLoaderContext(mClassLoaderContext)
+                    .setAbiName(mAbiName)
+                    .setLastUsedAtMs(mLastUsedAtMs);
+        }
+
+        void fromProto(@NonNull SecondaryDexUseRecordProto proto) {
+            mClassLoaderContext = Utils.assertNonEmpty(proto.getClassLoaderContext());
+            mAbiName = Utils.assertNonEmpty(proto.getAbiName());
+            mLastUsedAtMs = proto.getLastUsedAtMs();
+            Utils.check(mLastUsedAtMs > 0);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+
+        Injector(@NonNull Context context) {
+            mContext = context;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            ArtModuleServiceInitializer.getArtModuleServiceManager();
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @NonNull
+        public String getFilename() {
+            return FILENAME;
+        }
+
+        @NonNull
+        public ScheduledExecutorService createScheduledExecutor() {
+            return Executors.newSingleThreadScheduledExecutor();
+        }
+
+        @NonNull
+        public Context getContext() {
+            return mContext;
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexoptHelper.java b/libartservice/service/java/com/android/server/art/DexoptHelper.java
new file mode 100644
index 0000000..d139bfa
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.model.Config.Callback;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dexopt.
+ *
+ * It talks to other components (e.g., PowerManager) and dispatches tasks to dexopters.
+ *
+ * @hide
+ */
+public class DexoptHelper {
+    private static final String TAG = "DexoptHelper";
+
+    /**
+     * Timeout of the wake lock. This is required by AndroidLint, but we set it to a very large
+     * value so that it should normally never triggered.
+     */
+    private static final long WAKE_LOCK_TIMEOUT_MS = TimeUnit.DAYS.toMillis(1);
+
+    @NonNull private final Injector mInjector;
+
+    public DexoptHelper(@NonNull Context context, @NonNull Config config) {
+        this(new Injector(context, config));
+    }
+
+    @VisibleForTesting
+    public DexoptHelper(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor) {
+        return dexopt(snapshot, packageNames, params, cancellationSignal, dexoptExecutor,
+                null /* progressCallbackExecutor */, null /* progressCallback */);
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor,
+            @Nullable Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        return dexoptPackages(
+                getPackageStates(snapshot, packageNames,
+                        (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0),
+                params, cancellationSignal, dexoptExecutor, progressCallbackExecutor,
+                progressCallback);
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    private DexoptResult dexoptPackages(@NonNull List<PackageState> pkgStates,
+            @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor,
+            @Nullable Consumer<OperationProgress> progressCallback) {
+        int callingUid = Binder.getCallingUid();
+        long identityToken = Binder.clearCallingIdentity();
+        PowerManager.WakeLock wakeLock = null;
+
+        try {
+            // Acquire a wake lock.
+            PowerManager powerManager = mInjector.getPowerManager();
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+            wakeLock.setWorkSource(new WorkSource(callingUid));
+            wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS);
+
+            List<CompletableFuture<PackageDexoptResult>> futures = new ArrayList<>();
+            for (PackageState pkgState : pkgStates) {
+                futures.add(CompletableFuture.supplyAsync(
+                        () -> dexoptPackage(pkgState, params, cancellationSignal), dexoptExecutor));
+            }
+
+            if (progressCallback != null) {
+                CompletableFuture.runAsync(() -> {
+                    progressCallback.accept(
+                            OperationProgress.create(0 /* current */, futures.size()));
+                }, progressCallbackExecutor);
+                AtomicInteger current = new AtomicInteger(0);
+                for (CompletableFuture<PackageDexoptResult> future : futures) {
+                    future.thenRunAsync(() -> {
+                        progressCallback.accept(OperationProgress.create(
+                                current.incrementAndGet(), futures.size()));
+                    }, progressCallbackExecutor);
+                }
+            }
+
+            List<PackageDexoptResult> results =
+                    futures.stream().map(Utils::getFuture).collect(Collectors.toList());
+
+            var result =
+                    DexoptResult.create(params.getCompilerFilter(), params.getReason(), results);
+
+            for (Callback<DexoptDoneCallback, Boolean> doneCallback :
+                    mInjector.getConfig().getDexoptDoneCallbacks()) {
+                boolean onlyIncludeUpdates = doneCallback.extra();
+                if (onlyIncludeUpdates) {
+                    List<PackageDexoptResult> filteredResults =
+                            results.stream()
+                                    .filter(PackageDexoptResult::hasUpdatedArtifacts)
+                                    .collect(Collectors.toList());
+                    if (!filteredResults.isEmpty()) {
+                        var resultForCallback = DexoptResult.create(
+                                params.getCompilerFilter(), params.getReason(), filteredResults);
+                        CompletableFuture.runAsync(() -> {
+                            doneCallback.get().onDexoptDone(resultForCallback);
+                        }, doneCallback.executor());
+                    }
+                } else {
+                    CompletableFuture.runAsync(() -> {
+                        doneCallback.get().onDexoptDone(result);
+                    }, doneCallback.executor());
+                }
+            }
+
+            return result;
+        } finally {
+            if (wakeLock != null) {
+                wakeLock.release();
+            }
+            Binder.restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link
+     * ArtManagerLocal#dexoptPackages}.
+     */
+    @NonNull
+    private PackageDexoptResult dexoptPackage(@NonNull PackageState pkgState,
+            @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) {
+        List<DexContainerFileDexoptResult> results = new ArrayList<>();
+        Supplier<PackageDexoptResult> createResult = ()
+                -> PackageDexoptResult.create(
+                        pkgState.getPackageName(), results, cancellationSignal.isCanceled());
+
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        if (!canDexoptPackage(pkgState)) {
+            return createResult.get();
+        }
+
+        if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+            // Throws if the split is not found.
+            PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
+        }
+
+        try {
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.get();
+                }
+
+                results.addAll(
+                        mInjector.getPrimaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.get();
+                }
+
+                results.addAll(
+                        mInjector.getSecondaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("An error occurred when calling artd", e);
+        }
+
+        return createResult.get();
+    }
+
+    private boolean canDexoptPackage(@NonNull PackageState pkgState) {
+        // getAppHibernationManager may return null here during boot time compilation, which will
+        // make this function return true incorrectly for packages that shouldn't be dexopted due to
+        // hibernation. Further discussion in comments in ArtManagerLocal.getDefaultPackages.
+        return Utils.canDexoptPackage(pkgState, mInjector.getAppHibernationManager());
+    }
+
+    @NonNull
+    private List<PackageState> getPackageStates(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, boolean includeDependencies) {
+        var pkgStates = new LinkedHashMap<String, PackageState>();
+        Set<String> visitedLibraries = new HashSet<>();
+        Queue<SharedLibrary> queue = new LinkedList<>();
+
+        Consumer<SharedLibrary> maybeEnqueue = library -> {
+            // The package name is not null if the library is an APK.
+            // TODO(jiakaiz): Support JAR libraries.
+            if (library.getPackageName() != null && !visitedLibraries.contains(library.getName())) {
+                visitedLibraries.add(library.getName());
+                queue.add(library);
+            }
+        };
+
+        for (String packageName : packageNames) {
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            Utils.getPackageOrThrow(pkgState);
+            pkgStates.put(packageName, pkgState);
+            if (includeDependencies && canDexoptPackage(pkgState)) {
+                for (SharedLibrary library : pkgState.getSharedLibraryDependencies()) {
+                    maybeEnqueue.accept(library);
+                }
+            }
+        }
+
+        SharedLibrary library;
+        while ((library = queue.poll()) != null) {
+            String packageName = library.getPackageName();
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            if (canDexoptPackage(pkgState)) {
+                pkgStates.put(packageName, pkgState);
+
+                // Note that `library.getDependencies()` is different from
+                // `pkgState.getUsesLibraries()`. Different libraries can belong to the same
+                // package. `pkgState.getUsesLibraries()` returns a union of dependencies of
+                // libraries that belong to the same package, which is not what we want here.
+                // Therefore, this loop cannot be unified with the one above.
+                for (SharedLibrary dep : library.getDependencies()) {
+                    maybeEnqueue.accept(dep);
+                }
+            }
+        }
+
+        // `LinkedHashMap` guarantees deterministic order.
+        return new ArrayList<>(pkgStates.values());
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final Context mContext;
+        @NonNull private final Config mConfig;
+
+        Injector(@NonNull Context context, @NonNull Config config) {
+            mContext = context;
+            mConfig = config;
+
+            // Call the getters for the dependencies that aren't optional, to ensure correct
+            // initialization order.
+            getPowerManager();
+        }
+
+        @NonNull
+        PrimaryDexopter getPrimaryDexopter(@NonNull PackageState pkgState,
+                @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+                @NonNull CancellationSignal cancellationSignal) {
+            return new PrimaryDexopter(mContext, pkgState, pkg, params, cancellationSignal);
+        }
+
+        @NonNull
+        SecondaryDexopter getSecondaryDexopter(@NonNull PackageState pkgState,
+                @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+                @NonNull CancellationSignal cancellationSignal) {
+            return new SecondaryDexopter(mContext, pkgState, pkg, params, cancellationSignal);
+        }
+
+        /**
+         * Returns the registered AppHibernationManager instance.
+         *
+         * It may be null because ArtManagerLocal needs to be available early to compile packages at
+         * boot with {@link onBoot}, before the hibernation manager has been initialized. It should
+         * not be null for other dexopt calls.
+         */
+        @Nullable
+        public AppHibernationManager getAppHibernationManager() {
+            return mContext.getSystemService(AppHibernationManager.class);
+        }
+
+        @NonNull
+        public PowerManager getPowerManager() {
+            return Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
new file mode 100644
index 0000000..6e220c7
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
+    private static final String TAG = "Dexopter";
+
+    @NonNull protected final Injector mInjector;
+    @NonNull protected final PackageState mPkgState;
+    /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */
+    @NonNull protected final AndroidPackage mPkg;
+    @NonNull protected final DexoptParams mParams;
+    @NonNull protected final CancellationSignal mCancellationSignal;
+
+    protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        mInjector = injector;
+        mPkgState = pkgState;
+        mPkg = pkg;
+        mParams = params;
+        mCancellationSignal = cancellationSignal;
+        if (pkgState.getAppId() < 0) {
+            throw new IllegalStateException(
+                    "Package '" + pkgState.getPackageName() + "' has invalid app ID");
+        }
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link
+     * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String,
+     * DexoptParams)}.
+     */
+    @NonNull
+    public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException {
+        List<DexContainerFileDexoptResult> results = new ArrayList<>();
+
+        for (DexInfoType dexInfo : getDexInfoList()) {
+            ProfilePath profile = null;
+            boolean succeeded = true;
+            try {
+                if (!isDexoptable(dexInfo)) {
+                    continue;
+                }
+
+                String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
+                if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
+                    continue;
+                }
+
+                boolean needsToBeShared = needsToBeShared(dexInfo);
+                boolean isOtherReadable = true;
+                // If true, implies that the profile has changed since the last compilation.
+                boolean profileMerged = false;
+                if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
+                    if (needsToBeShared) {
+                        profile = initReferenceProfile(dexInfo);
+                    } else {
+                        Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo);
+                        if (pair != null) {
+                            profile = pair.first;
+                            isOtherReadable = pair.second;
+                        }
+                        ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
+                        if (mergedProfile != null) {
+                            if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                                mInjector.getArtd().deleteProfile(profile);
+                            }
+                            profile = mergedProfile;
+                            isOtherReadable = false;
+                            profileMerged = true;
+                        }
+                    }
+                    if (profile == null) {
+                        // A profile guided dexopt with no profile is essentially 'verify',
+                        // and dex2oat already makes this transformation. However, we need to
+                        // explicitly make this transformation here to guide the later decisions
+                        // such as whether the artifacts can be public and whether dexopt is needed.
+                        compilerFilter = needsToBeShared
+                                ? ReasonMapping.getCompilerFilterForShared()
+                                : "verify";
+                    }
+                }
+                boolean isProfileGuidedCompilerFilter =
+                        DexFile.isProfileGuidedCompilerFilter(compilerFilter);
+                Utils.check(isProfileGuidedCompilerFilter == (profile != null));
+
+                boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
+                        && isDexFilePublic(dexInfo);
+                Utils.check(Utils.implies(needsToBeShared, canBePublic));
+                PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
+
+                DexoptOptions dexoptOptions =
+                        getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter);
+
+                for (Abi abi : getAllAbis(dexInfo)) {
+                    @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED;
+                    long wallTimeMs = 0;
+                    long cpuTimeMs = 0;
+                    long sizeBytes = 0;
+                    long sizeBeforeBytes = 0;
+                    boolean isSkippedDueToStorageLow = false;
+                    try {
+                        var target = DexoptTarget.<DexInfoType>builder()
+                                             .setDexInfo(dexInfo)
+                                             .setIsa(abi.isa())
+                                             .setIsInDalvikCache(isInDalvikCache())
+                                             .setCompilerFilter(compilerFilter)
+                                             .build();
+                        var options = GetDexoptNeededOptions.builder()
+                                              .setProfileMerged(profileMerged)
+                                              .setFlags(mParams.getFlags())
+                                              .setNeedsToBePublic(needsToBeShared)
+                                              .build();
+
+                        GetDexoptNeededResult getDexoptNeededResult =
+                                getDexoptNeeded(target, options);
+
+                        if (!getDexoptNeededResult.isDexoptNeeded) {
+                            continue;
+                        }
+
+                        try {
+                            // `StorageManager.getAllocatableBytes` returns (free space + space used
+                            // by clearable cache - low storage threshold). Since we only compare
+                            // the result with 0, the clearable cache doesn't make a difference.
+                            // When the free space is below the threshold, there should be no
+                            // clearable cache left because system cleans up cache every minute.
+                            if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0
+                                    && mInjector.getStorageManager().getAllocatableBytes(
+                                               mPkg.getStorageUuid())
+                                            <= 0) {
+                                isSkippedDueToStorageLow = true;
+                                continue;
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+                        }
+
+                        IArtdCancellationSignal artdCancellationSignal =
+                                mInjector.getArtd().createCancellationSignal();
+                        mCancellationSignal.setOnCancelListener(() -> {
+                            try {
+                                artdCancellationSignal.cancel();
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "An error occurred when sending a cancellation signal",
+                                        e);
+                            }
+                        });
+
+                        ArtdDexoptResult dexoptResult = dexoptFile(target, profile,
+                                getDexoptNeededResult, permissionSettings,
+                                mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
+                        status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED
+                                                        : DexoptResult.DEXOPT_PERFORMED;
+                        wallTimeMs = dexoptResult.wallTimeMs;
+                        cpuTimeMs = dexoptResult.cpuTimeMs;
+                        sizeBytes = dexoptResult.sizeBytes;
+                        sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
+
+                        if (status == DexoptResult.DEXOPT_CANCELLED) {
+                            return results;
+                        }
+                    } catch (ServiceSpecificException e) {
+                        // Log the error and continue.
+                        Log.e(TAG,
+                                String.format("Failed to dexopt [packageName = %s, dexPath = %s, "
+                                                + "isa = %s, classLoaderContext = %s]",
+                                        mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
+                                        dexInfo.classLoaderContext()),
+                                e);
+                        status = DexoptResult.DEXOPT_FAILED;
+                    } finally {
+                        results.add(DexContainerFileDexoptResult.create(dexInfo.dexPath(),
+                                abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow));
+                        if (status != DexoptResult.DEXOPT_SKIPPED
+                                && status != DexoptResult.DEXOPT_PERFORMED) {
+                            succeeded = false;
+                        }
+                        // Make sure artd does not leak even if the caller holds
+                        // `mCancellationSignal` forever.
+                        mCancellationSignal.setOnCancelListener(null);
+                    }
+                }
+
+                if (profile != null && succeeded) {
+                    if (profile.getTag() == ProfilePath.tmpProfilePath) {
+                        // Commit the profile only if dexopt succeeds.
+                        if (commitProfileChanges(profile.getTmpProfilePath())) {
+                            profile = null;
+                        }
+                    }
+                    if (profileMerged) {
+                        // Note that this is just an optimization, to reduce the amount of data that
+                        // the runtime writes on every profile save. The profile merge result on the
+                        // next run won't change regardless of whether the cleanup is done or not
+                        // because profman only looks at the diff.
+                        // A caveat is that it may delete more than what has been merged, if the
+                        // runtime writes additional entries between the merge and the cleanup, but
+                        // this is fine because the runtime writes all JITed classes and methods on
+                        // every save and the additional entries will likely be written back on the
+                        // next save.
+                        cleanupCurProfiles(dexInfo);
+                    }
+                }
+            } finally {
+                if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                    mInjector.getArtd().deleteProfile(profile);
+                }
+            }
+        }
+
+        return results;
+    }
+
+    @NonNull
+    private String adjustCompilerFilter(
+            @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
+        if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
+            String systemUiCompilerFilter = getSystemUiCompilerFilter();
+            if (!systemUiCompilerFilter.isEmpty()) {
+                return systemUiCompilerFilter;
+            }
+        }
+
+        // We force vmSafeMode on debuggable apps as well:
+        //  - the runtime ignores their compiled code
+        //  - they generally have lots of methods that could make the compiler used run out of
+        //    memory (b/130828957)
+        // Note that forcing the compiler filter here applies to all compilations (even if they
+        // are done via adb shell commands). This is okay because the runtime will ignore the
+        // compiled code anyway.
+        if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
+            return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
+        }
+
+        // We cannot do AOT compilation if we don't have a valid class loader context.
+        if (dexInfo.classLoaderContext() == null) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        // This application wants to use the embedded dex in the APK, rather than extracted or
+        // locally compiled variants, so we only verify it.
+        // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
+        // won't extract the dex code because the APK is uncompressed, and the assumption is that
+        // such applications always use uncompressed APKs.
+        if (mPkg.isUseEmbeddedDex()) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        return targetCompilerFilter;
+    }
+
+    @NonNull
+    private String getSystemUiCompilerFilter() {
+        String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
+        if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
+            throw new IllegalStateException(
+                    "Got invalid compiler filter '" + compilerFilter + "' for System UI");
+        }
+        return compilerFilter;
+    }
+
+    /**
+     * Gets the existing reference profile if exists, or initializes a reference profile from an
+     * external profile.
+     *
+     * @return A pair where the first element is the found or initialized profile, and the second
+     *         element is true if the profile is readable by others. Or null if there is no
+     *         reference profile or external profile to use.
+     */
+    @Nullable
+    private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException {
+        ProfilePath refProfile = buildRefProfilePath(dexInfo);
+        try {
+            if (mInjector.getArtd().isProfileUsable(refProfile, dexInfo.dexPath())) {
+                boolean isOtherReadable = mInjector.getArtd().getProfileVisibility(refProfile)
+                        == FileVisibility.OTHER_READABLE;
+                return Pair.create(refProfile, isOtherReadable);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to use the existing reference profile "
+                            + AidlUtils.toString(refProfile),
+                    e);
+        }
+
+        ProfilePath initializedProfile = initReferenceProfile(dexInfo);
+        return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+    }
+
+    @NonNull
+    private DexoptOptions getDexoptOptions(
+            @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
+        DexoptOptions dexoptOptions = new DexoptOptions();
+        dexoptOptions.compilationReason = mParams.getReason();
+        dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
+        dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
+        // Generating a meaningful app image needs a profile to determine what to include in the
+        // image. Otherwise, the app image will be nearly empty.
+        dexoptOptions.generateAppImage =
+                isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled();
+        dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
+        return dexoptOptions;
+    }
+
+    private boolean isAlwaysDebuggable() {
+        return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
+    }
+
+    private boolean isAppImageEnabled() {
+        return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
+    }
+
+    private boolean isHiddenApiPolicyEnabled() {
+        return mPkgState.getHiddenApiEnforcementPolicy()
+                != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+    }
+
+    @NonNull
+    GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        int dexoptTrigger = getDexoptTrigger(target, options);
+
+        // The result should come from artd even if all the bits of `dexoptTrigger` are set
+        // because the result also contains information about the usable VDEX file.
+        // Note that the class loader context can be null. In that case, we intentionally pass the
+        // null value down to lower levels to indicate that the class loader context check should be
+        // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
+        GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+                target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+                target.compilerFilter(), dexoptTrigger);
+
+        return result;
+    }
+
+    int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
+                    | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                    | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        }
+
+        if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        if (options.profileMerged()) {
+            dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
+        }
+
+        ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath(
+                target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
+
+        if (options.needsToBePublic()
+                && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
+                        == FileVisibility.NOT_OTHER_READABLE) {
+            // Typically, this happens after an app starts being used by other apps.
+            // This case should be the same as force as we have no choice but to trigger a new
+            // dexopt.
+            dexoptTrigger |=
+                    DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        return dexoptTrigger;
+    }
+
+    private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
+            @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
+            @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
+            throws RemoteException {
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
+                target.isa(), target.isInDalvikCache(), permissionSettings);
+
+        VdexPath inputVdex =
+                getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
+
+        DexMetadataPath dmFile = getDmFile(target.dexInfo());
+        if (dmFile != null
+                && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
+            // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
+            // "install-dm").
+            // Note that this only applies to reasons for app install because the goal is to give
+            // Play a signal that a DM file is downloaded at install time. We actually pass the DM
+            // file regardless of the compilation reason, but we don't append a suffix when the
+            // compilation reason is not a reason for app install.
+            // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
+            // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
+            // compilation reason can still be "install-dm" even if dex2oat left all contents of the
+            // DM file unused or an empty DM file is passed to dex2oat.
+            dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
+        }
+
+        return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+                target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+                dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+    }
+
+    @Nullable
+    private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull String dexPath, @NonNull String isa) {
+        if (!getDexoptNeededResult.isVdexUsable) {
+            return null;
+        }
+        switch (getDexoptNeededResult.artifactsLocation) {
+            case ArtifactsLocation.DALVIK_CACHE:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */));
+            case ArtifactsLocation.NEXT_TO_DEX:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
+            case ArtifactsLocation.DM:
+                // The DM file is passed to dex2oat as a separate flag whenever it exists.
+                return null;
+            default:
+                // This should never happen as the value is got from artd.
+                throw new IllegalStateException(
+                        "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
+        }
+    }
+
+    @Nullable
+    private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
+        DexMetadataPath path = buildDmPath(dexInfo);
+        if (path == null) {
+            return null;
+        }
+        try {
+            if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
+                return path;
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
+        }
+        return null;
+    }
+
+    private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
+        try {
+            mInjector.getArtd().commitTmpProfile(profile);
+            return true;
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
+                    e);
+            return false;
+        }
+    }
+
+    @Nullable
+    private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
+            @Nullable ProfilePath referenceProfile) throws RemoteException {
+        OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
+
+        try {
+            if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
+                        List.of(dexInfo.dexPath()), new MergeProfileOptions())) {
+                return ProfilePath.tmpProfilePath(output.profilePath);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
+                    e);
+        }
+
+        return null;
+    }
+
+    private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
+        for (ProfilePath profile : getCurProfiles(dexInfo)) {
+            mInjector.getArtd().deleteProfile(profile);
+        }
+    }
+
+    // Methods to be implemented by child classes.
+
+    /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
+    protected abstract boolean isInDalvikCache();
+
+    /** Returns information about all dex files. */
+    @NonNull protected abstract List<DexInfoType> getDexInfoList();
+
+    /** Returns true if the given dex file should be dexopted. */
+    protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the artifacts should be shared with other apps. Note that this must imply
+     * {@link #isDexFilePublic(DexInfoType)}.
+     */
+    protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+     * (S_IROTH).
+     */
+    protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns a reference profile initialized from an external profile (e.g., a DM profile) if
+     * one exists, or null otherwise.
+     */
+    @Nullable
+    protected abstract ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException;
+
+    /** Returns the permission settings to use for the artifacts of the given dex file. */
+    @NonNull
+    protected abstract PermissionSettings getPermissionSettings(
+            @NonNull DexInfoType dexInfo, boolean canBePublic);
+
+    /** Returns all ABIs that the given dex file should be compiled for. */
+    @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
+
+    /** Returns the path to the reference profile of the given dex file. */
+    @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo);
+
+    /** Returns true if app image (--app-image-fd) is allowed. */
+    protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the data structure that represents the temporary profile to use during processing.
+     */
+    @NonNull
+    protected abstract OutputProfile buildOutputProfile(
+            @NonNull DexInfoType dexInfo, boolean isPublic);
+
+    /** Returns the paths to the current profiles of the given dex file. */
+    @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
+     * should be passed.
+     */
+    @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
+
+    @AutoValue
+    abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
+        abstract @NonNull DexInfoType dexInfo();
+        abstract @NonNull String isa();
+        abstract boolean isInDalvikCache();
+        abstract @NonNull String compilerFilter();
+
+        static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
+            return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder<DexInfoType extends DetailedDexInfo> {
+            abstract Builder setDexInfo(@NonNull DexInfoType value);
+            abstract Builder setIsa(@NonNull String value);
+            abstract Builder setIsInDalvikCache(boolean value);
+            abstract Builder setCompilerFilter(@NonNull String value);
+            abstract DexoptTarget<DexInfoType> build();
+        }
+    }
+
+    @AutoValue
+    abstract static class GetDexoptNeededOptions {
+        abstract @DexoptFlags int flags();
+        abstract boolean profileMerged();
+        abstract boolean needsToBePublic();
+
+        static Builder builder() {
+            return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            abstract Builder setFlags(@DexoptFlags int value);
+            abstract Builder setProfileMerged(boolean value);
+            abstract Builder setNeedsToBePublic(boolean value);
+            abstract GetDexoptNeededOptions build();
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public static class Injector {
+        @NonNull private final Context mContext;
+
+        public Injector(@NonNull Context context) {
+            mContext = context;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            getUserManager();
+            getDexUseManager();
+            getStorageManager();
+            ArtModuleServiceInitializer.getArtModuleServiceManager();
+        }
+
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return packageName.equals(mContext.getString(R.string.config_systemUi));
+        }
+
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/DumpHelper.java b/libartservice/service/java/com/android/server/art/DumpHelper.java
new file mode 100644
index 0000000..0de2c1b
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DumpHelper.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.VMRuntime;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dump.
+ *
+ * @hide
+ */
+public class DumpHelper {
+    @NonNull private final Injector mInjector;
+
+    public DumpHelper(@NonNull ArtManagerLocal artManagerLocal) {
+        this(new Injector(artManagerLocal));
+    }
+
+    @VisibleForTesting
+    public DumpHelper(@NonNull Injector injector) {
+        mInjector = injector;
+    }
+
+    /** Handles {@link ArtManagerLocal#dump(PrintWriter, PackageManagerLocal.FilteredSnapshot)}. */
+    public void dump(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        for (PackageState pkgState : snapshot.getPackageStates().values()) {
+            dumpPackage(pw, snapshot, pkgState);
+        }
+    }
+
+    /**
+     * Handles {@link
+     * ArtManagerLocal#dumpPackage(PrintWriter, PackageManagerLocal.FilteredSnapshot, String)}.
+     */
+    public void dumpPackage(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull PackageState pkgState) {
+        // An APEX has a uid of -1.
+        // TODO(b/256637152): Consider using `isApex` instead.
+        if (pkgState.getAppId() <= 0 || pkgState.getAndroidPackage() == null) {
+            return;
+        }
+
+        var ipw = new IndentingPrintWriter(pw);
+
+        String packageName = pkgState.getPackageName();
+        ipw.printf("[%s]\n", packageName);
+
+        List<DexContainerFileDexoptStatus> statuses =
+                mInjector.getArtManagerLocal()
+                        .getDexoptStatus(snapshot, packageName)
+                        .getDexContainerFileDexoptStatuses();
+        Map<String, SecondaryDexInfo> secondaryDexInfoByDexPath =
+                mInjector.getDexUseManager()
+                        .getSecondaryDexInfo(packageName)
+                        .stream()
+                        .collect(Collectors.toMap(SecondaryDexInfo::dexPath, Function.identity()));
+
+        // Use LinkedHashMap to keep the order.
+        var primaryStatusesByDexPath =
+                new LinkedHashMap<String, List<DexContainerFileDexoptStatus>>();
+        var secondaryStatusesByDexPath =
+                new LinkedHashMap<String, List<DexContainerFileDexoptStatus>>();
+        for (DexContainerFileDexoptStatus fileStatus : statuses) {
+            if (fileStatus.isPrimaryDex()) {
+                primaryStatusesByDexPath
+                        .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>())
+                        .add(fileStatus);
+            } else if (secondaryDexInfoByDexPath.containsKey(fileStatus.getDexContainerFile())) {
+                // The condition above is false only if a change occurs between
+                // `getDexoptStatus` and `getSecondaryDexInfo`, which is an edge case.
+                secondaryStatusesByDexPath
+                        .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>())
+                        .add(fileStatus);
+            }
+        }
+
+        ipw.increaseIndent();
+        for (List<DexContainerFileDexoptStatus> fileStatuses : primaryStatusesByDexPath.values()) {
+            dumpPrimaryDex(ipw, fileStatuses, packageName);
+        }
+        if (!secondaryStatusesByDexPath.isEmpty()) {
+            ipw.println("known secondary dex files:");
+            ipw.increaseIndent();
+            for (Map.Entry<String, List<DexContainerFileDexoptStatus>> entry :
+                    secondaryStatusesByDexPath.entrySet()) {
+                dumpSecondaryDex(ipw, entry.getValue(), packageName,
+                        secondaryDexInfoByDexPath.get(entry.getKey()));
+            }
+            ipw.decreaseIndent();
+        }
+        ipw.decreaseIndent();
+    }
+
+    private void dumpPrimaryDex(@NonNull IndentingPrintWriter ipw,
+            List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName) {
+        String dexPath = fileStatuses.get(0).getDexContainerFile();
+        ipw.printf("path: %s\n", dexPath);
+        ipw.increaseIndent();
+        dumpFileStatuses(ipw, fileStatuses);
+        dumpUsedByOtherApps(ipw,
+                mInjector.getDexUseManager().getPrimaryDexLoaders(packageName, dexPath),
+                packageName);
+        ipw.decreaseIndent();
+    }
+
+    private void dumpSecondaryDex(@NonNull IndentingPrintWriter ipw,
+            List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName,
+            @NonNull SecondaryDexInfo info) {
+        String dexPath = fileStatuses.get(0).getDexContainerFile();
+        ipw.println(dexPath);
+        ipw.increaseIndent();
+        dumpFileStatuses(ipw, fileStatuses);
+        ipw.printf("class loader context: %s\n", info.displayClassLoaderContext());
+        dumpUsedByOtherApps(ipw, info.loaders(), packageName);
+        ipw.decreaseIndent();
+    }
+
+    private void dumpFileStatuses(
+            @NonNull IndentingPrintWriter ipw, List<DexContainerFileDexoptStatus> fileStatuses) {
+        for (DexContainerFileDexoptStatus fileStatus : fileStatuses) {
+            ipw.printf("%s: [status=%s] [reason=%s]\n",
+                    VMRuntime.getInstructionSet(fileStatus.getAbi()),
+                    fileStatus.getCompilerFilter(), fileStatus.getCompilationReason());
+        }
+    }
+
+    private void dumpUsedByOtherApps(@NonNull IndentingPrintWriter ipw,
+            @NonNull Set<DexLoader> dexLoaders, @NonNull String packageName) {
+        List<DexLoader> otherApps =
+                dexLoaders.stream()
+                        .filter(loader -> DexUseManagerLocal.isLoaderOtherApp(loader, packageName))
+                        .collect(Collectors.toList());
+        if (!otherApps.isEmpty()) {
+            ipw.printf("used by other apps: [%s]\n",
+                    otherApps.stream()
+                            .map(loader
+                                    -> loader.loadingPackageName()
+                                            + (loader.isolatedProcess() ? " (isolated)" : ""))
+                            .collect(Collectors.joining(", ")));
+        }
+    }
+
+    /** Injector pattern for testing purpose. */
+    @VisibleForTesting
+    public static class Injector {
+        @NonNull private final ArtManagerLocal mArtManagerLocal;
+
+        Injector(@NonNull ArtManagerLocal artManagerLocal) {
+            mArtManagerLocal = artManagerLocal;
+        }
+
+        @NonNull
+        public ArtManagerLocal getArtManagerLocal() {
+            return mArtManagerLocal;
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java
new file mode 100644
index 0000000..b717786
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * A minimal copy of the hidden {@link android.util.IndentingPrintWriter}.
+ *
+ * TODO(b/264968147): Avoid the duplication.
+ *
+ * @hide
+ */
+public class IndentingPrintWriter extends PrintWriter {
+    private final String mSingleIndent;
+
+    /** Mutable version of current indent */
+    private StringBuilder mIndentBuilder = new StringBuilder();
+    /** Cache of current {@link #mIndentBuilder} value */
+    private char[] mCurrentIndent;
+    /** Length of current line being built, excluding any indent */
+    private int mCurrentLength;
+
+    /**
+     * Flag indicating if we're currently sitting on an empty line, and that
+     * next write should be prefixed with the current indent.
+     */
+    private boolean mEmptyLine = true;
+
+    private char[] mSingleChar = new char[1];
+
+    public IndentingPrintWriter(@NonNull Writer writer) {
+        super(writer);
+        mSingleIndent = "  ";
+    }
+
+    /**
+     * Increases the indent starting with the next printed line.
+     */
+    @NonNull
+    public IndentingPrintWriter increaseIndent() {
+        mIndentBuilder.append(mSingleIndent);
+        mCurrentIndent = null;
+        return this;
+    }
+
+    /**
+     * Decreases the indent starting with the next printed line.
+     */
+    @NonNull
+    public IndentingPrintWriter decreaseIndent() {
+        mIndentBuilder.delete(0, mSingleIndent.length());
+        mCurrentIndent = null;
+        return this;
+    }
+
+    @Override
+    public void println() {
+        write('\n');
+    }
+
+    @Override
+    public void write(int c) {
+        mSingleChar[0] = (char) c;
+        write(mSingleChar, 0, 1);
+    }
+
+    @Override
+    public void write(@NonNull String s, int off, int len) {
+        final char[] buf = new char[len];
+        s.getChars(off, len - off, buf, 0);
+        write(buf, 0, len);
+    }
+
+    @Override
+    public void write(@NonNull char[] buf, int offset, int count) {
+        final int indentLength = mIndentBuilder.length();
+        final int bufferEnd = offset + count;
+        int lineStart = offset;
+        int lineEnd = offset;
+
+        // March through incoming buffer looking for newlines
+        while (lineEnd < bufferEnd) {
+            char ch = buf[lineEnd++];
+            mCurrentLength++;
+            if (ch == '\n') {
+                maybeWriteIndent();
+                super.write(buf, lineStart, lineEnd - lineStart);
+                lineStart = lineEnd;
+                mEmptyLine = true;
+                mCurrentLength = 0;
+            }
+        }
+
+        if (lineStart != lineEnd) {
+            maybeWriteIndent();
+            super.write(buf, lineStart, lineEnd - lineStart);
+        }
+    }
+
+    private void maybeWriteIndent() {
+        if (mEmptyLine) {
+            mEmptyLine = false;
+            if (mIndentBuilder.length() != 0) {
+                if (mCurrentIndent == null) {
+                    mCurrentIndent = mIndentBuilder.toString().toCharArray();
+                }
+                super.write(mCurrentIndent, 0, mCurrentIndent.length);
+            }
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
new file mode 100644
index 0000000..2f441e5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** @hide */
+public class PrimaryDexUtils {
+    public static final String PROFILE_PRIMARY = "primary";
+    private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName();
+
+    /**
+     * Returns the basic information about all primary dex files belonging to the package. The
+     * return value is a list where the entry at index 0 is the information about the base APK, and
+     * the entry at index i is the information about the (i-1)-th split APK.
+     */
+    @NonNull
+    public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackage pkg) {
+        return getDexInfoImpl(pkg)
+                .stream()
+                .map(builder -> builder.build())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Same as above, but requires {@link PackageState} in addition, and returns the detailed
+     * information, including the class loader context.
+     */
+    @NonNull
+    public static List<DetailedPrimaryDexInfo> getDetailedDexInfo(
+            @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
+        return getDetailedDexInfoImpl(pkgState, pkg)
+                .stream()
+                .map(builder -> builder.buildDetailed())
+                .collect(Collectors.toList());
+    }
+
+    /** Returns the basic information about a dex file specified by {@code splitName}. */
+    @NonNull
+    public static PrimaryDexInfo getDexInfoBySplitName(
+            @NonNull AndroidPackage pkg, @Nullable String splitName) {
+        if (splitName == null) {
+            return getDexInfo(pkg).get(0);
+        } else {
+            return getDexInfo(pkg)
+                    .stream()
+                    .filter(info -> splitName.equals(info.splitName()))
+                    .findFirst()
+                    .orElseThrow(() -> {
+                        return new IllegalArgumentException(
+                                String.format("Split '%s' not found", splitName));
+                    });
+        }
+    }
+
+    @NonNull
+    private static List<PrimaryDexInfoBuilder> getDexInfoImpl(@NonNull AndroidPackage pkg) {
+        List<PrimaryDexInfoBuilder> dexInfos = new ArrayList<>();
+
+        for (var split : pkg.getSplits()) {
+            dexInfos.add(new PrimaryDexInfoBuilder(split));
+        }
+
+        return dexInfos;
+    }
+
+    @NonNull
+    private static List<PrimaryDexInfoBuilder> getDetailedDexInfoImpl(
+            @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
+        List<PrimaryDexInfoBuilder> dexInfos = getDexInfoImpl(pkg);
+
+        PrimaryDexInfoBuilder baseApk = dexInfos.get(0);
+        baseApk.mClassLoaderName = baseApk.mSplit.getClassLoaderName();
+        File baseDexFile = new File(baseApk.mSplit.getPath());
+        baseApk.mRelativeDexPath = baseDexFile.getName();
+
+        // Shared libraries are the dependencies of the base APK.
+        baseApk.mSharedLibrariesContext =
+                encodeSharedLibraries(pkgState.getSharedLibraryDependencies());
+
+        boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg);
+
+        for (int i = 1; i < dexInfos.size(); i++) {
+            var dexInfoBuilder = dexInfos.get(i);
+            File splitDexFile = new File(dexInfoBuilder.mSplit.getPath());
+            if (!splitDexFile.getParent().equals(baseDexFile.getParent())) {
+                throw new IllegalStateException(
+                        "Split APK and base APK are in different directories: "
+                        + splitDexFile.getParent() + " != " + baseDexFile.getParent());
+            }
+            dexInfoBuilder.mRelativeDexPath = splitDexFile.getName();
+            if (isIsolatedSplitLoading && dexInfoBuilder.mSplit.isHasCode()) {
+                dexInfoBuilder.mClassLoaderName = dexInfoBuilder.mSplit.getClassLoaderName();
+
+                List<AndroidPackageSplit> dependencies = dexInfoBuilder.mSplit.getDependencies();
+                if (!Utils.isEmpty(dependencies)) {
+                    // We only care about the first dependency because it is the parent split. The
+                    // rest are configuration splits, which we don't care.
+                    AndroidPackageSplit dependency = dependencies.get(0);
+                    for (var dexInfo : dexInfos) {
+                        if (Objects.equals(dexInfo.mSplit, dependency)) {
+                            dexInfoBuilder.mSplitDependency = dexInfo;
+                            break;
+                        }
+                    }
+
+                    if (dexInfoBuilder.mSplitDependency == null) {
+                        throw new IllegalStateException(
+                                "Split dependency not found for " + splitDexFile);
+                    }
+                }
+            }
+        }
+
+        if (isIsolatedSplitLoading) {
+            computeClassLoaderContextsIsolated(dexInfos);
+        } else {
+            computeClassLoaderContexts(dexInfos);
+        }
+
+        return dexInfos;
+    }
+
+    /**
+     * Computes class loader context for an app that didn't request isolated split loading. Stores
+     * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+     *
+     * In this case, all the splits will be loaded in the base apk class loader (in the order of
+     * their definition).
+     *
+     * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK is
+     * `CLN[base.apk, split_0.apk, ..., split_n-1.apk]{shared-libraries}`; where `CLN` is the
+     * class loader name for the base APK.
+     */
+    private static void computeClassLoaderContexts(@NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+        String baseClassLoaderName = dexInfos.get(0).mClassLoaderName;
+        String sharedLibrariesContext = dexInfos.get(0).mSharedLibrariesContext;
+        List<String> classpath = new ArrayList<>();
+        for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+            if (dexInfo.mSplit.isHasCode()) {
+                dexInfo.mClassLoaderContext = encodeClassLoader(baseClassLoaderName, classpath,
+                        null /* parentContext */, sharedLibrariesContext);
+            }
+            // Note that the splits with no code are not removed from the classpath computation.
+            // I.e., split_n might get the split_n-1 in its classpath dependency even if split_n-1
+            // has no code.
+            // The splits with no code do not matter for the runtime which ignores APKs without code
+            // when doing the classpath checks. As such we could actually filter them but we don't
+            // do it in order to keep consistency with how the apps are loaded.
+            classpath.add(dexInfo.mRelativeDexPath);
+        }
+    }
+
+    /**
+     * Computes class loader context for an app that requested for isolated split loading. Stores
+     * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+     *
+     * In this case, each split will be loaded with a separate class loader, whose context is a
+     * chain formed from inter-split dependencies.
+     *
+     * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK that
+     * depends on the base APK is `CLN_n[];CLN[base.apk]{shared-libraries}`; the CLC for the n-th
+     * split APK that depends on the m-th split APK is
+     * `CLN_n[];CLN_m[split_m.apk];...;CLN[base.apk]{shared-libraries}`; where `CLN` is the base
+     * class loader name for the base APK, `CLN_i` is the class loader name for the i-th split APK,
+     * and `...` represents the ancestors along the dependency chain.
+     *
+     * Specially, if a split does not have any dependency, the CLC for it is `CLN_n[]`.
+     */
+    private static void computeClassLoaderContextsIsolated(
+            @NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+        for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+            if (dexInfo.mSplit.isHasCode()) {
+                dexInfo.mClassLoaderContext = encodeClassLoader(dexInfo.mClassLoaderName,
+                        null /* classpath */, getParentContextRecursive(dexInfo),
+                        dexInfo.mSharedLibrariesContext);
+            }
+        }
+    }
+
+    /**
+     * Computes the parent class loader context, recursively. Caches results in {@link
+     * PrimaryDexInfoBuilder#mContextForChildren}.
+     */
+    @Nullable
+    private static String getParentContextRecursive(@NonNull PrimaryDexInfoBuilder dexInfo) {
+        if (dexInfo.mSplitDependency == null) {
+            return null;
+        }
+        PrimaryDexInfoBuilder parent = dexInfo.mSplitDependency;
+        if (parent.mContextForChildren == null) {
+            parent.mContextForChildren =
+                    encodeClassLoader(parent.mClassLoaderName, List.of(parent.mRelativeDexPath),
+                            getParentContextRecursive(parent), parent.mSharedLibrariesContext);
+        }
+        return parent.mContextForChildren;
+    }
+
+    /**
+     * Returns class loader context in the format of
+     * `CLN[classpath...]{share-libraries};parent-context`, where `CLN` is the class loader name.
+     */
+    @NonNull
+    private static String encodeClassLoader(@Nullable String classLoaderName,
+            @Nullable List<String> classpath, @Nullable String parentContext,
+            @Nullable String sharedLibrariesContext) {
+        StringBuilder classLoaderContext = new StringBuilder();
+
+        classLoaderContext.append(encodeClassLoaderName(classLoaderName));
+
+        classLoaderContext.append(
+                "[" + (classpath != null ? String.join(":", classpath) : "") + "]");
+
+        if (!TextUtils.isEmpty(sharedLibrariesContext)) {
+            classLoaderContext.append(sharedLibrariesContext);
+        }
+
+        if (!TextUtils.isEmpty(parentContext)) {
+            classLoaderContext.append(";" + parentContext);
+        }
+
+        return classLoaderContext.toString();
+    }
+
+    @NonNull
+    private static String encodeClassLoaderName(@Nullable String classLoaderName) {
+        // `PathClassLoader` and `DexClassLoader` are grouped together because they have the same
+        // behavior. For null values we default to "PCL". This covers the case where a package does
+        // not specify any value for its class loader.
+        if (classLoaderName == null || PathClassLoader.class.getName().equals(classLoaderName)
+                || DexClassLoader.class.getName().equals(classLoaderName)) {
+            return "PCL";
+        } else if (DelegateLastClassLoader.class.getName().equals(classLoaderName)) {
+            return "DLC";
+        } else {
+            throw new IllegalStateException("Unsupported classLoaderName: " + classLoaderName);
+        }
+    }
+
+    /**
+     * Returns shared libraries context in the format of
+     * `{PCL[library_1_dex_1.jar:library_1_dex_2.jar:...]{library_1-dependencies}#PCL[
+     *     library_1_dex_2.jar:library_2_dex_2.jar:...]{library_2-dependencies}#...}`.
+     */
+    @Nullable
+    private static String encodeSharedLibraries(@Nullable List<SharedLibrary> sharedLibraries) {
+        if (Utils.isEmpty(sharedLibraries)) {
+            return null;
+        }
+        return sharedLibraries.stream()
+                .map(library
+                        -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(),
+                                null /* parentContext */,
+                                encodeSharedLibraries(library.getDependencies())))
+                .collect(Collectors.joining("#", "{", "}"));
+    }
+
+    public static boolean isIsolatedSplitLoading(@NonNull AndroidPackage pkg) {
+        return pkg.isIsolatedSplitLoading() && pkg.getSplits().size() > 1;
+    }
+
+    @NonNull
+    public static ProfilePath buildRefProfilePath(
+            @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+        String profileName = getProfileName(dexInfo.splitName());
+        return AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName);
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfile(@NonNull PackageState pkgState,
+            @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic) {
+        String profileName = getProfileName(dexInfo.splitName());
+        return AidlUtils.buildOutputProfileForPrimary(
+                pkgState.getPackageName(), profileName, uid, gid, isPublic);
+    }
+
+    @NonNull
+    public static List<ProfilePath> getCurProfiles(@NonNull UserManager userManager,
+            @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+        List<ProfilePath> profiles = new ArrayList<>();
+        for (UserHandle handle : userManager.getUserHandles(true /* excludeDying */)) {
+            int userId = handle.getIdentifier();
+            PackageUserState userState = pkgState.getStateForUser(handle);
+            if (userState.isInstalled()) {
+                profiles.add(AidlUtils.buildProfilePathForPrimaryCur(
+                        userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName())));
+            }
+        }
+        return profiles;
+    }
+
+    @NonNull
+    public static String getProfileName(@Nullable String splitName) {
+        return splitName == null ? PROFILE_PRIMARY : splitName + ".split";
+    }
+
+    /** Basic information about a primary dex file (either the base APK or a split APK). */
+    @Immutable
+    public static class PrimaryDexInfo {
+        private final @NonNull AndroidPackageSplit mSplit;
+
+        PrimaryDexInfo(@NonNull AndroidPackageSplit split) {
+            mSplit = split;
+        }
+
+        /** The path to the dex file. */
+        public @NonNull String dexPath() {
+            return mSplit.getPath();
+        }
+
+        /** True if the dex file has code. */
+        public boolean hasCode() {
+            return mSplit.isHasCode();
+        }
+
+        /** The name of the split, or null for base APK. */
+        public @Nullable String splitName() {
+            return mSplit.getName();
+        }
+    }
+
+    /**
+     * Detailed information about a primary dex file (either the base APK or a split APK). It
+     * contains the class loader context in addition to what is in {@link PrimaryDexInfo}, but
+     * producing it requires {@link PackageState}.
+     */
+    @Immutable
+    public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo {
+        private final @Nullable String mClassLoaderContext;
+
+        DetailedPrimaryDexInfo(
+                @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) {
+            super(split);
+            mClassLoaderContext = classLoaderContext;
+        }
+
+        /**
+         * A string describing the structure of the class loader that the dex file is loaded with.
+         */
+        public @Nullable String classLoaderContext() {
+            return mClassLoaderContext;
+        }
+    }
+
+    private static class PrimaryDexInfoBuilder {
+        @NonNull AndroidPackageSplit mSplit;
+        @Nullable String mRelativeDexPath = null;
+        @Nullable String mClassLoaderContext = null;
+        @Nullable String mClassLoaderName = null;
+        @Nullable PrimaryDexInfoBuilder mSplitDependency = null;
+        /** The class loader context of the shared libraries. Only applicable for the base APK. */
+        @Nullable String mSharedLibrariesContext = null;
+        /** The class loader context for children to use when this dex file is used as a parent. */
+        @Nullable String mContextForChildren = null;
+
+        PrimaryDexInfoBuilder(@NonNull AndroidPackageSplit split) {
+            mSplit = split;
+        }
+
+        PrimaryDexInfo build() {
+            return new PrimaryDexInfo(mSplit);
+        }
+
+        DetailedPrimaryDexInfo buildDetailed() {
+            return new DetailedPrimaryDexInfo(mSplit, mClassLoaderContext);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
new file mode 100644
index 0000000..3728ab8
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> {
+    private static final String TAG = "PrimaryDexopter";
+
+    private final int mSharedGid;
+
+    public PrimaryDexopter(@NonNull Context context, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+    }
+
+    @VisibleForTesting
+    public PrimaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        super(injector, pkgState, pkg, params, cancellationSignal);
+
+        mSharedGid = UserHandle.getSharedAppGid(pkgState.getAppId());
+        if (mSharedGid < 0) {
+            throw new IllegalStateException(
+                    String.format("Unable to get shared gid for package '%s' (app ID: %d)",
+                            pkgState.getPackageName(), pkgState.getAppId()));
+        }
+    }
+
+    @Override
+    protected boolean isInDalvikCache() {
+        return Utils.isInDalvikCache(mPkgState);
+    }
+
+    @Override
+    @NonNull
+    protected List<DetailedPrimaryDexInfo> getDexInfoList() {
+        return PrimaryDexUtils.getDetailedDexInfo(mPkgState, mPkg);
+    }
+
+    @Override
+    protected boolean isDexoptable(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        if (!dexInfo.hasCode()) {
+            return false;
+        }
+        if ((mParams.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+            return Objects.equals(mParams.getSplitName(), dexInfo.splitName());
+        }
+        return true;
+    }
+
+    @Override
+    protected boolean needsToBeShared(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return isSharedLibrary()
+                || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps(
+                        mPkgState.getPackageName(), dexInfo.dexPath());
+    }
+
+    @Override
+    protected boolean isDexFilePublic(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        // The filesystem permission of a primary dex file always has the S_IROTH bit. In practice,
+        // the accessibility is enforced by Application Sandbox, not filesystem permission.
+        return true;
+    }
+
+    @Override
+    @Nullable
+    protected ProfilePath initReferenceProfile(@NonNull DetailedPrimaryDexInfo dexInfo)
+            throws RemoteException {
+        OutputProfile output = buildOutputProfile(dexInfo, true /* isPublic */);
+
+        ProfilePath prebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath());
+        try {
+            // If the APK is really a prebuilt one, rewriting the profile is unnecessary because the
+            // dex location is known at build time and is correctly set in the profile header.
+            // However, the APK can also be an installed one, in which case partners may place a
+            // profile file next to the APK at install time. Rewriting the profile in the latter
+            // case is necessary.
+            if (mInjector.getArtd().copyAndRewriteProfile(
+                        prebuiltProfile, output, dexInfo.dexPath())) {
+                return ProfilePath.tmpProfilePath(output.profilePath);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to use prebuilt profile "
+                            + AidlUtils.toString(output.profilePath.finalPath),
+                    e);
+        }
+
+        ProfilePath dmProfile = AidlUtils.buildProfilePathForDm(dexInfo.dexPath());
+        try {
+            if (mInjector.getArtd().copyAndRewriteProfile(dmProfile, output, dexInfo.dexPath())) {
+                return ProfilePath.tmpProfilePath(output.profilePath);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to use profile in dex metadata file "
+                            + AidlUtils.toString(output.profilePath.finalPath),
+                    e);
+        }
+
+        return null;
+    }
+
+    @Override
+    @NonNull
+    protected PermissionSettings getPermissionSettings(
+            @NonNull DetailedPrimaryDexInfo dexInfo, boolean canBePublic) {
+        // The files and directories should belong to the system so that Package Manager can manage
+        // them (e.g., move them around).
+        // We don't need the "read" bit for "others" on the directories because others only need to
+        // access the files in the directories, but they don't need to "ls" the directories.
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID /* uid */,
+                Process.SYSTEM_UID /* gid */, false /* isOtherReadable */,
+                true /* isOtherExecutable */);
+        FsPermission fileFsPermission = AidlUtils.buildFsPermission(
+                Process.SYSTEM_UID /* uid */, mSharedGid /* gid */, canBePublic);
+        // For primary dex, we can use the default SELinux context.
+        SeContext seContext = null;
+        return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+    }
+
+    @Override
+    @NonNull
+    protected List<Abi> getAllAbis(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return Utils.getAllAbis(mPkgState);
+    }
+
+    @Override
+    @NonNull
+    protected ProfilePath buildRefProfilePath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.buildRefProfilePath(mPkgState, dexInfo);
+    }
+
+    @Override
+    protected boolean isAppImageAllowed(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        // Only allow app image for the base APK because having multiple app images is not
+        // supported.
+        // Additionally, disable app images if the app requests for the splits to be loaded in
+        // isolation because app images are unsupported for multiple class loaders (b/72696798).
+        // TODO(jiakaiz): Investigate whether this is still the best choice today.
+        return dexInfo.splitName() == null && !PrimaryDexUtils.isIsolatedSplitLoading(mPkg);
+    }
+
+    @Override
+    @NonNull
+    protected OutputProfile buildOutputProfile(
+            @NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) {
+        return PrimaryDexUtils.buildOutputProfile(
+                mPkgState, dexInfo, Process.SYSTEM_UID, mSharedGid, isPublic);
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getCurProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), mPkgState, dexInfo);
+    }
+
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return AidlUtils.buildDexMetadataPath(dexInfo.dexPath());
+    }
+
+    @SuppressLint("NewApi")
+    private boolean isSharedLibrary() {
+        return PackageStateModulesUtils.isLoadableInOtherProcesses(mPkgState, true /* codeOnly */);
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
new file mode 100644
index 0000000..ca9b9ed
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.pm.PackageManagerLocal;
+
+import dalvik.system.DexFile;
+
+import java.util.Set;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Maps a compilation reason to a compiler filter and a priority class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ReasonMapping {
+    private ReasonMapping() {}
+
+    /** Dexopting apps on the first boot after flashing or factory resetting the device. */
+    public static final String REASON_FIRST_BOOT = "first-boot";
+    /** Dexopting apps on the next boot after an OTA. */
+    public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota";
+    /** Dexopting apps on the next boot after a mainline update. */
+    public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update";
+    /** Installing an app after user presses the "install"/"update" button. */
+    public static final String REASON_INSTALL = "install";
+    /** Dexopting apps in the background. */
+    public static final String REASON_BG_DEXOPT = "bg-dexopt";
+    /** Invoked by cmdline. */
+    public static final String REASON_CMDLINE = "cmdline";
+    /** Downgrading the compiler filter when an app is not used for a long time. */
+    public static final String REASON_INACTIVE = "inactive";
+
+    // Reasons for Play Install Hints (go/install-hints).
+    public static final String REASON_INSTALL_FAST = "install-fast";
+    public static final String REASON_INSTALL_BULK = "install-bulk";
+    public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary";
+    public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded";
+    public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
+            "install-bulk-secondary-downgraded";
+
+    /** @hide */
+    public static final Set<String> REASONS_FOR_INSTALL = Set.of(REASON_INSTALL,
+            REASON_INSTALL_FAST, REASON_INSTALL_BULK, REASON_INSTALL_BULK_SECONDARY,
+            REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+
+    /**
+     * Reasons for {@link ArtManagerLocal#dexoptPackages}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @StringDef(prefix = "REASON_", value = {
+        REASON_FIRST_BOOT,
+        REASON_BOOT_AFTER_OTA,
+        REASON_BOOT_AFTER_MAINLINE_UPDATE,
+        REASON_BG_DEXOPT,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BatchDexoptReason {}
+
+    /**
+     * Reasons for {@link ArtManagerLocal#onBoot(String, Executor, Consumer<OperationProgress>)}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @StringDef(prefix = "REASON_", value = {
+        REASON_FIRST_BOOT,
+        REASON_BOOT_AFTER_OTA,
+        REASON_BOOT_AFTER_MAINLINE_UPDATE,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BootReason {}
+
+    /**
+     * Loads the compiler filter from the system property for the given reason and checks for
+     * validity.
+     *
+     * @throws IllegalArgumentException if the reason is invalid
+     * @throws IllegalStateException if the system property value is invalid
+     *
+     * @hide
+     */
+    @NonNull
+    public static String getCompilerFilterForReason(@NonNull String reason) {
+        String value = SystemProperties.get("pm.dexopt." + reason);
+        if (TextUtils.isEmpty(value)) {
+            throw new IllegalArgumentException("No compiler filter for reason '" + reason + "'");
+        }
+        if (!Utils.isValidArtServiceCompilerFilter(value)) {
+            throw new IllegalStateException(
+                    "Got invalid compiler filter '" + value + "' for reason '" + reason + "'");
+        }
+        return value;
+    }
+
+    /**
+     * Loads the compiler filter from the system property for:
+     * - shared libraries
+     * - apps used by other apps without a dex metadata file
+     *
+     * @throws IllegalStateException if the system property value is invalid
+     *
+     * @hide
+     */
+    @NonNull
+    public static String getCompilerFilterForShared() {
+        // "shared" is technically not a compilation reason, but the compiler filter is defined as a
+        // system property as if "shared" is a reason.
+        String value = getCompilerFilterForReason("shared");
+        if (DexFile.isProfileGuidedCompilerFilter(value)) {
+            throw new IllegalStateException(
+                    "Compiler filter for 'shared' must not be profile guided, got '" + value + "'");
+        }
+        return value;
+    }
+
+    /**
+     * Returns the priority for the given reason.
+     *
+     * @throws IllegalArgumentException if the reason is invalid
+     * @see PriorityClassApi
+     *
+     * @hide
+     */
+    public static @PriorityClassApi byte getPriorityClassForReason(@NonNull String reason) {
+        switch (reason) {
+            case REASON_FIRST_BOOT:
+            case REASON_BOOT_AFTER_OTA:
+            case REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                return ArtFlags.PRIORITY_BOOT;
+            case REASON_INSTALL_FAST:
+                return ArtFlags.PRIORITY_INTERACTIVE_FAST;
+            case REASON_INSTALL:
+            case REASON_CMDLINE:
+                return ArtFlags.PRIORITY_INTERACTIVE;
+            case REASON_BG_DEXOPT:
+            case REASON_INACTIVE:
+            case REASON_INSTALL_BULK:
+            case REASON_INSTALL_BULK_SECONDARY:
+            case REASON_INSTALL_BULK_DOWNGRADED:
+            case REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                return ArtFlags.PRIORITY_BACKGROUND;
+            default:
+                throw new IllegalArgumentException("No priority class for reason '" + reason + "'");
+        }
+    }
+
+    /**
+     * Loads the concurrency from the system property, for batch dexopt ({@link
+     * ArtManagerLocal#dexoptPackages}), or 1 if the system property is not found or cannot be
+     * parsed.
+     *
+     * @hide
+     */
+    public static int getConcurrencyForReason(@NonNull @BatchDexoptReason String reason) {
+        return SystemProperties.getInt("pm.dexopt." + reason + ".concurrency", 1 /* def */);
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
new file mode 100644
index 0000000..b751073
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.List;
+
+/** @hide */
+public class SecondaryDexopter extends Dexopter<DetailedSecondaryDexInfo> {
+    private static final String TAG = "SecondaryDexopter";
+
+    public SecondaryDexopter(@NonNull Context context, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+    }
+
+    @VisibleForTesting
+    public SecondaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        super(injector, pkgState, pkg, params, cancellationSignal);
+    }
+
+    @Override
+    protected boolean isInDalvikCache() {
+        // A secondary dex file is added by the app, so it's always in a writable location and hence
+        // never uses dalvik-cache.
+        return false;
+    }
+
+    @Override
+    @NonNull
+    protected List<DetailedSecondaryDexInfo> getDexInfoList() {
+        return mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
+                mPkgState.getPackageName());
+    }
+
+    @Override
+    protected boolean isDexoptable(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return true;
+    }
+
+    @Override
+    protected boolean needsToBeShared(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.isUsedByOtherApps();
+    }
+
+    @Override
+    protected boolean isDexFilePublic(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.isDexFilePublic();
+    }
+
+    @Override
+    @Nullable
+    protected ProfilePath initReferenceProfile(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // A secondary dex file doesn't have any external profile to use.
+        return null;
+    }
+
+    @Override
+    @NonNull
+    protected PermissionSettings getPermissionSettings(
+            @NonNull DetailedSecondaryDexInfo dexInfo, boolean canBePublic) {
+        int uid = getUid(dexInfo);
+        // We need the "execute" bit for "others" even though `canBePublic` is false because the
+        // directory can contain other artifacts that needs to be public.
+        // We don't need the "read" bit for "others" on the directories because others only need to
+        // access the files in the directories, but they don't need to "ls" the directories.
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */,
+                false /* isOtherReadable */, true /* isOtherExecutable */);
+        FsPermission fileFsPermission =
+                AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */, canBePublic);
+        SeContext seContext = AidlUtils.buildSeContext(mPkgState.getSeInfo(), uid);
+        return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+    }
+
+    @Override
+    @NonNull
+    protected List<Abi> getAllAbis(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return Utils.getAllAbisForNames(dexInfo.abiNames(), mPkgState);
+    }
+
+    @Override
+    @NonNull
+    protected ProfilePath buildRefProfilePath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath());
+    }
+
+    @Override
+    protected boolean isAppImageAllowed(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // The runtime can only load the app image of the base APK.
+        return false;
+    }
+
+    @Override
+    @NonNull
+    protected OutputProfile buildOutputProfile(
+            @NonNull DetailedSecondaryDexInfo dexInfo, boolean isPublic) {
+        int uid = getUid(dexInfo);
+        return AidlUtils.buildOutputProfileForSecondary(dexInfo.dexPath(), uid, uid, isPublic);
+    }
+
+    @Override
+    @NonNull
+    protected List<ProfilePath> getCurProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // A secondary dex file can only be loaded by one user, so there is only one profile.
+        return List.of(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+    }
+
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return null;
+    }
+
+    private int getUid(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return dexInfo.userHandle().getUid(mPkgState.getAppId());
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
new file mode 100644
index 0000000..0d40923
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.apphibernation.AppHibernationManager;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+
+/** @hide */
+public final class Utils {
+    public static final String TAG = "ArtServiceUtils";
+    public static final String PLATFORM_PACKAGE_NAME = "android";
+
+    private Utils() {}
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static <T> boolean isEmpty(@Nullable Collection<T> array) {
+        return array == null || array.isEmpty();
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static <T> boolean isEmpty(@Nullable SparseArray<T> array) {
+        return array == null || array.size() == 0;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /** Returns the ABI information for the package. */
+    @NonNull
+    public static List<Abi> getAllAbis(@NonNull PackageState pkgState) {
+        List<Abi> abis = new ArrayList<>();
+        abis.add(getPrimaryAbi(pkgState));
+        String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
+        String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+        if (pkgSecondaryCpuAbi != null) {
+            Utils.check(pkgState.getPrimaryCpuAbi() != null);
+            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi));
+            abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */));
+        }
+        // Primary and secondary ABIs should be guaranteed to have different ISAs.
+        if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) {
+            throw new IllegalStateException(String.format(
+                    "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')",
+                    pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name()));
+        }
+        return abis;
+    }
+
+    /** Returns the ABI information for the ABIs with the given names. */
+    @NonNull
+    public static List<Abi> getAllAbisForNames(
+            @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
+        Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
+        return abiNames.stream()
+                .map(name
+                        -> Abi.create(name, VMRuntime.getInstructionSet(name),
+                                name.equals(pkgPrimaryAbi.name())))
+                .collect(Collectors.toList());
+    }
+
+    @NonNull
+    public static Abi getPrimaryAbi(@NonNull PackageState pkgState) {
+        String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
+        if (primaryCpuAbi != null) {
+            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi));
+            return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */);
+        }
+        // This is the most common case. The package manager can't infer the ABIs, probably because
+        // the package doesn't contain any native library. The app is launched with the device's
+        // preferred ABI.
+        String preferredAbi = Constants.getPreferredAbi();
+        return Abi.create(
+                preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */);
+    }
+
+    /**
+     * If the given ISA isn't native to the device, returns the ISA that the native bridge
+     * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually
+     * launched with and therefore the ISA that should be used to compile the app.
+     */
+    @NonNull
+    private static String getTranslatedIsa(@NonNull String isa) {
+        String abi64 = Constants.getNative64BitAbi();
+        String abi32 = Constants.getNative32BitAbi();
+        if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64)))
+                || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) {
+            return isa;
+        }
+        String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa);
+        if (TextUtils.isEmpty(translatedIsa)) {
+            throw new IllegalStateException(String.format("Unsupported isa '%s'", isa));
+        }
+        return translatedIsa;
+    }
+
+    @NonNull
+    private static String nativeIsaToAbi(@NonNull String isa) {
+        String abi64 = Constants.getNative64BitAbi();
+        if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) {
+            return abi64;
+        }
+        String abi32 = Constants.getNative32BitAbi();
+        if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) {
+            return abi32;
+        }
+        throw new IllegalStateException(String.format("Non-native isa '%s'", isa));
+    }
+
+    @NonNull
+    public static boolean isInDalvikCache(@NonNull PackageState pkg) {
+        return pkg.isSystem() && !pkg.isUpdatedSystemApp();
+    }
+
+    /** Returns true if the given string is a valid compiler filter. */
+    public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) {
+        if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
+            return true;
+        }
+        return DexFile.isValidCompilerFilter(compilerFilter);
+    }
+
+    @NonNull
+    public static IArtd getArtd() {
+        IArtd artd = IArtd.Stub.asInterface(ArtModuleServiceInitializer.getArtModuleServiceManager()
+                                                    .getArtdServiceRegisterer()
+                                                    .waitForService());
+        if (artd == null) {
+            throw new IllegalStateException("Unable to connect to artd");
+        }
+        return artd;
+    }
+
+    public static boolean implies(boolean cond1, boolean cond2) {
+        return cond1 ? cond2 : true;
+    }
+
+    public static void check(boolean cond) {
+        // This cannot be replaced with `assert` because `assert` is not enabled in Android.
+        if (!cond) {
+            throw new IllegalStateException("Check failed");
+        }
+    }
+
+    @NonNull
+    public static PackageState getPackageStateOrThrow(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = snapshot.getPackageState(packageName);
+        if (pkgState == null) {
+            throw new IllegalArgumentException("Package not found: " + packageName);
+        }
+        return pkgState;
+    }
+
+    @NonNull
+    public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) {
+        AndroidPackage pkg = pkgState.getAndroidPackage();
+        if (pkg == null) {
+            throw new IllegalArgumentException(
+                    "Unable to get package " + pkgState.getPackageName());
+        }
+        return pkg;
+    }
+
+    @NonNull
+    public static String assertNonEmpty(@Nullable String str) {
+        if (TextUtils.isEmpty(str)) {
+            throw new IllegalArgumentException();
+        }
+        return str;
+    }
+
+    public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) {
+        getFuture(CompletableFuture.runAsync(runnable, executor));
+    }
+
+    public static <T> T getFuture(Future<T> future) {
+        try {
+            return future.get();
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            throw new RuntimeException(cause);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns true if the given package is dexoptable.
+     *
+     * @param appHibernationManager the {@link AppHibernationManager} instance for checking
+     *         hibernation status, or null to skip the check
+     */
+    @SuppressLint("NewApi")
+    public static boolean canDexoptPackage(
+            @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) {
+        if (!PackageStateModulesUtils.isDexoptable(pkgState)) {
+            return false;
+        }
+
+        // We do not dexopt unused packages.
+        // If `appHibernationManager` is null, the caller's intention is to skip the check.
+        if (appHibernationManager != null
+                && appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
+                && appHibernationManager.isOatArtifactDeletionEnabled()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static long getPackageLastActiveTime(@NonNull PackageState pkgState,
+            @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) {
+        long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName());
+        // The time where the last user installed the package the first time.
+        long lastFirstInstallTimeMs =
+                userManager.getUserHandles(true /* excludeDying */)
+                        .stream()
+                        .map(handle -> pkgState.getStateForUser(handle))
+                        .map(userState -> userState.getFirstInstallTimeMillis())
+                        .max(Long::compare)
+                        .orElse(0l);
+        return Math.max(lastUsedAtMs, lastFirstInstallTimeMs);
+    }
+
+    public static void deleteIfExistsSafe(@Nullable File file) {
+        if (file != null) {
+            deleteIfExistsSafe(file.toPath());
+        }
+    }
+
+    public static void deleteIfExistsSafe(@NonNull Path path) {
+        try {
+            Files.deleteIfExists(path);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to delete file '" + path + "'", e);
+        }
+    }
+
+    @AutoValue
+    public abstract static class Abi {
+        static @NonNull Abi create(
+                @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) {
+            return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi);
+        }
+
+        // The ABI name. E.g., "arm64-v8a".
+        abstract @NonNull String name();
+
+        // The instruction set name. E.g., "arm64".
+        abstract @NonNull String isa();
+
+        abstract boolean isPrimaryAbi();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/ArtFlags.java b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
new file mode 100644
index 0000000..64ae807
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.job.JobScheduler;
+
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.PriorityClass;
+import com.android.server.art.ReasonMapping;
+import com.android.server.pm.PackageManagerLocal;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtFlags {
+    // Common flags.
+
+    /** Whether the operation is applied for primary dex'es. */
+    public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0;
+    /** Whether the operation is applied for secondary dex'es. */
+    public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1;
+
+    // Flags specific to `dexoptPackage`.
+
+    /** Whether to dexopt dependency libraries as well. */
+    public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 1 << 2;
+    /**
+     * Whether the intention is to downgrade the compiler filter. If true, the dexopt will
+     * be skipped if the target compiler filter is better than or equal to the compiler filter
+     * of the existing dexopt artifacts, or dexopt artifacts do not exist.
+     */
+    public static final int FLAG_SHOULD_DOWNGRADE = 1 << 3;
+    /**
+     * Whether to force dexopt. If true, the dexopt will be performed regardless of
+     * any existing dexopt artifacts.
+     */
+    public static final int FLAG_FORCE = 1 << 4;
+    /**
+     * If set, the dexopt will be performed for a single split. Otherwise, the dexopt
+     * will be performed for all splits. {@link DexoptParams.Builder#setSplitName()} can be used
+     * to specify the split to dexopt.
+     *
+     * When this flag is set, {@link #FLAG_FOR_PRIMARY_DEX} must be set, and {@link
+     * #FLAG_FOR_SECONDARY_DEX} and {@link #FLAG_SHOULD_INCLUDE_DEPENDENCIES} must not be set.
+     */
+    public static final int FLAG_FOR_SINGLE_SPLIT = 1 << 5;
+    /**
+     * If set, skips the dexopt if the remaining storage space is low. The threshold is
+     * controlled by the global settings {@code sys_storage_threshold_percentage} and {@code
+     * sys_storage_threshold_max_bytes}.
+     */
+    public static final int FLAG_SKIP_IF_STORAGE_LOW = 1 << 6;
+
+    /**
+     * Flags for {@link
+     * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String, int)}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_FOR_PRIMARY_DEX,
+        FLAG_FOR_SECONDARY_DEX,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GetStatusFlags {}
+
+    /**
+     * Default flags that are used when {@link
+     * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)} is
+     * called.
+     * Value: {@link #FLAG_FOR_PRIMARY_DEX}, {@link #FLAG_FOR_SECONDARY_DEX}.
+     */
+    public static @GetStatusFlags int defaultGetStatusFlags() {
+        return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX;
+    }
+
+    /**
+     * Flags for {@link DexoptParams}.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_FOR_PRIMARY_DEX,
+        FLAG_FOR_SECONDARY_DEX,
+        FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+        FLAG_SHOULD_DOWNGRADE,
+        FLAG_FORCE,
+        FLAG_FOR_SINGLE_SPLIT,
+        FLAG_SKIP_IF_STORAGE_LOW,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DexoptFlags {}
+
+    /**
+     * Default flags that are used when
+     * {@link DexoptParams.Builder#Builder(String)} is called.
+     *
+     * @hide
+     */
+    public static @DexoptFlags int defaultDexoptFlags(@NonNull String reason) {
+        switch (reason) {
+            case ReasonMapping.REASON_INSTALL:
+            case ReasonMapping.REASON_INSTALL_FAST:
+            case ReasonMapping.REASON_INSTALL_BULK:
+            case ReasonMapping.REASON_INSTALL_BULK_SECONDARY:
+            case ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED:
+            case ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                return FLAG_FOR_PRIMARY_DEX;
+            case ReasonMapping.REASON_INACTIVE:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX | FLAG_SHOULD_DOWNGRADE;
+            case ReasonMapping.REASON_FIRST_BOOT:
+            case ReasonMapping.REASON_BOOT_AFTER_OTA:
+            case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+            case ReasonMapping.REASON_BG_DEXOPT:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX
+                        | FLAG_SHOULD_INCLUDE_DEPENDENCIES | FLAG_SKIP_IF_STORAGE_LOW;
+            case ReasonMapping.REASON_CMDLINE:
+            default:
+                return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX
+                        | FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+        }
+    }
+
+    // Keep in sync with `PriorityClass` except for `PRIORITY_NONE`.
+
+    /**
+     * Initial value. Not expected.
+     *
+     * @hide
+     */
+    public static final int PRIORITY_NONE = -1;
+    /** Indicates that the operation blocks boot. */
+    public static final int PRIORITY_BOOT = PriorityClass.BOOT;
+    /**
+     * Indicates that a human is waiting on the result and the operation is more latency sensitive
+     * than usual.
+     */
+    public static final int PRIORITY_INTERACTIVE_FAST = PriorityClass.INTERACTIVE_FAST;
+    /** Indicates that a human is waiting on the result. */
+    public static final int PRIORITY_INTERACTIVE = PriorityClass.INTERACTIVE;
+    /** Indicates that the operation runs in background. */
+    public static final int PRIORITY_BACKGROUND = PriorityClass.BACKGROUND;
+
+    /**
+     * Indicates the priority of an operation. The value affects the resource usage and the process
+     * priority. A higher value may result in faster execution but may consume more resources and
+     * compete for resources with other processes.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(prefix = "PRIORITY_", value = {
+        PRIORITY_NONE,
+        PRIORITY_BOOT,
+        PRIORITY_INTERACTIVE_FAST,
+        PRIORITY_INTERACTIVE,
+        PRIORITY_BACKGROUND,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PriorityClassApi {}
+
+    /** The job has been successfully scheduled. */
+    public static final int SCHEDULE_SUCCESS = 0;
+
+    /** @see JobScheduler#RESULT_FAILURE */
+    public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1;
+
+    /** The job is disabled by the system property {@code pm.dexopt.disable_bg_dexopt}. */
+    public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2;
+
+    /**
+     * Indicates the result of scheduling a background dexopt job.
+     *
+     * @hide
+     */
+    // clang-format off
+    @IntDef(prefix = "SCHEDULE_", value = {
+        SCHEDULE_SUCCESS,
+        SCHEDULE_JOB_SCHEDULER_FAILURE,
+        SCHEDULE_DISABLED_BY_SYSPROP,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScheduleStatus {}
+
+    private ArtFlags() {}
+}
diff --git a/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java
new file mode 100644
index 0000000..ffe5500
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class BatchDexoptParams {
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public static final class Builder {
+        private @NonNull List<String> mPackageNames; // This is assumed immutable.
+        private @NonNull DexoptParams mDexoptParams;
+
+        /** @hide */
+        public Builder(
+                @NonNull List<String> defaultPackages, @NonNull DexoptParams defaultDexoptParams) {
+            mPackageNames = defaultPackages; // The argument is assumed immutable.
+            mDexoptParams = defaultDexoptParams;
+        }
+
+        /**
+         * Sets the list of packages to dexopt. The dexopt will be scheduled in the given
+         * order.
+         *
+         * If not called, the default list will be used.
+         */
+        @NonNull
+        public Builder setPackages(@NonNull List<String> packageNames) {
+            mPackageNames = Collections.unmodifiableList(new ArrayList<>(packageNames));
+            return this;
+        }
+
+        /**
+         * Sets the params for dexopting each package.
+         *
+         * If not called, the default params built from {@link DexoptParams#Builder(String)} will
+         * be used.
+         */
+        @NonNull
+        public Builder setDexoptParams(@NonNull DexoptParams dexoptParams) {
+            mDexoptParams = dexoptParams;
+            return this;
+        }
+
+        /** Returns the built object. */
+        @NonNull
+        public BatchDexoptParams build() {
+            return new AutoValue_BatchDexoptParams(mPackageNames, mDexoptParams);
+        }
+    }
+
+    /** @hide */
+    protected BatchDexoptParams() {}
+
+    /** The ordered list of packages to dexopt. */
+    public abstract @NonNull List<String> getPackages();
+
+    /** The params for dexopting each package. */
+    public abstract @NonNull DexoptParams getDexoptParams();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/Config.java b/libartservice/service/java/com/android/server/art/model/Config.java
new file mode 100644
index 0000000..49bc930
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/Config.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import static com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback;
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.art.ArtManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A class that stores the configurations set by the consumer of ART Service at runtime. This class
+ * is thread-safe.
+ *
+ * @hide
+ */
+public class Config {
+    /** @see ArtManagerLocal#setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback) */
+    @GuardedBy("this")
+    @Nullable
+    private Callback<BatchDexoptStartCallback, Void> mBatchDexoptStartCallback = null;
+
+    /**
+     * @see ArtManagerLocal#setScheduleBackgroundDexoptJobCallback(Executor,
+     *         ScheduleBackgroundDexoptJobCallback)
+     */
+    @GuardedBy("this")
+    @Nullable
+    private Callback<ScheduleBackgroundDexoptJobCallback, Void>
+            mScheduleBackgroundDexoptJobCallback = null;
+
+    /**
+     * @see ArtManagerLocal#addDexoptDoneCallback(Executor, DexoptDoneCallback)
+     */
+    @GuardedBy("this")
+    @NonNull
+    private LinkedHashMap<DexoptDoneCallback, Callback<DexoptDoneCallback, Boolean>>
+            mDexoptDoneCallbacks = new LinkedHashMap<>();
+
+    public synchronized void setBatchDexoptStartCallback(
+            @NonNull Executor executor, @NonNull BatchDexoptStartCallback callback) {
+        mBatchDexoptStartCallback = Callback.create(callback, executor);
+    }
+
+    public synchronized void clearBatchDexoptStartCallback() {
+        mBatchDexoptStartCallback = null;
+    }
+
+    @Nullable
+    public synchronized Callback<BatchDexoptStartCallback, Void> getBatchDexoptStartCallback() {
+        return mBatchDexoptStartCallback;
+    }
+
+    public synchronized void setScheduleBackgroundDexoptJobCallback(
+            @NonNull Executor executor, @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+        mScheduleBackgroundDexoptJobCallback = Callback.create(callback, executor);
+    }
+
+    public synchronized void clearScheduleBackgroundDexoptJobCallback() {
+        mScheduleBackgroundDexoptJobCallback = null;
+    }
+
+    @Nullable
+    public synchronized Callback<ScheduleBackgroundDexoptJobCallback, Void>
+    getScheduleBackgroundDexoptJobCallback() {
+        return mScheduleBackgroundDexoptJobCallback;
+    }
+
+    public synchronized void addDexoptDoneCallback(boolean onlyIncludeUpdates,
+            @NonNull Executor executor, @NonNull DexoptDoneCallback callback) {
+        if (mDexoptDoneCallbacks.putIfAbsent(
+                    callback, Callback.create(callback, executor, onlyIncludeUpdates))
+                != null) {
+            throw new IllegalStateException("callback already added");
+        }
+    }
+
+    public synchronized void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) {
+        mDexoptDoneCallbacks.remove(callback);
+    }
+
+    @NonNull
+    public synchronized List<Callback<DexoptDoneCallback, Boolean>> getDexoptDoneCallbacks() {
+        return new ArrayList<>(mDexoptDoneCallbacks.values());
+    }
+
+    @AutoValue
+    public static abstract class Callback<CallbackType, ExtraType> {
+        public abstract @NonNull CallbackType get();
+        public abstract @NonNull Executor executor();
+        public abstract @Nullable ExtraType extra();
+        static <CallbackType, ExtraType> @NonNull Callback<CallbackType, ExtraType> create(
+                @NonNull CallbackType callback, @NonNull Executor executor,
+                @Nullable ExtraType extra) {
+            return new AutoValue_Config_Callback<CallbackType, ExtraType>(
+                    callback, executor, extra);
+        }
+        static <CallbackType> @NonNull Callback<CallbackType, Void> create(
+                @NonNull CallbackType callback, @NonNull Executor executor) {
+            return new AutoValue_Config_Callback<CallbackType, Void>(
+                    callback, executor, null /* extra */);
+        }
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DeleteResult.java b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
new file mode 100644
index 0000000..e8e1520
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DeleteResult {
+    /** @hide */
+    protected DeleteResult() {}
+
+    /** @hide */
+    public static @NonNull DeleteResult create(long freedBytes) {
+        return new AutoValue_DeleteResult(freedBytes);
+    }
+
+    /** The amount of the disk space freed by the deletion, in bytes. */
+    public abstract long getFreedBytes();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
new file mode 100644
index 0000000..932813f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Detailed information about a dex file.
+ *
+ * @hide
+ */
+@Immutable
+public interface DetailedDexInfo {
+    /** The path to the dex file. */
+    @NonNull String dexPath();
+
+    /**
+     * A string describing the structure of the class loader that the dex file is loaded with, or
+     * null if the class loader context is invalid.
+     */
+    @Nullable String classLoaderContext();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java
new file mode 100644
index 0000000..1f4c013
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The information about the use of a dex container file.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexContainerFileUseInfo {
+    /** @hide */
+    protected DexContainerFileUseInfo() {}
+
+    /** @hide */
+    public static @NonNull DexContainerFileUseInfo create(@NonNull String dexContainerFile,
+            @NonNull UserHandle userHandle, @NonNull Set<String> loadingPackages) {
+        return new AutoValue_DexContainerFileUseInfo(dexContainerFile, userHandle, loadingPackages);
+    }
+
+    /** The absolute path to the dex container file. */
+    public abstract @NonNull String getDexContainerFile();
+
+    /** The {@link UserHandle} that represents the human user who loads the dex file. */
+    public abstract @NonNull UserHandle getUserHandle();
+
+    /** The names of packages that load the dex file. Guaranteed to be non-empty. */
+    public abstract @NonNull Set<String> getLoadingPackages();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptParams.java b/libartservice/service/java/com/android/server/art/model/DexoptParams.java
new file mode 100644
index 0000000..4dc9471
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptParams.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public class DexoptParams {
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    public static final class Builder {
+        private DexoptParams mParams = new DexoptParams();
+
+        /**
+         * Creates a builder.
+         *
+         * Uses default flags ({@link ArtFlags#defaultDexoptFlags()}).
+         *
+         * @param reason Compilation reason. Can be a string defined in {@link ReasonMapping} or a
+         *         custom string. If the value is a string defined in {@link ReasonMapping}, it
+         *         determines the compiler filter and/or the priority class, if those values are not
+         *         explicitly set. If the value is a custom string, the priority class and the
+         *         compiler filter must be explicitly set.
+         */
+        public Builder(@NonNull String reason) {
+            this(reason, ArtFlags.defaultDexoptFlags(reason));
+        }
+
+        /**
+         * Same as above, but allows to specify flags.
+         */
+        public Builder(@NonNull String reason, @DexoptFlags int flags) {
+            mParams.mReason = reason;
+            setFlags(flags);
+        }
+
+        /** Replaces all flags with the given value. */
+        @NonNull
+        public Builder setFlags(@DexoptFlags int value) {
+            mParams.mFlags = value;
+            return this;
+        }
+
+        /** Replaces the flags specified by the mask with the given value. */
+        @NonNull
+        public Builder setFlags(@DexoptFlags int value, @DexoptFlags int mask) {
+            mParams.mFlags = (mParams.mFlags & ~mask) | (value & mask);
+            return this;
+        }
+
+        /**
+         * The target compiler filter, passed as the {@code --compiler-filer} option to dex2oat.
+         * Supported values are listed in
+         * https://source.android.com/docs/core/dalvik/configure#compilation_options.
+         *
+         * Note that the compiler filter might be adjusted before the execution based on factors
+         * like whether the profile is available or whether the app is used by other apps. If not
+         * set, the default compiler filter for the given reason will be used.
+         */
+        @NonNull
+        public Builder setCompilerFilter(@NonNull String value) {
+            mParams.mCompilerFilter = value;
+            return this;
+        }
+
+        /**
+         * The priority of the operation. If not set, the default priority class for the given
+         * reason will be used.
+         *
+         * @see PriorityClassApi
+         */
+        @NonNull
+        public Builder setPriorityClass(@PriorityClassApi int value) {
+            mParams.mPriorityClass = value;
+            return this;
+        }
+
+        /**
+         * The name of the split to dexopt, or null for the base split. This option is only
+         * available when {@link ArtFlags#FLAG_FOR_SINGLE_SPLIT} is set.
+         */
+        @NonNull
+        public Builder setSplitName(@Nullable String value) {
+            mParams.mSplitName = value;
+            return this;
+        }
+
+        /**
+         * Returns the built object.
+         *
+         * @throws IllegalArgumentException if the built options would be invalid
+         */
+        @NonNull
+        public DexoptParams build() {
+            if (mParams.mReason.isEmpty()) {
+                throw new IllegalArgumentException("Reason must not be empty");
+            }
+
+            if (mParams.mCompilerFilter.isEmpty()) {
+                mParams.mCompilerFilter = ReasonMapping.getCompilerFilterForReason(mParams.mReason);
+            } else if (!Utils.isValidArtServiceCompilerFilter(mParams.mCompilerFilter)) {
+                throw new IllegalArgumentException(
+                        "Invalid compiler filter '" + mParams.mCompilerFilter + "'");
+            }
+
+            if (mParams.mPriorityClass == ArtFlags.PRIORITY_NONE) {
+                mParams.mPriorityClass = ReasonMapping.getPriorityClassForReason(mParams.mReason);
+            } else if (mParams.mPriorityClass < 0 || mParams.mPriorityClass > 100) {
+                throw new IllegalArgumentException("Invalid priority class "
+                        + mParams.mPriorityClass + ". Must be between 0 and 100");
+            }
+
+            if ((mParams.mFlags & (ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX))
+                    == 0) {
+                throw new IllegalArgumentException("Nothing to dexopt");
+            }
+
+            if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+                    && (mParams.mFlags & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
+                throw new IllegalArgumentException(
+                        "FLAG_SHOULD_INCLUDE_DEPENDENCIES must not set if FLAG_FOR_PRIMARY_DEX is "
+                        + "not set.");
+            }
+
+            if ((mParams.mFlags & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+                if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0) {
+                    throw new IllegalArgumentException(
+                            "FLAG_FOR_PRIMARY_DEX must be set when FLAG_FOR_SINGLE_SPLIT is set");
+                }
+                if ((mParams.mFlags
+                            & (ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                    | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES))
+                        != 0) {
+                    throw new IllegalArgumentException(
+                            "FLAG_FOR_SECONDARY_DEX and FLAG_SHOULD_INCLUDE_DEPENDENCIES must "
+                            + "not be set when FLAG_FOR_SINGLE_SPLIT is set");
+                }
+            } else {
+                if (mParams.mSplitName != null) {
+                    throw new IllegalArgumentException(
+                            "Split name must not be set when FLAG_FOR_SINGLE_SPLIT is not set");
+                }
+            }
+
+            return mParams;
+        }
+    }
+
+    /**
+     * A value indicating that dexopt shouldn't be run. This value is consumed by ART Services and
+     * is not propagated to dex2oat.
+     */
+    public static final String COMPILER_FILTER_NOOP = "skip";
+
+    private @DexoptFlags int mFlags = 0;
+    private @NonNull String mCompilerFilter = "";
+    private @PriorityClassApi int mPriorityClass = ArtFlags.PRIORITY_NONE;
+    private @NonNull String mReason = "";
+    private @Nullable String mSplitName = null;
+
+    private DexoptParams() {}
+
+    /** Returns all flags. */
+    public @DexoptFlags int getFlags() {
+        return mFlags;
+    }
+
+    /** The target compiler filter. */
+    public @NonNull String getCompilerFilter() {
+        return mCompilerFilter;
+    }
+
+    /** The priority class. */
+    public @PriorityClassApi int getPriorityClass() {
+        return mPriorityClass;
+    }
+
+    /**
+     * The compilation reason.
+     *
+     * DO NOT directly use the string value to determine the resource usage and the process
+     * priority. Use {@link #getPriorityClass}.
+     */
+    public @NonNull String getReason() {
+        return mReason;
+    }
+
+    /** The name of the split to dexopt, or null for the base split. */
+    public @Nullable String getSplitName() {
+        return mSplitName;
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptResult.java b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
new file mode 100644
index 0000000..d8a8514
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexoptResult {
+    // Possible values of {@link #DexoptResultStatus}.
+    // A larger number means a higher priority. If multiple dex container files are processed, the
+    // final status will be the one with the highest priority.
+    /** Dexopt is skipped because there is no need to do it. */
+    public static final int DEXOPT_SKIPPED = 10;
+    /** Dexopt is performed successfully. */
+    public static final int DEXOPT_PERFORMED = 20;
+    /** Dexopt is failed. */
+    public static final int DEXOPT_FAILED = 30;
+    /** Dexopt is cancelled. */
+    public static final int DEXOPT_CANCELLED = 40;
+
+    /** @hide */
+    // clang-format off
+    @IntDef(prefix = {"DEXOPT_"}, value = {
+        DEXOPT_SKIPPED,
+        DEXOPT_FAILED,
+        DEXOPT_PERFORMED,
+        DEXOPT_CANCELLED,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DexoptResultStatus {}
+
+    /** @hide */
+    protected DexoptResult() {}
+
+    /** @hide */
+    public static @NonNull DexoptResult create(@NonNull String requestedCompilerFilter,
+            @NonNull String reason, @NonNull List<PackageDexoptResult> packageDexoptResult) {
+        return new AutoValue_DexoptResult(requestedCompilerFilter, reason, packageDexoptResult);
+    }
+
+    /**
+     * The requested compiler filter. Note that the compiler filter might be adjusted before the
+     * execution based on factors like whether the profile is available or whether the app is
+     * used by other apps.
+     *
+     * @see DexoptParams.Builder#setCompilerFilter(String)
+     * @see DexContainerFileDexoptResult#getActualCompilerFilter()
+     */
+    public abstract @NonNull String getRequestedCompilerFilter();
+
+    /** The compilation reason. */
+    public abstract @NonNull String getReason();
+
+    /**
+     * The result of each individual package.
+     *
+     * If the request is to dexopt a single package without dexopting dependencies, the only
+     * element is the result of the requested package.
+     *
+     * If the request is to dexopt a single package with {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} set, the first element is the result of the
+     * requested package, and the rest are the results of the dependency packages.
+     *
+     * If the request is to dexopt multiple packages, the list contains the results of all the
+     * requested packages. The results of their dependency packages are also included if {@link
+     * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} is set.
+     *
+     * If the request is a batch dexopt operation that got cancelled, the list still has an entry
+     * for every package that was requested to be optimized.
+     */
+    public abstract @NonNull List<PackageDexoptResult> getPackageDexoptResults();
+
+    /** The final status. */
+    public @DexoptResultStatus int getFinalStatus() {
+        return getPackageDexoptResults()
+                .stream()
+                .mapToInt(result -> result.getStatus())
+                .max()
+                .orElse(DEXOPT_SKIPPED);
+    }
+
+    /**
+     * Describes the result of a package.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public static abstract class PackageDexoptResult {
+        /** @hide */
+        protected PackageDexoptResult() {}
+
+        /** @hide */
+        public static @NonNull PackageDexoptResult create(@NonNull String packageName,
+                @NonNull List<DexContainerFileDexoptResult> dexContainerFileDexoptResults,
+                boolean isCanceled) {
+            return new AutoValue_DexoptResult_PackageDexoptResult(
+                    packageName, dexContainerFileDexoptResults, isCanceled);
+        }
+
+        /** The package name. */
+        public abstract @NonNull String getPackageName();
+
+        /**
+         * The results of dexopting dex container files. Note that there can be multiple entries
+         * for the same dex container file, but for different ABIs.
+         */
+        public abstract @NonNull List<DexContainerFileDexoptResult>
+        getDexContainerFileDexoptResults();
+
+        /** @hide */
+        public abstract boolean isCanceled();
+
+        /** The overall status of the package. */
+        public @DexoptResultStatus int getStatus() {
+            return isCanceled() ? DEXOPT_CANCELLED
+                                : getDexContainerFileDexoptResults()
+                                          .stream()
+                                          .mapToInt(result -> result.getStatus())
+                                          .max()
+                                          .orElse(DEXOPT_SKIPPED);
+        }
+
+        /** True if the package has any artifacts updated by this operation. */
+        public boolean hasUpdatedArtifacts() {
+            return getDexContainerFileDexoptResults().stream().anyMatch(
+                    result -> result.getStatus() == DEXOPT_PERFORMED);
+        }
+    }
+
+    /**
+     * Describes the result of dexopting a dex container file.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public static abstract class DexContainerFileDexoptResult {
+        /** @hide */
+        protected DexContainerFileDexoptResult() {}
+
+        /** @hide */
+        public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile,
+                boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
+                @DexoptResultStatus int status, long dex2oatWallTimeMillis,
+                long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes,
+                boolean isSkippedDueToStorageLow) {
+            return new AutoValue_DexoptResult_DexContainerFileDexoptResult(dexContainerFile,
+                    isPrimaryAbi, abi, compilerFilter, status, dex2oatWallTimeMillis,
+                    dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow);
+        }
+
+        /** The absolute path to the dex container file. */
+        public abstract @NonNull String getDexContainerFile();
+
+        /**
+         * If true, the dexopt is for the primary ABI of the package (the ABI that the
+         * application is launched with). Otherwise, the dexopt is for an ABI that other
+         * applications might be launched with when using this application's code.
+         */
+        public abstract boolean isPrimaryAbi();
+
+        /**
+         * Returns the ABI that the dexopt is for. Possible values are documented at
+         * https://developer.android.com/ndk/guides/abis#sa.
+         */
+        public abstract @NonNull String getAbi();
+
+        /**
+         * The actual compiler filter.
+         *
+         * @see DexoptParams.Builder#setCompilerFilter(String)
+         */
+        public abstract @NonNull String getActualCompilerFilter();
+
+        /** The status of dexopting this dex container file. */
+        public abstract @DexoptResultStatus int getStatus();
+
+        /**
+         * The wall time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public abstract @DurationMillisLong long getDex2oatWallTimeMillis();
+
+        /**
+         * The CPU time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public abstract @DurationMillisLong long getDex2oatCpuTimeMillis();
+
+        /**
+         * The total size, in bytes, of the dexopt artifacts. Returns 0 if {@link #getStatus()}
+         * is not {@link #DEXOPT_PERFORMED}.
+         */
+        public abstract long getSizeBytes();
+
+        /**
+         * The total size, in bytes, of the previous dexopt artifacts that has been replaced.
+         * Returns 0 if there were no previous dexopt artifacts or {@link #getStatus()} is not
+         * {@link #DEXOPT_PERFORMED}.
+         */
+        public abstract long getSizeBeforeBytes();
+
+        /** @hide */
+        public abstract boolean isSkippedDueToStorageLow();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptStatus.java b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java
new file mode 100644
index 0000000..40130ea
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+
+/**
+ * Describes the dexopt status of a package.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class DexoptStatus {
+    /** @hide */
+    protected DexoptStatus() {}
+
+    /** @hide */
+    public static @NonNull DexoptStatus create(
+            @NonNull List<DexContainerFileDexoptStatus> dexContainerFileDexoptStatuses) {
+        return new AutoValue_DexoptStatus(dexContainerFileDexoptStatuses);
+    }
+
+    /**
+     * The statuses of the dex container file dexopts. Note that there can be multiple entries
+     * for the same dex container file, but for different ABIs.
+     */
+    @NonNull public abstract List<DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses();
+
+    /**
+     * Describes the dexopt status of a dex container file.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @Immutable
+    @AutoValue
+    public abstract static class DexContainerFileDexoptStatus {
+        /** @hide */
+        protected DexContainerFileDexoptStatus() {}
+
+        /** @hide */
+        public static @NonNull DexContainerFileDexoptStatus create(@NonNull String dexContainerFile,
+                boolean isPrimaryDex, boolean isPrimaryAbi, @NonNull String abi,
+                @NonNull String compilerFilter, @NonNull String compilationReason,
+                @NonNull String locationDebugString) {
+            return new AutoValue_DexoptStatus_DexContainerFileDexoptStatus(dexContainerFile,
+                    isPrimaryDex, isPrimaryAbi, abi, compilerFilter, compilationReason,
+                    locationDebugString);
+        }
+
+        /** The absolute path to the dex container file. */
+        public abstract @NonNull String getDexContainerFile();
+
+        /**
+         * If true, the dex container file is a primary dex (the base APK or a split APK).
+         * Otherwise, it's a secondary dex (a APK or a JAR that the package sideloaded into its data
+         * directory).
+         */
+        public abstract boolean isPrimaryDex();
+
+        /**
+         * If true, the dexopt is for the primary ABI of the package (the ABI that the
+         * application is launched with). Otherwise, the dexopt is for an ABI that other
+         * applications might be launched with when using this application's code.
+         */
+        public abstract boolean isPrimaryAbi();
+
+        /**
+         * Returns the ABI that the dexopt is for. Possible values are documented at
+         * https://developer.android.com/ndk/guides/abis#sa.
+         */
+        public abstract @NonNull String getAbi();
+
+        /**
+         * A human-readable string that describes the compiler filter.
+         *
+         * Possible values are:
+         * <ul>
+         *   <li>A valid value of the {@code --compiler-filer} option passed to {@code dex2oat}, if
+         *     the dexopt artifacts are valid. See
+         *     https://source.android.com/docs/core/dalvik/configure#compilation_options.
+         *   <li>{@code "run-from-apk"}, if the dexopt artifacts do not exist.
+         *   <li>{@code "run-from-apk-fallback"}, if the dexopt artifacts exist but are invalid
+         *     because the dex container file has changed.
+         *   <li>{@code "error"}, if an unexpected error occurs.
+         * </ul>
+         */
+        public abstract @NonNull String getCompilerFilter();
+
+        /**
+         * A string that describes the compilation reason.
+         *
+         * Possible values are:
+         * <ul>
+         *   <li>The compilation reason, in text format, passed to {@code dex2oat}.
+         *   <li>{@code "unknown"}: if the reason is empty or the dexopt artifacts do not exist.
+         *   <li>{@code "error"}: if an unexpected error occurs.
+         * </ul>
+         *
+         * Note that this value can differ from the requested compilation reason passed to {@link
+         * DexoptParams.Builder}. Specifically, if the requested reason is for app install (e.g.,
+         * "install"), and a DM file is passed to {@code dex2oat}, a "-dm" suffix will be appended
+         * to the actual reason (e.g., "install-dm"). Other compilation reasons remain unchanged
+         * even if a DM file is passed to {@code dex2oat}.
+         *
+         * Also note that the "-dm" suffix does <b>not</b> imply anything in the DM file being used
+         * by {@code dex2oat}. The compilation reason can still be "install-dm" even if {@code
+         * dex2oat} left all contents of the DM file unused or an empty DM file is passed to
+         * {@code dex2oat}.
+         */
+        public abstract @NonNull String getCompilationReason();
+
+        /**
+         * A human-readable string that describes the location of the dexopt artifacts.
+         *
+         * Note that this string is for debugging purposes only. There is no stability guarantees
+         * for the format of the string. DO NOT use it programmatically.
+         */
+        public abstract @NonNull String getLocationDebugString();
+    }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OperationProgress.java b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
new file mode 100644
index 0000000..a47a556
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class OperationProgress {
+    /** @hide */
+    protected OperationProgress() {}
+
+    /** @hide */
+    public static @NonNull OperationProgress create(int current, int total) {
+        return new AutoValue_OperationProgress(current, total);
+    }
+
+    /** The overall progress, in the range of [0, 100]. */
+    public int getPercentage() {
+        return 100 * getCurrent() / getTotal();
+    }
+
+    /**
+     * The number of processed items. Can be 0, which means the operation was just started.
+     *
+     * Currently, this is the number of packages, for which dexopt has been done, regardless
+     * of the results (performed, failed, skipped, etc.).
+     *
+     * @hide
+     */
+    public abstract int getCurrent();
+
+    /**
+     * The total number of items. Stays constant during the operation.
+     *
+     * Currently, this is the total number of packages to dexopt.
+     *
+     * @hide
+     */
+    public abstract int getTotal();
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index a27dfa5..54a6a77 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,29 +16,889 @@
 
 package com.android.server.art;
 
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+import static com.android.server.art.testing.TestingUtils.inAnyOrder;
+import static com.android.server.art.testing.TestingUtils.inAnyOrderDeepEquals;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.apphibernation.AppHibernationManager;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
 import androidx.test.filters.SmallTest;
 
-import com.android.server.art.ArtManagerLocal;
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.testing.TestingUtils;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 @SmallTest
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(Parameterized.class)
 public class ArtManagerLocalTest {
+    private static final String PKG_NAME = "com.example.foo";
+    private static final String PKG_NAME_SYS_UI = "com.android.systemui";
+    private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
+    private static final int INACTIVE_DAYS = 1;
+    private static final long CURRENT_TIME_MS = 10000000000l;
+    private static final long RECENT_TIME_MS =
+            CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) + 1;
+    private static final long NOT_RECENT_TIME_MS =
+            CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) - 1;
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(
+            SystemProperties.class, Constants.class, PackageStateModulesUtils.class);
+
+    @Mock private ArtManagerLocal.Injector mInjector;
+    @Mock private PackageManagerLocal mPackageManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private IArtd mArtd;
+    @Mock private DexoptHelper mDexoptHelper;
+    @Mock private AppHibernationManager mAppHibernationManager;
+    @Mock private UserManager mUserManager;
+    @Mock private DexUseManagerLocal mDexUseManager;
+    @Mock private StorageManager mStorageManager;
+    private PackageState mPkgState;
+    private AndroidPackage mPkg;
+    private Config mConfig;
+
+    // True if the primary dex'es are in a readonly partition.
+    @Parameter(0) public boolean mIsInReadonlyPartition;
+
     private ArtManagerLocal mArtManagerLocal;
 
+    @Parameters(name = "isInReadonlyPartition={0}")
+    public static Iterable<? extends Object> data() {
+        return List.of(false, true);
+    }
+
     @Before
-    public void setUp() {
-        mArtManagerLocal = new ArtManagerLocal();
+    public void setUp() throws Exception {
+        mConfig = new Config();
+
+        // Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
+        // These are the default test setups. They may or may not be used depending on the code path
+        // that each test case examines.
+        lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.getDexoptHelper()).thenReturn(mDexoptHelper);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+        lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
+        lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.isSystemUiPackage(PKG_NAME_SYS_UI)).thenReturn(true);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+
+        lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify");
+        lenient()
+                .when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update")))
+                .thenReturn("verify");
+        lenient().when(SystemProperties.get(eq("pm.dexopt.inactive"))).thenReturn("verify");
+        lenient()
+                .when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(
+                        eq("pm.dexopt.boot-after-mainline-update.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(eq("pm.dexopt.inactive.concurrency"), anyInt()))
+                .thenReturn(3);
+        lenient()
+                .when(SystemProperties.getInt(
+                        eq("pm.dexopt.downgrade_after_inactive_days"), anyInt()))
+                .thenReturn(INACTIVE_DAYS);
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
+        lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+        lenient()
+                .when(mUserManager.getUserHandles(anyBoolean()))
+                .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
+
+        // All packages are by default recently used.
+        lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS);
+        List<? extends SecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
+        lenient().doReturn(secondaryDexInfo).when(mDexUseManager).getSecondaryDexInfo(eq(PKG_NAME));
+
+        simulateStorageNotLow();
+
+        lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+        List<PackageState> pkgStates = createPackageStates();
+        for (PackageState pkgState : pkgStates) {
+            lenient()
+                    .when(mSnapshot.getPackageState(pkgState.getPackageName()))
+                    .thenReturn(pkgState);
+        }
+        var packageStateMap = pkgStates.stream().collect(
+                Collectors.toMap(PackageState::getPackageName, it -> it));
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(packageStateMap);
+        mPkgState = mSnapshot.getPackageState(PKG_NAME);
+        mPkg = mPkgState.getAndroidPackage();
+
+        mArtManagerLocal = new ArtManagerLocal(mInjector);
     }
 
     @Test
-    public void testScaffolding() {
-        assertThat(true).isTrue();
+    public void testdeleteDexoptArtifacts() throws Exception {
+        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+        DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+        verifyNoMoreInteractions(mArtd);
+    }
+
+    @Test
+    public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception {
+        lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
+        lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
+        lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
+
+        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+        DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86_64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86_64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86", mIsInReadonlyPartition)));
+        // We assume that the ISA got from `DexUseManagerLocal` is already the translated one.
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+        verifyNoMoreInteractions(mArtd);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsNoPackage() throws Exception {
+        when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME);
+    }
+
+    @Test
+    public void testGetDexoptStatus() throws Exception {
+        doReturn(createGetDexoptStatusResult(
+                         "speed", "compilation-reason-0", "location-debug-string-0"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/base.apk", "arm64", "PCL[]");
+        doReturn(createGetDexoptStatusResult(
+                         "speed-profile", "compilation-reason-1", "location-debug-string-1"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/base.apk", "arm", "PCL[]");
+        doReturn(createGetDexoptStatusResult(
+                         "verify", "compilation-reason-2", "location-debug-string-2"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]");
+        doReturn(createGetDexoptStatusResult(
+                         "extract", "compilation-reason-3", "location-debug-string-3"))
+                .when(mArtd)
+                .getDexoptStatus("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]");
+        doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown"))
+                .when(mArtd)
+                .getDexoptStatus("/data/user/0/foo/1.apk", "arm64", "CLC");
+
+        DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME);
+
+        assertThat(result.getDexContainerFileDexoptStatuses())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptStatus>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "speed", "compilation-reason-0", "location-debug-string-0"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "speed-profile", "compilation-reason-1", "location-debug-string-1"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "compilation-reason-2", "location-debug-string-2"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "extract", "compilation-reason-3", "location-debug-string-3"),
+                        DexContainerFileDexoptStatus.create("/data/user/0/foo/1.apk",
+                                false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "run-from-apk", "unknown", "unknown"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDexoptStatusPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDexoptStatusNoPackage() throws Exception {
+        when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME);
+    }
+
+    @Test
+    public void testGetDexoptStatusNonFatalError() throws Exception {
+        when(mArtd.getDexoptStatus(any(), any(), any()))
+                .thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
+
+        DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME);
+
+        List<DexContainerFileDexoptStatus> statuses = result.getDexContainerFileDexoptStatuses();
+        assertThat(statuses.size()).isEqualTo(5);
+
+        for (DexContainerFileDexoptStatus status : statuses) {
+            assertThat(status.getCompilerFilter()).isEqualTo("error");
+            assertThat(status.getCompilationReason()).isEqualTo("error");
+            assertThat(status.getLocationDebugString()).isEqualTo("some error message");
+        }
+    }
+
+    @Test
+    public void testClearAppProfiles() throws Exception {
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME, "primary")));
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClearAppProfilesPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClearAppProfilesNoPackage() throws Exception {
+        when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME);
+    }
+
+    @Test
+    public void testDexoptPackage() throws Exception {
+        var params = new DexoptParams.Builder("install").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        when(mDexoptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
+                     same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.dexoptPackage(mSnapshot, PKG_NAME, params, cancellationSignal))
+                .isSameInstanceAs(result);
+    }
+
+    @Test
+    public void testResetDexoptStatus() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        when(mDexoptHelper.dexopt(
+                     any(), deepEq(List.of(PKG_NAME)), any(), same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.resetDexoptStatus(mSnapshot, PKG_NAME, cancellationSignal))
+                .isSameInstanceAs(result);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME, "primary")));
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mIsInReadonlyPartition)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mIsInReadonlyPartition)));
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+    }
+
+    @Test
+    public void testDexoptPackages() throws Exception {
+        var dexoptResult = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_SYS_UI)).thenReturn(CURRENT_TIME_MS);
+        simulateStorageLow();
+
+        // It should use the default package list and params. The list is sorted by last active
+        // time in descending order.
+        doReturn(dexoptResult)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_SYS_UI, PKG_NAME)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")),
+                        same(cancellationSignal), any(), any(), any());
+
+        assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                           null /* processCallbackExecutor */, null /* processCallback */))
+                .isSameInstanceAs(dexoptResult);
+
+        // Nothing to downgrade.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesRecentlyInstalled() throws Exception {
+        // The package is recently installed but hasn't been used.
+        PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(0l);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME should be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), inAnyOrder(PKG_NAME, PKG_NAME_SYS_UI),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME should not be downgraded.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesInactive() throws Exception {
+        // PKG_NAME is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_SYS_UI)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        // PKG_NAME should be downgraded.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME)),
+                        argThat(params -> params.getReason().equals("inactive")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testDexoptPackagesInactiveStorageNotLow() throws Exception {
+        // PKG_NAME is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(NOT_RECENT_TIME_MS);
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_SYS_UI)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME should not be downgraded because the storage is not low.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesBootAfterMainlineUpdate() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+        simulateStorageLow();
+
+        // It should only dexopt system UI.
+        when(mDexoptHelper.dexopt(
+                     any(), deepEq(List.of(PKG_NAME_SYS_UI)), any(), any(), any(), any(), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update",
+                           cancellationSignal, null /* processCallbackExecutor */,
+                           null /* processCallback */))
+                .isSameInstanceAs(result);
+
+        // It should never downgrade apps, even if the storage is low.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesOverride() throws Exception {
+        // PKG_NAME is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME)).thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        var params = new DexoptParams.Builder("bg-dexopt").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    assertThat(reason).isEqualTo("bg-dexopt");
+                    assertThat(defaultPackages).containsExactly(PKG_NAME_SYS_UI);
+                    assertThat(passedSignal).isSameInstanceAs(cancellationSignal);
+                    builder.setPackages(List.of(PKG_NAME)).setDexoptParams(params);
+                });
+
+        // It should use the overridden package list and params.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME)), same(params), any(), any(), any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // It should not downgrade PKG_NAME because it's in the overridden package list. It should
+        // not downgrade PKG_NAME_SYS_UI either because it's not an inactive package.
+        verify(mDexoptHelper, never())
+                .dexopt(any(), any(), argThat(params2 -> params2.getReason().equals("inactive")),
+                        any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDexoptPackagesOverrideCleared() throws Exception {
+        var params = new DexoptParams.Builder("bg-dexopt").build();
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    builder.setPackages(List.of(PKG_NAME)).setDexoptParams(params);
+                });
+        mArtManagerLocal.clearBatchDexoptStartCallback();
+
+        // It should use the default package list and params.
+        when(mDexoptHelper.dexopt(any(), inAnyOrder(PKG_NAME, PKG_NAME_SYS_UI), not(same(params)),
+                     same(cancellationSignal), any(), any(), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                           null /* processCallbackExecutor */, null /* processCallback */))
+                .isSameInstanceAs(result);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testDexoptPackagesOverrideReasonChanged() throws Exception {
+        var params = new DexoptParams.Builder("first-boot").build();
+        var cancellationSignal = new CancellationSignal();
+
+        mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    builder.setDexoptParams(params);
+                });
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testSnapshotAppProfile() throws Exception {
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = false;
+
+        File tempFile = File.createTempFile("primary", ".prof");
+        tempFile.deleteOnExit();
+
+        when(mArtd.mergeProfiles(
+                     deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME, "primary"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "primary",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of("/data/app/foo/base.apk")), deepEq(options)))
+                .thenAnswer(invocation -> {
+                    try (var writer = new FileWriter(tempFile)) {
+                        writer.write("snapshot");
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                    var output = invocation.<OutputProfile>getArgument(2);
+                    output.profilePath.tmpPath = tempFile.getPath();
+                    return true;
+                });
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+
+        verify(mArtd).deleteProfile(
+                argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
+
+        try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+            String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+            assertThat(contents).isEqualTo("snapshot");
+        }
+    }
+
+    @Test
+    public void testSnapshotAppProfileSplit() throws Exception {
+        when(mArtd.mergeProfiles(deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(
+                                                        PKG_NAME, "split_0.split"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 0 /* userId */, PKG_NAME, "split_0.split"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 1 /* userId */, PKG_NAME, "split_0.split"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "split_0.split",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of("/data/app/foo/split_0.apk")), any()))
+                .thenReturn(false);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "split_0");
+    }
+
+    @Test
+    public void testSnapshotAppProfileEmpty() throws Exception {
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+
+        verify(mArtd, never()).deleteProfile(any());
+
+        try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+            assertThat(inputStream.readAllBytes()).isEmpty();
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfilePackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileNoPackage() throws Exception {
+        when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileSplitNotFound() throws Exception {
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "non-existent-split");
+    }
+
+    @Test
+    public void testDumpAppProfile() throws Exception {
+        var options = new MergeProfileOptions();
+        options.dumpOnly = true;
+
+        when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                mSnapshot, PKG_NAME, null /* splitName */, false /* dumpClassesAndMethods */);
+    }
+
+    @Test
+    public void testDumpAppProfileDumpClassesAndMethods() throws Exception {
+        var options = new MergeProfileOptions();
+        options.dumpClassesAndMethods = true;
+
+        when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                mSnapshot, PKG_NAME, null /* splitName */, true /* dumpClassesAndMethods */);
+    }
+
+    @Test
+    public void testSnapshotBootImageProfile() throws Exception {
+        // `lenient()` is required to allow mocking the same method multiple times.
+        lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1");
+        lenient().when(Constants.getenv("SYSTEMSERVERCLASSPATH")).thenReturn("sscp0:sscp1");
+        lenient().when(Constants.getenv("STANDALONE_SYSTEMSERVER_JARS")).thenReturn("sssj0:sssj1");
+
+        var options = new MergeProfileOptions();
+        options.forceMerge = true;
+        options.forBootImage = true;
+
+        when(mArtd.mergeProfiles(
+                     inAnyOrderDeepEquals(
+                             AidlUtils.buildProfilePathForPrimaryRef("android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, "android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, "android", "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_SYS_UI, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_SYS_UI, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_SYS_UI, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(
+                                     PKG_NAME_HIBERNATING, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_HIBERNATING, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_HIBERNATING, "primary")),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")),
+                     deepEq(options)))
+                .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+        mArtManagerLocal.snapshotBootImageProfile(mSnapshot);
+    }
+
+    private AndroidPackage createPackage(boolean multiSplit) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+
+        if (multiSplit) {
+            // split_0 has code while split_1 doesn't.
+            var split0 = mock(AndroidPackageSplit.class);
+            lenient().when(split0.getName()).thenReturn("split_0");
+            lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+            lenient().when(split0.isHasCode()).thenReturn(true);
+            var split1 = mock(AndroidPackageSplit.class);
+            lenient().when(split1.getName()).thenReturn("split_1");
+            lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+            lenient().when(split1.isHasCode()).thenReturn(false);
+
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
+        } else {
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+        }
+
+        return pkg;
+    }
+
+    private PackageUserState createPackageUserState() {
+        PackageUserState pkgUserState = mock(PackageUserState.class);
+        lenient().when(pkgUserState.isInstalled()).thenReturn(true);
+        // All packages are by default pre-installed.
+        lenient().when(pkgUserState.getFirstInstallTimeMillis()).thenReturn(0l);
+        return pkgUserState;
+    }
+
+    private PackageState createPackageState(
+            String packageName, boolean isDexoptable, boolean multiSplit) {
+        PackageState pkgState = mock(PackageState.class);
+
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.isSystem()).thenReturn(mIsInReadonlyPartition);
+        lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+
+        AndroidPackage pkg = createPackage(multiSplit);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+
+        PackageUserState pkgUserState0 = createPackageUserState();
+        lenient().when(pkgState.getStateForUser(UserHandle.of(0))).thenReturn(pkgUserState0);
+        PackageUserState pkgUserState1 = createPackageUserState();
+        lenient().when(pkgState.getStateForUser(UserHandle.of(1))).thenReturn(pkgUserState1);
+
+        lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(isDexoptable);
+
+        return pkgState;
+    }
+
+    private List<PackageState> createPackageStates() {
+        PackageState pkgState =
+                createPackageState(PKG_NAME, true /* isDexoptable */, true /* multiSplit */);
+
+        PackageState sysUiPkgState = createPackageState(
+                PKG_NAME_SYS_UI, true /* isDexoptable */, false /* multiSplit */);
+
+        // This should not be dexopted because it's hibernating. However, it should be included
+        // when snapshotting boot image profile.
+        PackageState pkgHibernatingState = createPackageState(
+                PKG_NAME_HIBERNATING, true /* isDexoptable */, false /* multiSplit */);
+        lenient()
+                .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
+                .thenReturn(true);
+
+        // This should not be dexopted because it's not dexoptable.
+        PackageState nonDexoptablePkgState = createPackageState(
+                "com.example.non-dexoptable", false /* isDexoptable */, false /* multiSplit */);
+
+        return List.of(pkgState, sysUiPkgState, pkgHibernatingState, nonDexoptablePkgState);
+    }
+
+    private GetDexoptStatusResult createGetDexoptStatusResult(
+            String compilerFilter, String compilationReason, String locationDebugString) {
+        var getDexoptStatusResult = new GetDexoptStatusResult();
+        getDexoptStatusResult.compilerFilter = compilerFilter;
+        getDexoptStatusResult.compilationReason = compilationReason;
+        getDexoptStatusResult.locationDebugString = locationDebugString;
+        return getDexoptStatusResult;
+    }
+
+    private List<? extends SecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+        var dexInfo = mock(SecondaryDexInfo.class);
+        lenient().when(dexInfo.dexPath()).thenReturn("/data/user/0/foo/1.apk");
+        lenient().when(dexInfo.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dexInfo.classLoaderContext()).thenReturn("CLC");
+        return List.of(dexInfo);
+    }
+
+    private void simulateStorageLow() throws Exception {
+        lenient()
+                .when(mStorageManager.getAllocatableBytes(any()))
+                .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES - 1);
+    }
+
+    private void simulateStorageNotLow() throws Exception {
+        lenient()
+                .when(mStorageManager.getAllocatableBytes(any()))
+                .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES);
     }
 }
diff --git a/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
new file mode 100644
index 0000000..b6a0a81
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.model.Config.Callback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.BackgroundDexoptJob.CompletedResult;
+import com.android.server.art.BackgroundDexoptJob.FatalErrorResult;
+import com.android.server.art.BackgroundDexoptJob.Result;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundDexoptJobTest {
+    private static final long TIMEOUT_SEC = 1;
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, BackgroundDexoptJobService.class);
+
+    @Mock private BackgroundDexoptJob.Injector mInjector;
+    @Mock private ArtManagerLocal mArtManagerLocal;
+    @Mock private PackageManagerLocal mPackageManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private JobScheduler mJobScheduler;
+    @Mock private DexoptResult mDexoptResult;
+    @Mock private BackgroundDexoptJobService mJobService;
+    @Mock private JobParameters mJobParameters;
+    private Config mConfig;
+    private BackgroundDexoptJob mBackgroundDexoptJob;
+    private Semaphore mJobFinishedCalled = new Semaphore(0);
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(false);
+
+        lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+
+        mConfig = new Config();
+
+        lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+        lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+        lenient().when(mInjector.getJobScheduler()).thenReturn(mJobScheduler);
+
+        mBackgroundDexoptJob = new BackgroundDexoptJob(mInjector);
+        lenient().when(BackgroundDexoptJobService.getJob()).thenReturn(mBackgroundDexoptJob);
+
+        lenient()
+                .doAnswer(invocation -> {
+                    mJobFinishedCalled.release();
+                    return null;
+                })
+                .when(mJobService)
+                .jobFinished(any(), anyBoolean());
+
+        lenient()
+                .when(mJobParameters.getStopReason())
+                .thenReturn(JobParameters.STOP_REASON_UNDEFINED);
+    }
+
+    @Test
+    public void testStart() {
+        when(mArtManagerLocal.dexoptPackages(
+                     same(mSnapshot), eq(ReasonMapping.REASON_BG_DEXOPT), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        Result result = Utils.getFuture(mBackgroundDexoptJob.start());
+        assertThat(result).isInstanceOf(CompletedResult.class);
+        assertThat(((CompletedResult) result).dexoptResult()).isSameInstanceAs(mDexoptResult);
+    }
+
+    @Test
+    public void testStartAlreadyRunning() {
+        Semaphore dexoptDone = new Semaphore(0);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenAnswer(invocation -> {
+                    assertThat(dexoptDone.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+                    return mDexoptResult;
+                });
+
+        Future<Result> future1 = mBackgroundDexoptJob.start();
+        Future<Result> future2 = mBackgroundDexoptJob.start();
+        assertThat(future1).isSameInstanceAs(future2);
+
+        dexoptDone.release();
+        Utils.getFuture(future1);
+
+        verify(mArtManagerLocal, times(1)).dexoptPackages(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testStartAnother() {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        Future<Result> future1 = mBackgroundDexoptJob.start();
+        Utils.getFuture(future1);
+        Future<Result> future2 = mBackgroundDexoptJob.start();
+        Utils.getFuture(future2);
+        assertThat(future1).isNotSameInstanceAs(future2);
+    }
+
+    @Test
+    public void testStartFatalError() {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenThrow(IllegalStateException.class);
+
+        Result result = Utils.getFuture(mBackgroundDexoptJob.start());
+        assertThat(result).isInstanceOf(FatalErrorResult.class);
+    }
+
+    @Test
+    public void testStartIgnoreDisabled() {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(true);
+
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        // The `start` method should ignore the system property. The system property is for
+        // `schedule`.
+        Utils.getFuture(mBackgroundDexoptJob.start());
+    }
+
+    @Test
+    public void testCancel() {
+        Semaphore dexoptCancelled = new Semaphore(0);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenAnswer(invocation -> {
+                    assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+                    var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+                    assertThat(cancellationSignal.isCanceled()).isTrue();
+                    return mDexoptResult;
+                });
+
+        Future<Result> future = mBackgroundDexoptJob.start();
+        mBackgroundDexoptJob.cancel();
+        dexoptCancelled.release();
+        Utils.getFuture(future);
+    }
+
+    @Test
+    public void testSchedule() {
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS);
+        assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
+        assertThat(jobInfo.isRequireCharging()).isTrue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
+        assertThat(jobInfo.isRequireStorageNotLow()).isFalse();
+    }
+
+    @Test
+    public void testScheduleDisabled() {
+        when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+                .thenReturn(true);
+
+        assertThat(mBackgroundDexoptJob.schedule())
+                .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
+
+        verify(mJobScheduler, never()).schedule(any());
+    }
+
+    @Test
+    public void testScheduleOverride() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(Runnable::run, builder -> {
+            builder.setRequiresBatteryNotLow(false);
+            builder.setPriority(JobInfo.PRIORITY_LOW);
+        });
+
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS);
+        assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
+        assertThat(jobInfo.isRequireCharging()).isTrue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isFalse();
+        assertThat(jobInfo.getPriority()).isEqualTo(JobInfo.PRIORITY_LOW);
+    }
+
+    @Test
+    public void testScheduleOverrideCleared() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(
+                Runnable::run, builder -> { builder.setRequiresBatteryNotLow(false); });
+        mConfig.clearScheduleBackgroundDexoptJobCallback();
+
+        var captor = ArgumentCaptor.forClass(JobInfo.class);
+        when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+
+        assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+
+        JobInfo jobInfo = captor.getValue();
+        assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testScheduleOverrideStorageNotLow() {
+        mConfig.setScheduleBackgroundDexoptJobCallback(
+                Runnable::run, builder -> { builder.setRequiresStorageNotLow(true); });
+
+        mBackgroundDexoptJob.schedule();
+    }
+
+    @Test
+    public void testUnschedule() {
+        mBackgroundDexoptJob.unschedule();
+        verify(mJobScheduler).cancel(anyInt());
+    }
+
+    @Test
+    public void testWantsRescheduleFalsePerformed() throws Exception {
+        when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_PERFORMED);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */);
+    }
+
+    @Test
+    public void testWantsRescheduleFalseFatalError() throws Exception {
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenThrow(RuntimeException.class);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */);
+    }
+
+    @Test
+    public void testWantsRescheduleTrue() throws Exception {
+        when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_CANCELLED);
+        when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any()))
+                .thenReturn(mDexoptResult);
+
+        mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters);
+        assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        verify(mJobService).jobFinished(any(), eq(true) /* wantsReschedule */);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DebouncerTest.java b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java
new file mode 100644
index 0000000..bf0bc70
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.lenient;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.testing.MockClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class DebouncerTest {
+    private MockClock mMockClock;
+    private Debouncer mDebouncer;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockClock = new MockClock();
+        mDebouncer =
+                new Debouncer(100 /* intervalMs */, () -> mMockClock.createScheduledExecutor());
+    }
+
+    @Test
+    public void test() throws Exception {
+        List<Integer> list = new ArrayList<>();
+
+        mDebouncer.maybeRunAsync(() -> list.add(1));
+        mDebouncer.maybeRunAsync(() -> list.add(2));
+        mMockClock.advanceTime(100);
+        mDebouncer.maybeRunAsync(() -> list.add(3));
+        mMockClock.advanceTime(99);
+        mDebouncer.maybeRunAsync(() -> list.add(4));
+        mMockClock.advanceTime(99);
+        mDebouncer.maybeRunAsync(() -> list.add(5));
+        mMockClock.advanceTime(1000);
+
+        assertThat(list).containsExactly(2, 5).inOrder();
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
new file mode 100644
index 0000000..5eb5f71
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.DexContainerFileUseInfo;
+import com.android.server.art.testing.MockClock;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexUseManagerTest {
+    private static final String LOADING_PKG_NAME = "com.example.loadingpackage";
+    private static final String OWNING_PKG_NAME = "com.example.owningpackage";
+    private static final String BASE_APK = "/data/app/" + OWNING_PKG_NAME + "/base.apk";
+    private static final String SPLIT_APK = "/data/app/" + OWNING_PKG_NAME + "/split_0.apk";
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class, Process.class);
+
+    private final UserHandle mUserHandle = Binder.getCallingUserHandle();
+
+    /**
+     * The default value of `isDexFilePublic` returned by `getSecondaryDexInfo`. The value doesn't
+     * matter because it's undefined, but it's needed for deep equality check, to make the test
+     * simpler.
+     */
+    private final boolean mDefaultIsDexFilePublic = true;
+
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private DexUseManagerLocal.Injector mInjector;
+    @Mock private IArtd mArtd;
+    @Mock private Context mContext;
+    private DexUseManagerLocal mDexUseManager;
+    private String mCeDir;
+    private String mDeDir;
+    private MockClock mMockClock;
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient().when(Process.isIsolatedUid(anyInt())).thenReturn(false);
+
+        PackageState loadingPkgState = createPackageState(LOADING_PKG_NAME, "armeabi-v7a");
+        lenient().when(mSnapshot.getPackageState(eq(LOADING_PKG_NAME))).thenReturn(loadingPkgState);
+        PackageState owningPkgState = createPackageState(OWNING_PKG_NAME, "arm64-v8a");
+        lenient().when(mSnapshot.getPackageState(eq(OWNING_PKG_NAME))).thenReturn(owningPkgState);
+
+        lenient()
+                .when(mSnapshot.getPackageStates())
+                .thenReturn(
+                        Map.of(LOADING_PKG_NAME, loadingPkgState, OWNING_PKG_NAME, owningPkgState));
+
+        mBroadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        lenient()
+                .when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any()))
+                .thenReturn(mock(Intent.class));
+
+        mCeDir = Environment
+                         .getDataCePackageDirectoryForUser(StorageManager.UUID_DEFAULT,
+                                 Binder.getCallingUserHandle(), OWNING_PKG_NAME)
+                         .toString();
+        mDeDir = Environment
+                         .getDataDePackageDirectoryForUser(StorageManager.UUID_DEFAULT,
+                                 Binder.getCallingUserHandle(), OWNING_PKG_NAME)
+                         .toString();
+        mMockClock = new MockClock();
+
+        File tempFile = File.createTempFile("package-dex-usage", ".pb");
+        tempFile.deleteOnExit();
+
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l);
+        lenient().when(mInjector.getFilename()).thenReturn(tempFile.getPath());
+        lenient()
+                .when(mInjector.createScheduledExecutor())
+                .thenAnswer(invocation -> mMockClock.createScheduledExecutor());
+        lenient().when(mInjector.getContext()).thenReturn(mContext);
+
+        mDexUseManager = new DexUseManagerLocal(mInjector);
+        mDexUseManager.systemReady();
+    }
+
+    @Test
+    public void testPrimaryDexOwned() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    @Test
+    public void testPrimaryDexOwnedIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    @Test
+    public void testPrimaryDexOwnedSplitIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)).isTrue();
+    }
+
+    @Test
+    public void testPrimaryDexOthers() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */));
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue();
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty();
+        assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK))
+                .isFalse();
+    }
+
+    /** Checks that it ignores and dedups things correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntries() throws Exception {
+        verifyPrimaryDexMultipleEntries(false /* saveAndLoad */, false /* shutdown */);
+    }
+
+    /** Checks that it saves data after some time has passed and loads data correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntriesPersisted() throws Exception {
+        verifyPrimaryDexMultipleEntries(true /*saveAndLoad */, false /* shutdown */);
+    }
+
+    /** Checks that it saves data when the device is being shutdown and loads data correctly. */
+    @Test
+    public void testPrimaryDexMultipleEntriesPersistedDueToShutdown() throws Exception {
+        verifyPrimaryDexMultipleEntries(true /*saveAndLoad */, true /* shutdown */);
+    }
+
+    private void verifyPrimaryDexMultipleEntries(boolean saveAndLoad, boolean shutdown)
+            throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        // These should be ignored.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(BASE_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of("/data/app/" + OWNING_PKG_NAME + "/non-existing.apk", "CLC"));
+
+        // Some of these should be deduped.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+        when(mInjector.getCurrentTimeMillis()).thenReturn(2000l);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+        if (saveAndLoad) {
+            if (shutdown) {
+                mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class));
+            } else {
+                // MockClock runs tasks synchronously.
+                mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+            }
+            mDexUseManager = new DexUseManagerLocal(mInjector);
+        }
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                        DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */),
+                        DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */));
+
+        assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK))
+                .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */));
+
+        assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l);
+    }
+
+    @Test
+    public void testSecondaryDexOwned() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+                        false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexOwnedIsolated() {
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mDeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mDeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexOthers() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
+    }
+
+    @Test
+    public void testSecondaryDexUnsupportedClc() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT, Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
+    }
+
+    @Test
+    public void testSecondaryDexVariableClc() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC2"));
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
+                        Set.of("arm64-v8a", "armeabi-v7a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                                DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+        assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
+    }
+
+    /** Checks that it ignores and dedups things correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntries() throws Exception {
+        verifySecondaryDexMultipleEntries(false /*saveAndLoad */, false /* shutdown */);
+    }
+
+    /** Checks that it saves data after some time has passed and loads data correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntriesPersisted() throws Exception {
+        verifySecondaryDexMultipleEntries(true /*saveAndLoad */, false /* shutdown */);
+    }
+
+    /** Checks that it saves data when the device is being shutdown and loads data correctly. */
+    @Test
+    public void testSecondaryDexMultipleEntriesPersistedDueToShutdown() throws Exception {
+        verifySecondaryDexMultipleEntries(true /*saveAndLoad */, true /* shutdown */);
+    }
+
+    private void verifySecondaryDexMultipleEntries(boolean saveAndLoad, boolean shutdown)
+            throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        // These should be ignored.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of("/some/non-existing.apk", "CLC"));
+
+        // Some of these should be deduped.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", "CLC", mCeDir + "/bar.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", "UpdatedCLC", mCeDir + "/bar.apk", "UpdatedCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "UpdatedCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "DifferentCLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "UpdatedDifferentCLC"));
+
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/baz.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        when(mInjector.getCurrentTimeMillis()).thenReturn(2000l);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+                Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+        if (saveAndLoad) {
+            if (shutdown) {
+                mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class));
+            } else {
+                // MockClock runs tasks synchronously.
+                mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+            }
+            mDexUseManager = new DexUseManagerLocal(mInjector);
+        }
+
+        List<? extends SecondaryDexInfo> dexInfoList =
+                mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+        assertThat(dexInfoList)
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                                         "UpdatedCLC", Set.of("arm64-v8a", "armeabi-v7a"),
+                                         Set.of(DexLoader.create(OWNING_PKG_NAME,
+                                                        false /* isolatedProcess */),
+                                                 DexLoader.create(OWNING_PKG_NAME,
+                                                         true /* isolatedProcess */),
+                                                 DexLoader.create(LOADING_PKG_NAME,
+                                                         false /* isolatedProcess */)),
+                                         true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+                        DetailedSecondaryDexInfo.create(mCeDir + "/bar.apk", mUserHandle,
+                                SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
+                                Set.of("arm64-v8a", "armeabi-v7a"),
+                                Set.of(DexLoader.create(
+                                               OWNING_PKG_NAME, false /* isolatedProcess */),
+                                        DexLoader.create(
+                                                LOADING_PKG_NAME, false /* isolatedProcess */)),
+                                true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+                        DetailedSecondaryDexInfo.create(mCeDir + "/baz.apk", mUserHandle,
+                                SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT,
+                                Set.of("arm64-v8a"),
+                                Set.of(DexLoader.create(
+                                        OWNING_PKG_NAME, false /* isolatedProcess */)),
+                                false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+
+        assertThat(mDexUseManager.getSecondaryDexContainerFileUseInfo(OWNING_PKG_NAME))
+                .containsExactly(DexContainerFileUseInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                                         Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)),
+                        DexContainerFileUseInfo.create(mCeDir + "/bar.apk", mUserHandle,
+                                Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)),
+                        DexContainerFileUseInfo.create(
+                                mCeDir + "/baz.apk", mUserHandle, Set.of(OWNING_PKG_NAME)));
+
+        assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l);
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexPublic() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a", "armeabi-v7a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+                                DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, true /* isDexFilePublic */));
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexPrivate() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("arm64-v8a"),
+                        Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+                        false /* isUsedByOtherApps */, false /* isDexFilePublic */));
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexFilteredDueToVisibility() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+    }
+
+    @Test
+    public void testFilteredDetailedSecondaryDexFilteredDueToNotFound() throws Exception {
+        when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
+
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testUnknownPackage() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, "bogus", Map.of(BASE_APK, "CLC"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEmptyMap() {
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, Map.of());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullKey() {
+        var map = new HashMap<String, String>();
+        map.put(null, "CLC");
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNonAbsoluteKey() {
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of("a/b.jar", "CLC"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullValue() {
+        var map = new HashMap<String, String>();
+        map.put(mCeDir + "/foo.apk", null);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+    }
+
+    @Test
+    public void testFileNotFound() {
+        // It should fail to load the file.
+        when(mInjector.getFilename()).thenReturn("/nonexisting/file");
+        mDexUseManager = new DexUseManagerLocal(mInjector);
+
+        // Add some arbitrary data to see if it works fine after a failed load.
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+        assertThat(mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME))
+                .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+                        "CLC", Set.of("armeabi-v7a"),
+                        Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+                        true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+    }
+
+    private AndroidPackage createPackage(String packageName) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+        lenient().when(pkg.getStorageUuid()).thenReturn(StorageManager.UUID_DEFAULT);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/" + packageName + "/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/" + packageName + "/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0));
+
+        return pkg;
+    }
+
+    private PackageState createPackageState(String packageName, String primaryAbi) {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        AndroidPackage pkg = createPackage(packageName);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi);
+        return pkgState;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
new file mode 100644
index 0000000..a7651e6
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.apphibernation.AppHibernationManager;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.OperationProgress;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexoptHelperTest {
+    private static final String PKG_NAME_FOO = "com.example.foo";
+    private static final String PKG_NAME_BAR = "com.example.bar";
+    private static final String PKG_NAME_LIB1 = "com.example.lib1";
+    private static final String PKG_NAME_LIB2 = "com.example.lib2";
+    private static final String PKG_NAME_LIB3 = "com.example.lib3";
+    private static final String PKG_NAME_LIB4 = "com.example.lib4";
+    private static final String PKG_NAME_LIBBAZ = "com.example.libbaz";
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(PackageStateModulesUtils.class);
+
+    @Mock private DexoptHelper.Injector mInjector;
+    @Mock private PrimaryDexopter mPrimaryDexopter;
+    @Mock private SecondaryDexopter mSecondaryDexopter;
+    @Mock private AppHibernationManager mAhm;
+    @Mock private PowerManager mPowerManager;
+    @Mock private PowerManager.WakeLock mWakeLock;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    private PackageState mPkgStateFoo;
+    private PackageState mPkgStateBar;
+    private PackageState mPkgStateLib1;
+    private PackageState mPkgStateLib2;
+    private PackageState mPkgStateLib4;
+    private PackageState mPkgStateLibbaz;
+    private AndroidPackage mPkgFoo;
+    private AndroidPackage mPkgBar;
+    private AndroidPackage mPkgLib1;
+    private AndroidPackage mPkgLib2;
+    private AndroidPackage mPkgLib4;
+    private AndroidPackage mPkgLibbaz;
+    private CancellationSignal mCancellationSignal;
+    private ExecutorService mExecutor;
+    private List<DexContainerFileDexoptResult> mPrimaryResults;
+    private List<DexContainerFileDexoptResult> mSecondaryResults;
+    private Config mConfig;
+    private DexoptParams mParams;
+    private List<String> mRequestedPackages;
+    private DexoptHelper mDexoptHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(mPowerManager.newWakeLock(eq(PowerManager.PARTIAL_WAKE_LOCK), any()))
+                .thenReturn(mWakeLock);
+
+        lenient().when(mAhm.isHibernatingGlobally(any())).thenReturn(false);
+        lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+        mCancellationSignal = new CancellationSignal();
+        mExecutor = Executors.newSingleThreadExecutor();
+        mConfig = new Config();
+
+        preparePackagesAndLibraries();
+
+        mPrimaryResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_PERFORMED /* status2 */);
+        mSecondaryResults = createResults("/data/user_de/0/foo/foo.apk",
+                DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                DexoptResult.DEXOPT_PERFORMED /* status2 */);
+
+        lenient()
+                .when(mInjector.getPrimaryDexopter(any(), any(), any(), any()))
+                .thenReturn(mPrimaryDexopter);
+        lenient().when(mPrimaryDexopter.dexopt()).thenReturn(mPrimaryResults);
+
+        lenient()
+                .when(mInjector.getSecondaryDexopter(any(), any(), any(), any()))
+                .thenReturn(mSecondaryDexopter);
+        lenient().when(mSecondaryDexopter.dexopt()).thenReturn(mSecondaryResults);
+
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAhm);
+        lenient().when(mInjector.getPowerManager()).thenReturn(mPowerManager);
+        lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+
+        mDexoptHelper = new DexoptHelper(mInjector);
+    }
+
+    @After
+    public void tearDown() {
+        mExecutor.shutdown();
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        // Only package libbaz fails.
+        var failingPrimaryDexopter = mock(PrimaryDexopter.class);
+        List<DexContainerFileDexoptResult> partialFailureResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        lenient().when(failingPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateLibbaz), any(), any(), any()))
+                .thenReturn(failingPrimaryDexopter);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
+        assertThat(result.getReason()).isEqualTo("install");
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_FAILED);
+
+        // The requested packages must come first.
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_FAILED,
+                List.of(partialFailureResults, mSecondaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        // The order matters. It should acquire the wake lock only once, at the beginning, and
+        // release the wake lock at the end. When running in a single thread, it should dexopt
+        // primary dex files and the secondary dex files together for each package, and it should
+        // dexopt requested packages, in the given order, and then dexopt dependencies.
+        InOrder inOrder = inOrder(mInjector, mWakeLock);
+        inOrder.verify(mWakeLock).setWorkSource(any());
+        inOrder.verify(mWakeLock).acquire(anyLong());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
+        inOrder.verify(mWakeLock).release();
+
+        verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */);
+
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    @Test
+    public void testDexoptNoDependencies() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(3);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptPrimaryOnly() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+
+        verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptPrimaryOnlyNoDependencies() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(0,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(3);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults));
+
+        verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception {
+        when(mPrimaryDexopter.dexopt()).thenAnswer(invocation -> {
+            mCancellationSignal.cancel();
+            return mPrimaryResults;
+        });
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_CANCELLED);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_CANCELLED,
+                List.of(mPrimaryResults));
+        checkPackageResult(
+                result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_CANCELLED, List.of());
+        checkPackageResult(
+                result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_CANCELLED, List.of());
+
+        verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+
+        verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptNotDexoptable() throws Exception {
+        when(PackageStateModulesUtils.isDexoptable(mPkgStateFoo)).thenReturn(false);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(result.getPackageDexoptResults()).hasSize(1);
+        checkPackageResult(
+                result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptLibraryNotDexoptable() throws Exception {
+        when(PackageStateModulesUtils.isDexoptable(mPkgStateLib1)).thenReturn(false);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(result.getPackageDexoptResults()).hasSize(1);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+
+        verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 1 /* expectedSecondaryTimes */);
+    }
+
+    @Test
+    public void testDexoptIsHibernating() throws Exception {
+        lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
+
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        checkPackageResult(
+                result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptIsHibernatingButOatArtifactDeletionDisabled() throws Exception {
+        lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
+        lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(result.getPackageDexoptResults()).hasSize(6);
+        checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+        checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
+                List.of(mPrimaryResults, mSecondaryResults));
+    }
+
+    @Test
+    public void testDexoptAlwaysReleasesWakeLock() throws Exception {
+        when(mPrimaryDexopter.dexopt()).thenThrow(IllegalStateException.class);
+
+        try {
+            mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+        } catch (Exception ignored) {
+        }
+
+        verify(mWakeLock).release();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDexoptPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(any())).thenReturn(null);
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        verifyNoDexopt();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDexoptNoPackage() throws Exception {
+        lenient().when(mPkgStateFoo.getAndroidPackage()).thenReturn(null);
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        verifyNoDexopt();
+    }
+
+    @Test
+    public void testDexoptSplit() throws Exception {
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                          .setSplitName("split_0")
+                          .build();
+
+        mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+    }
+
+    @Test
+    public void testDexoptSplitNotFound() throws Exception {
+        mRequestedPackages = List.of(PKG_NAME_FOO);
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                          .setSplitName("split_bogus")
+                          .build();
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+        });
+    }
+
+    @Test
+    public void testCallbacks() throws Exception {
+        List<DexoptResult> list1 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list1.add(result));
+
+        List<DexoptResult> list2 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(list1).containsExactly(result);
+        assertThat(list2).containsExactly(result);
+    }
+
+    @Test
+    public void testCallbackRemoved() throws Exception {
+        List<DexoptResult> list1 = new ArrayList<>();
+        DexoptDoneCallback callback1 = result -> list1.add(result);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback1);
+
+        List<DexoptResult> list2 = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
+
+        mConfig.removeDexoptDoneCallback(callback1);
+
+        DexoptResult result = mDexoptHelper.dexopt(
+                mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+        assertThat(list1).isEmpty();
+        assertThat(list2).containsExactly(result);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCallbackAlreadyAdded() throws Exception {
+        List<DexoptResult> list = new ArrayList<>();
+        DexoptDoneCallback callback = result -> list.add(result);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
+        mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
+    }
+
+    // Tests `addDexoptDoneCallback` with `onlyIncludeUpdates` being true and false.
+    @Test
+    public void testCallbackWithFailureResults() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(0,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        // This list should collect all results.
+        List<DexoptResult> listAll = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(
+                false /* onlyIncludeUpdates */, Runnable::run, result -> listAll.add(result));
+
+        // This list should only collect results that have updates.
+        List<DexoptResult> listOnlyIncludeUpdates = new ArrayList<>();
+        mConfig.addDexoptDoneCallback(true /* onlyIncludeUpdates */, Runnable::run,
+                result -> listOnlyIncludeUpdates.add(result));
+
+        // Dexopt partially fails on package "foo".
+        List<DexContainerFileDexoptResult> partialFailureResults =
+                createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        var fooPrimaryDexopter = mock(PrimaryDexopter.class);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateFoo), any(), any(), any()))
+                .thenReturn(fooPrimaryDexopter);
+        when(fooPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
+
+        // Dexopt totally fails on package "bar".
+        List<DexContainerFileDexoptResult> totalFailureResults =
+                createResults("/data/app/bar/base.apk", DexoptResult.DEXOPT_FAILED /* status1 */,
+                        DexoptResult.DEXOPT_FAILED /* status2 */);
+        var barPrimaryDexopter = mock(PrimaryDexopter.class);
+        when(mInjector.getPrimaryDexopter(same(mPkgStateBar), any(), any(), any()))
+                .thenReturn(barPrimaryDexopter);
+        when(barPrimaryDexopter.dexopt()).thenReturn(totalFailureResults);
+
+        DexoptResult resultWithSomeUpdates = mDexoptHelper.dexopt(mSnapshot,
+                List.of(PKG_NAME_FOO, PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
+        DexoptResult resultWithNoUpdates = mDexoptHelper.dexopt(
+                mSnapshot, List.of(PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
+
+        assertThat(listAll).containsExactly(resultWithSomeUpdates, resultWithNoUpdates);
+
+        assertThat(listOnlyIncludeUpdates).hasSize(1);
+        assertThat(listOnlyIncludeUpdates.get(0)
+                           .getPackageDexoptResults()
+                           .stream()
+                           .map(PackageDexoptResult::getPackageName)
+                           .collect(Collectors.toList()))
+                .containsExactly(PKG_NAME_FOO);
+    }
+
+    @Test
+    public void testProgressCallback() throws Exception {
+        mParams = new DexoptParams.Builder("install")
+                          .setCompilerFilter("speed-profile")
+                          .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+                                  ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                          | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                          .build();
+
+        // Delay the executor to verify that the commands passed to the executor are not bound to
+        // changing variables.
+        var progressCallbackExecutor = new DelayedExecutor();
+        Consumer<OperationProgress> progressCallback = mock(Consumer.class);
+
+        mDexoptHelper.dexopt(mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor,
+                progressCallbackExecutor, progressCallback);
+
+        progressCallbackExecutor.runAll();
+
+        InOrder inOrder = inOrder(progressCallback);
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(0 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */)));
+        inOrder.verify(progressCallback)
+                .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */)));
+    }
+
+    private AndroidPackage createPackage(boolean multiSplit) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+
+        if (multiSplit) {
+            var split0 = mock(AndroidPackageSplit.class);
+            lenient().when(split0.getName()).thenReturn("split_0");
+
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0));
+        } else {
+            lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+        }
+
+        return pkg;
+    }
+
+    private PackageState createPackageState(
+            String packageName, List<SharedLibrary> deps, boolean multiSplit) {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getAppId()).thenReturn(12345);
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(deps);
+        AndroidPackage pkg = createPackage(multiSplit);
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(true);
+        return pkgState;
+    }
+
+    private SharedLibrary createLibrary(
+            String libraryName, String packageName, List<SharedLibrary> deps) {
+        SharedLibrary library = mock(SharedLibrary.class);
+        lenient().when(library.getName()).thenReturn(libraryName);
+        lenient().when(library.getPackageName()).thenReturn(packageName);
+        lenient().when(library.getDependencies()).thenReturn(deps);
+        return library;
+    }
+
+    private void preparePackagesAndLibraries() {
+        // Dependency graph:
+        //                foo                bar
+        //                 |                  |
+        //            lib1a (lib1)       lib1b (lib1)       lib1c (lib1)
+        //               /   \             /   \                  |
+        //              /     \           /     \                 |
+        //  libbaz (libbaz)    lib2 (lib2)    lib4 (lib4)    lib3 (lib3)
+        //
+        // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1".
+
+        mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ);
+
+        SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of());
+        SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of());
+        SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of());
+        SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of());
+        SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2));
+        SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, lib4));
+        SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
+
+        mPkgStateFoo = createPackageState(PKG_NAME_FOO, List.of(lib1a), true /* multiSplit */);
+        mPkgFoo = mPkgStateFoo.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo);
+
+        mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b), false /* multiSplit */);
+        mPkgBar = mPkgStateBar.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar);
+
+        mPkgStateLib1 = createPackageState(
+                PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4), false /* multiSplit */);
+        mPkgLib1 = mPkgStateLib1.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1);
+
+        mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of(), false /* multiSplit */);
+        mPkgLib2 = mPkgStateLib2.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2);
+
+        // This should not be considered as a transitive dependency of any requested package, even
+        // though it is a dependency of package "lib1".
+        PackageState pkgStateLib3 =
+                createPackageState(PKG_NAME_LIB3, List.of(), false /* multiSplit */);
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3);
+
+        mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of(), false /* multiSplit */);
+        mPkgLib4 = mPkgStateLib4.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4);
+
+        mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of(), false /* multiSplit */);
+        mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage();
+        lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz);
+    }
+
+    private void verifyNoDexopt() {
+        verify(mInjector, never()).getPrimaryDexopter(any(), any(), any(), any());
+        verify(mInjector, never()).getSecondaryDexopter(any(), any(), any(), any());
+    }
+
+    private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) {
+        verify(mInjector, times(expectedPrimaryTimes))
+                .getPrimaryDexopter(any(), any(), any(), any());
+        verify(mInjector, times(expectedSecondaryTimes))
+                .getSecondaryDexopter(any(), any(), any(), any());
+    }
+
+    private List<DexContainerFileDexoptResult> createResults(
+            String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2) {
+        return List.of(DexContainerFileDexoptResult.create(dexPath, true /* isPrimaryAbi */,
+                               "arm64-v8a", "verify", status1, 100 /* dex2oatWallTimeMillis */,
+                               400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */,
+                               0 /* sizeBeforeBytes */, false /* isSkippedDueToStorageLow */),
+                DexContainerFileDexoptResult.create(dexPath, false /* isPrimaryAbi */,
+                        "armeabi-v7a", "verify", status2, 100 /* dex2oatWallTimeMillis */,
+                        400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                        false /* isSkippedDueToStorageLow */));
+    }
+
+    private void checkPackageResult(DexoptResult result, int index, String packageName,
+            @DexoptResult.DexoptResultStatus int status,
+            List<List<DexContainerFileDexoptResult>> dexContainerFileDexoptResults) {
+        PackageDexoptResult packageResult = result.getPackageDexoptResults().get(index);
+        assertThat(packageResult.getPackageName()).isEqualTo(packageName);
+        assertThat(packageResult.getStatus()).isEqualTo(status);
+        assertThat(packageResult.getDexContainerFileDexoptResults())
+                .containsExactlyElementsIn(dexContainerFileDexoptResults.stream()
+                                                   .flatMap(r -> r.stream())
+                                                   .collect(Collectors.toList()));
+    }
+
+    /** An executor that delays execution until `runAll` is called. */
+    private static class DelayedExecutor implements Executor {
+        private List<Runnable> mCommands = new ArrayList<>();
+
+        public void execute(Runnable command) {
+            mCommands.add(command);
+        }
+
+        public void runAll() {
+            for (Runnable command : mCommands) {
+                command.run();
+            }
+            mCommands.clear();
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
new file mode 100644
index 0000000..9a518ed
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DexLoader;
+import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class DumpHelperTest {
+    private static final String PKG_NAME_FOO = "com.example.foo";
+    private static final String PKG_NAME_BAR = "com.example.bar";
+
+    @Mock private DumpHelper.Injector mInjector;
+    @Mock private ArtManagerLocal mArtManagerLocal;
+    @Mock private DexUseManagerLocal mDexUseManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+
+    private DumpHelper mDumpHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManagerLocal);
+
+        LinkedHashMap<String, PackageState> pkgStates = createPackageStates();
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(pkgStates);
+
+        setUpForFoo();
+        setUpForBar();
+
+        mDumpHelper = new DumpHelper(mInjector);
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        String expected = "[com.example.foo]\n"
+                + "  path: /data/app/foo/base.apk\n"
+                + "    arm64: [status=speed-profile] [reason=bg-dexopt]\n"
+                + "    arm: [status=verify] [reason=install]\n"
+                + "  path: /data/app/foo/split_0.apk\n"
+                + "    arm64: [status=verify] [reason=vdex]\n"
+                + "    arm: [status=verify] [reason=vdex]\n"
+                + "    used by other apps: [com.example.bar]\n"
+                + "  known secondary dex files:\n"
+                + "    /data/user_de/0/foo/1.apk\n"
+                + "      arm: [status=run-from-apk] [reason=unknown]\n"
+                + "      class loader context: =VaryingClassLoaderContexts=\n"
+                + "      used by other apps: [com.example.foo (isolated), com.example.baz]\n"
+                + "    /data/user_de/0/foo/2.apk\n"
+                + "      arm64: [status=speed-profile] [reason=bg-dexopt]\n"
+                + "      arm: [status=verify] [reason=vdex]\n"
+                + "      class loader context: PCL[]\n"
+                + "[com.example.bar]\n"
+                + "  path: /data/app/bar/base.apk\n"
+                + "    arm: [status=verify] [reason=install]\n"
+                + "    arm64: [status=verify] [reason=install]\n";
+
+        var stringWriter = new StringWriter();
+        mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
+        assertThat(stringWriter.toString()).isEqualTo(expected);
+    }
+
+    private PackageState createPackageState(String packageName, int appId, boolean hasPackage) {
+        var pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+        lenient().when(pkgState.getAppId()).thenReturn(appId);
+        lenient()
+                .when(pkgState.getAndroidPackage())
+                .thenReturn(hasPackage ? mock(AndroidPackage.class) : null);
+        return pkgState;
+    }
+
+    private LinkedHashMap<String, PackageState> createPackageStates() {
+        // Use LinkedHashMap to ensure the determinism of the output.
+        var pkgStates = new LinkedHashMap<String, PackageState>();
+        pkgStates.put(PKG_NAME_FOO,
+                createPackageState(PKG_NAME_FOO, 10001 /* appId */, true /* hasPackage */));
+        pkgStates.put(PKG_NAME_BAR,
+                createPackageState(PKG_NAME_BAR, 10003 /* appId */, true /* hasPackage */));
+        // This should not be included in the output because it has a negative app id.
+        pkgStates.put("com.android.art",
+                createPackageState("com.android.art", -1 /* appId */, true /* hasPackage */));
+        // This should not be included in the output because it does't have AndroidPackage.
+        pkgStates.put("com.example.null",
+                createPackageState("com.example.null", 10010 /* appId */, false /* hasPackage */));
+        return pkgStates;
+    }
+
+    private void setUpForFoo() {
+        // The order of the dex path and the ABI should be kept in the output.
+        var status = DexoptStatus.create(
+                List.of(DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "speed-profile", "bg-dexopt", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "verify", "install", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "vdex", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "verify", "vdex", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/user_de/0/foo/1.apk",
+                                false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "run-from-apk", "unknown", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                                false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                                "speed-profile", "bg-dexopt", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                                false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                                "verify", "vdex", "location-ignored")));
+
+        lenient()
+                .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_FOO)))
+                .thenReturn(status);
+
+        // The output should not show "used by other apps:".
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_FOO, "/data/app/foo/base.apk"))
+                .thenReturn(Set.of());
+
+        // The output should not show "foo" in "used by other apps:".
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_FOO, "/data/app/foo/split_0.apk"))
+                .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */),
+                        DexLoader.create(PKG_NAME_BAR, false /* isolatedProcess */)));
+
+        var info1 = mock(SecondaryDexInfo.class);
+        lenient().when(info1.dexPath()).thenReturn("/data/user_de/0/foo/1.apk");
+        lenient()
+                .when(info1.displayClassLoaderContext())
+                .thenReturn(SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS);
+        var loaders = new LinkedHashSet<DexLoader>();
+        // The output should show "foo" with "(isolated)" in "used by other apps:".
+        loaders.add(DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */));
+        loaders.add(DexLoader.create("com.example.baz", false /* isolatedProcess */));
+        lenient().when(info1.loaders()).thenReturn(loaders);
+
+        var info2 = mock(SecondaryDexInfo.class);
+        lenient().when(info2.dexPath()).thenReturn("/data/user_de/0/foo/2.apk");
+        lenient().when(info2.displayClassLoaderContext()).thenReturn("PCL[]");
+        // The output should not show "used by other apps:".
+        lenient()
+                .when(info2.loaders())
+                .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)));
+
+        lenient()
+                .doReturn(List.of(info1, info2))
+                .when(mDexUseManagerLocal)
+                .getSecondaryDexInfo(PKG_NAME_FOO);
+    }
+
+    private void setUpForBar() {
+        // The order of the ABI should be kept in the output, despite that it's different from the
+        // order for package "foo".
+        // The output should not show "known secondary dex files:".
+        var status = DexoptStatus.create(
+                List.of(DexContainerFileDexoptStatus.create("/data/app/bar/base.apk",
+                                true /* isPrimaryDex */, true /* isPrimaryAbi */, "armeabi-v7a",
+                                "verify", "install", "location-ignored"),
+                        DexContainerFileDexoptStatus.create("/data/app/bar/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "install", "location-ignored")));
+
+        lenient()
+                .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_BAR)))
+                .thenReturn(status);
+
+        lenient()
+                .when(mDexUseManagerLocal.getPrimaryDexLoaders(
+                        PKG_NAME_BAR, "/data/app/bar/base.apk"))
+                .thenReturn(Set.of());
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
new file mode 100644
index 0000000..3a83c6b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class PrimaryDexUtilsTest {
+    @Before
+    public void setUp() {}
+
+    @Test
+    public void testGetDexInfo() {
+        List<PrimaryDexInfo> infos =
+                PrimaryDexUtils.getDexInfo(createPackage(false /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+    }
+
+    @Test
+    public void testGetDetailedDexInfo() {
+        List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
+                createPackageState(), createPackage(false /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+
+        String sharedLibrariesContext = "{"
+                + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+                + "PCL[library_3.jar]#"
+                + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+                + "}";
+
+        assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext);
+        assertThat(infos.get(1).classLoaderContext())
+                .isEqualTo("PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(2).classLoaderContext()).isEqualTo(null);
+        assertThat(infos.get(3).classLoaderContext())
+                .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(4).classLoaderContext())
+                .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]"
+                        + sharedLibrariesContext);
+    }
+
+    @Test
+    public void testGetDetailedDexInfoIsolated() {
+        List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
+                createPackageState(), createPackage(true /* isIsolatedSplitLoading */));
+        checkBasicInfo(infos);
+
+        String sharedLibrariesContext = "{"
+                + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+                + "PCL[library_3.jar]#"
+                + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+                + "}";
+
+        assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext);
+        assertThat(infos.get(1).classLoaderContext())
+                .isEqualTo("PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(2).classLoaderContext()).isEqualTo(null);
+        assertThat(infos.get(3).classLoaderContext())
+                .isEqualTo("DLC[];PCL[base.apk]" + sharedLibrariesContext);
+        assertThat(infos.get(4).classLoaderContext()).isEqualTo("PCL[]");
+        assertThat(infos.get(5).classLoaderContext()).isEqualTo("PCL[];PCL[split_3.apk]");
+    }
+
+    private <T extends PrimaryDexInfo> void checkBasicInfo(List<T> infos) {
+        assertThat(infos.get(0).dexPath()).isEqualTo("/data/app/foo/base.apk");
+        assertThat(infos.get(0).hasCode()).isTrue();
+        assertThat(infos.get(0).splitName()).isNull();
+
+        assertThat(infos.get(1).dexPath()).isEqualTo("/data/app/foo/split_0.apk");
+        assertThat(infos.get(1).hasCode()).isTrue();
+        assertThat(infos.get(1).splitName()).isEqualTo("split_0");
+
+        assertThat(infos.get(2).dexPath()).isEqualTo("/data/app/foo/split_1.apk");
+        assertThat(infos.get(2).hasCode()).isFalse();
+        assertThat(infos.get(2).splitName()).isEqualTo("split_1");
+
+        assertThat(infos.get(3).dexPath()).isEqualTo("/data/app/foo/split_2.apk");
+        assertThat(infos.get(3).hasCode()).isTrue();
+        assertThat(infos.get(3).splitName()).isEqualTo("split_2");
+
+        assertThat(infos.get(4).dexPath()).isEqualTo("/data/app/foo/split_3.apk");
+        assertThat(infos.get(4).hasCode()).isTrue();
+        assertThat(infos.get(4).splitName()).isEqualTo("split_3");
+
+        assertThat(infos.get(5).dexPath()).isEqualTo("/data/app/foo/split_4.apk");
+        assertThat(infos.get(5).hasCode()).isTrue();
+        assertThat(infos.get(5).splitName()).isEqualTo("split_4");
+    }
+
+    private AndroidPackage createPackage(boolean isIsolatedSplitLoading) {
+        AndroidPackage pkg = mock(AndroidPackage.class);
+
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        var split1 = mock(AndroidPackageSplit.class);
+        lenient().when(split1.getName()).thenReturn("split_1");
+        lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+        lenient().when(split1.isHasCode()).thenReturn(false);
+
+        var split2 = mock(AndroidPackageSplit.class);
+        lenient().when(split2.getName()).thenReturn("split_2");
+        lenient().when(split2.getPath()).thenReturn("/data/app/foo/split_2.apk");
+        lenient().when(split2.isHasCode()).thenReturn(true);
+
+        var split3 = mock(AndroidPackageSplit.class);
+        lenient().when(split3.getName()).thenReturn("split_3");
+        lenient().when(split3.getPath()).thenReturn("/data/app/foo/split_3.apk");
+        lenient().when(split3.isHasCode()).thenReturn(true);
+
+        var split4 = mock(AndroidPackageSplit.class);
+        lenient().when(split4.getName()).thenReturn("split_4");
+        lenient().when(split4.getPath()).thenReturn("/data/app/foo/split_4.apk");
+        lenient().when(split4.isHasCode()).thenReturn(true);
+
+        var splits = List.of(baseSplit, split0, split1, split2, split3, split4);
+        lenient().when(pkg.getSplits()).thenReturn(splits);
+
+        if (isIsolatedSplitLoading) {
+            // split_0: PCL(PathClassLoader), depends on split_2.
+            // split_1: no code.
+            // split_2: DLC(DelegateLastClassLoader), depends on base.
+            // split_3: PCL(DexClassLoader), no dependency.
+            // split_4: PCL(null), depends on split_3.
+            lenient().when(split0.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+            lenient().when(split1.getClassLoaderName()).thenReturn(null);
+            lenient()
+                    .when(split2.getClassLoaderName())
+                    .thenReturn(DelegateLastClassLoader.class.getName());
+            lenient().when(split3.getClassLoaderName()).thenReturn(DexClassLoader.class.getName());
+            lenient().when(split4.getClassLoaderName()).thenReturn(null);
+
+            lenient().when(split0.getDependencies()).thenReturn(List.of(split2));
+            lenient().when(split2.getDependencies()).thenReturn(List.of(baseSplit));
+            lenient().when(split4.getDependencies()).thenReturn(List.of(split3));
+            lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(true);
+        } else {
+            lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(false);
+        }
+
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        PackageState pkgState = mock(PackageState.class);
+
+        lenient().when(pkgState.getPackageName()).thenReturn("com.example.foo");
+
+        // Base depends on library 2, 3, 4.
+        // Library 2, 4 depends on library 1.
+        List<SharedLibrary> usesLibraryInfos = new ArrayList<>();
+
+        SharedLibrary library1 = mock(SharedLibrary.class);
+        lenient()
+                .when(library1.getAllCodePaths())
+                .thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar"));
+        lenient().when(library1.getDependencies()).thenReturn(null);
+
+        SharedLibrary library2 = mock(SharedLibrary.class);
+        lenient().when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
+        lenient().when(library2.getDependencies()).thenReturn(List.of(library1));
+        usesLibraryInfos.add(library2);
+
+        SharedLibrary library3 = mock(SharedLibrary.class);
+        lenient().when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
+        lenient().when(library3.getDependencies()).thenReturn(null);
+        usesLibraryInfos.add(library3);
+
+        SharedLibrary library4 = mock(SharedLibrary.class);
+        lenient().when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar"));
+        lenient().when(library4.getDependencies()).thenReturn(List.of(library1));
+        usesLibraryInfos.add(library4);
+
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(usesLibraryInfos);
+
+        return pkgState;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
new file mode 100644
index 0000000..a5245c1
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.AidlUtils.buildFsPermission;
+import static com.android.server.art.AidlUtils.buildOutputArtifacts;
+import static com.android.server.art.AidlUtils.buildPermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.OnSuccessRule;
+import com.android.server.art.testing.TestingUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
+    @Rule
+    public OnSuccessRule onSuccessRule = new OnSuccessRule(() -> {
+        // Don't do this on failure because it will make the failure hard to understand.
+        verifyNoMoreInteractions(mArtd);
+    });
+
+    private DexoptParams mDexoptParams;
+
+    private PrimaryDexopter mPrimaryDexopter;
+
+    @Parameter(0) public Params mParams;
+
+    @Parameters(name = "{0}")
+    public static Iterable<Params> data() {
+        List<Params> list = new ArrayList<>();
+        Params params;
+
+        // Baseline.
+        params = new Params();
+        list.add(params);
+
+        params = new Params();
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "speed";
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mExpectedIsInDalvikCache = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mIsUpdatedSystemApp = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystem = true;
+        params.mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+        params.mExpectedIsInDalvikCache = true;
+        params.mExpectedIsHiddenApiPolicyEnabled = false;
+        list.add(params);
+
+        params = new Params();
+        params.mIsDebuggable = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        params.mExpectedIsDebuggable = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsVmSafeMode = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        list.add(params);
+
+        params = new Params();
+        params.mIsUseEmbeddedDex = true;
+        params.mRequestedCompilerFilter = "speed";
+        params.mExpectedCompilerFilter = "verify";
+        list.add(params);
+
+        params = new Params();
+        params.mAlwaysDebuggable = true;
+        params.mExpectedIsDebuggable = true;
+        list.add(params);
+
+        params = new Params();
+        params.mIsSystemUi = true;
+        params.mExpectedCompilerFilter = "speed";
+        list.add(params);
+
+        params = new Params();
+        params.mForce = true;
+        params.mShouldDowngrade = false;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        list.add(params);
+
+        params = new Params();
+        params.mForce = true;
+        params.mShouldDowngrade = true;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        list.add(params);
+
+        params = new Params();
+        params.mShouldDowngrade = true;
+        params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        list.add(params);
+
+        params = new Params();
+        // This should not change the result.
+        params.mSkipIfStorageLow = true;
+        list.add(params);
+
+        return list;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(mParams.mIsSystemUi);
+
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(mParams.mAlwaysDebuggable);
+
+        lenient().when(mPkg.isVmSafeMode()).thenReturn(mParams.mIsVmSafeMode);
+        lenient().when(mPkg.isDebuggable()).thenReturn(mParams.mIsDebuggable);
+        lenient().when(mPkg.getTargetSdkVersion()).thenReturn(123);
+        lenient()
+                .when(mPkgState.getHiddenApiEnforcementPolicy())
+                .thenReturn(mParams.mHiddenApiEnforcementPolicy);
+        lenient().when(mPkg.isUseEmbeddedDex()).thenReturn(mParams.mIsUseEmbeddedDex);
+        lenient().when(mPkgState.isSystem()).thenReturn(mParams.mIsSystem);
+        lenient().when(mPkgState.isUpdatedSystemApp()).thenReturn(mParams.mIsUpdatedSystemApp);
+
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter(mParams.mRequestedCompilerFilter)
+                        .setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE)
+                        .setFlags(mParams.mForce ? ArtFlags.FLAG_FORCE : 0, ArtFlags.FLAG_FORCE)
+                        .setFlags(mParams.mShouldDowngrade ? ArtFlags.FLAG_SHOULD_DOWNGRADE : 0,
+                                ArtFlags.FLAG_SHOULD_DOWNGRADE)
+                        .setFlags(mParams.mSkipIfStorageLow ? ArtFlags.FLAG_SKIP_IF_STORAGE_LOW : 0,
+                                ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
+                        .build();
+
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(
+                buildFsPermission(Process.SYSTEM_UID /* uid */, Process.SYSTEM_UID /* gid */,
+                        false /* isOtherReadable */, true /* isOtherExecutable */),
+                buildFsPermission(Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */,
+                        true /* isOtherReadable */),
+                null /* seContext */);
+        DexoptOptions dexoptOptions = new DexoptOptions();
+        dexoptOptions.compilationReason = "install";
+        dexoptOptions.targetSdkVersion = 123;
+        dexoptOptions.debuggable = mParams.mExpectedIsDebuggable;
+        dexoptOptions.generateAppImage = false;
+        dexoptOptions.hiddenApiPolicyEnabled = mParams.mExpectedIsHiddenApiPolicyEnabled;
+
+        when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
+        when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
+        // The first one is normal.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doReturn(createArtdDexoptResult(false /* cancelled */, 100 /* wallTimeMs */,
+                         400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
+                        eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+        // The second one fails on `dexopt`.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/base.apk", "arm", "PCL[]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doThrow(ServiceSpecificException.class)
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/base.apk"), eq("arm"), eq("PCL[]"),
+                        eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+        // The third one doesn't need dexopt.
+        doReturn(dexoptIsNotNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+
+        // The fourth one is normal.
+        doReturn(dexoptIsNeeded())
+                .when(mArtd)
+                .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]",
+                        mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+        doReturn(createArtdDexoptResult(false /* cancelled */, 200 /* wallTimeMs */,
+                         200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
+                        eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+        assertThat(mPrimaryDexopter.dexopt())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptResult.create("/data/app/foo/base.apk",
+                                true /* isPrimaryAbi */, "arm64-v8a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
+                                100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+                                30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/base.apk",
+                                false /* isPrimaryAbi */, "armeabi-v7a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
+                                true /* isPrimaryAbi */, "arm64-v8a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk",
+                                false /* isPrimaryAbi */, "armeabi-v7a",
+                                mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
+                                200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
+                                10000 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */));
+    }
+
+    private static class Params {
+        // Package information.
+        public boolean mIsSystem = false;
+        public boolean mIsUpdatedSystemApp = false;
+        public int mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED;
+        public boolean mIsVmSafeMode = false;
+        public boolean mIsDebuggable = false;
+        public boolean mIsSystemUi = false;
+        public boolean mIsUseEmbeddedDex = false;
+
+        // Options.
+        public String mRequestedCompilerFilter = "verify";
+        public boolean mForce = false;
+        public boolean mShouldDowngrade = false;
+        public boolean mSkipIfStorageLow = false;
+
+        // System properties.
+        public boolean mAlwaysDebuggable = false;
+
+        // Expectations.
+        public String mExpectedCompilerFilter = "verify";
+        public int mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        public boolean mExpectedIsInDalvikCache = false;
+        public boolean mExpectedIsDebuggable = false;
+        public boolean mExpectedIsHiddenApiPolicyEnabled = true;
+
+        public String toString() {
+            return String.format("isSystem=%b,isUpdatedSystemApp=%b,mHiddenApiEnforcementPolicy=%d"
+                            + ",isVmSafeMode=%b,isDebuggable=%b,isSystemUi=%b,isUseEmbeddedDex=%b,"
+                            + "requestedCompilerFilter=%s,force=%b,shouldDowngrade=%b,"
+                            + "mSkipIfStorageLow=%b,alwaysDebuggable=%b => targetCompilerFilter=%s,"
+                            + "expectedDexoptTrigger=%d,expectedIsInDalvikCache=%b,"
+                            + "expectedIsDebuggable=%b,expectedIsHiddenApiPolicyEnabled=%b",
+                    mIsSystem, mIsUpdatedSystemApp, mHiddenApiEnforcementPolicy, mIsVmSafeMode,
+                    mIsDebuggable, mIsSystemUi, mIsUseEmbeddedDex, mRequestedCompilerFilter, mForce,
+                    mShouldDowngrade, mSkipIfStorageLow, mAlwaysDebuggable, mExpectedCompilerFilter,
+                    mExpectedDexoptTrigger, mExpectedIsInDalvikCache, mExpectedIsDebuggable,
+                    mExpectedIsHiddenApiPolicyEnabled);
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
new file mode 100644
index 0000000..c474f41
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.TestingUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
+    private final String mDexPath = "/data/app/foo/base.apk";
+    private final ProfilePath mRefProfile =
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
+    private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
+    private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
+    private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
+    private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+            PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
+    private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+            PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */);
+
+    private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
+    private final ProfilePath mSplit0RefProfile =
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split");
+
+    private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+    private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+
+    private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+    private final ArtdDexoptResult mArtdDexoptResult =
+            createArtdDexoptResult(false /* cancelled */);
+
+    private DexoptParams mDexoptParams =
+            new DexoptParams.Builder("install").setCompilerFilter("speed-profile").build();
+
+    private PrimaryDexopter mPrimaryDexopter;
+
+    private List<ProfilePath> mUsedProfiles;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // By default, none of the profiles are usable.
+        lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
+        lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
+
+        // By default, no DM file exists.
+        lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
+        // Dexopt is by default needed and successful.
+        lenient()
+                .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+                .thenReturn(dexoptIsNeeded());
+        lenient()
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
+                .thenReturn(mArtdDexoptResult);
+
+        lenient()
+                .when(mArtd.createCancellationSignal())
+                .thenReturn(mock(IArtdCancellationSignal.class));
+
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mUsedProfiles = new ArrayList<>();
+    }
+
+    @Test
+    public void testDexoptInputVdex() throws Exception {
+        // null.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(),
+                        anyInt(), any(), any());
+
+        // ArtifactsPath, isInDalvikCache=true.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mDexPath, "arm", true /* isInDalvikCache */))),
+                        any(), anyInt(), any(), any());
+
+        // ArtifactsPath, isInDalvikCache=false.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
+                        any(), anyInt(), any(), any());
+
+        // DexMetadataPath.
+        doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
+                .when(mArtd)
+                .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
+        doReturn(mArtdDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
+                        anyInt(), any(), any());
+
+        mPrimaryDexopter.dexopt();
+    }
+
+    @Test
+    public void testDexoptDm() throws Exception {
+        lenient()
+                .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
+                        anyInt(),
+                        argThat(dexoptOptions
+                                -> dexoptOptions.compilationReason.equals("install-dm")),
+                        any());
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(),
+                        anyInt(),
+                        argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")),
+                        any());
+    }
+
+    @Test
+    public void testDexoptUsesRefProfile() throws Exception {
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Other profiles are also usable, but they shouldn't be used.
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        // There is no profile for split 0, so it should fall back to "verify".
+        verify(mArtd).getDexoptNeeded(
+                eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "verify");
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "verify");
+
+        verifyProfileNotUsed(mPrebuiltProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptUsesPublicRefProfile() throws Exception {
+        // The ref profile is usable and public.
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        // Other profiles are also usable, but they shouldn't be used.
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verifyProfileNotUsed(mPrebuiltProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptUsesPrebuiltProfile() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        InOrder inOrder = inOrder(mArtd);
+
+        inOrder.verify(mArtd).copyAndRewriteProfile(
+                deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mDmProfile);
+    }
+
+    @Test
+    public void testDexoptMergesProfiles() throws Exception {
+        when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
+        when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
+
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
+
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        InOrder inOrder = inOrder(mArtd);
+
+        inOrder.verify(mArtd).mergeProfiles(
+                deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur(
+                                       0 /* userId */, PKG_NAME, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                2 /* userId */, PKG_NAME, "primary"))),
+                deepEq(mRefProfile), deepEq(mPrivateOutputProfile), deepEq(List.of(mDexPath)),
+                deepEq(mMergeProfileOptions));
+
+        // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs.
+        inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"),
+                eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"),
+                eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
+                false /* isOtherReadable */, true /* generateAppImage */);
+
+        inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
+
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary")));
+    }
+
+    @Test
+    public void testDexoptMergesProfilesMergeFailed() throws Exception {
+        when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
+        when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
+
+        when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        makeProfileUsable(mRefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        // It should still use "speed-profile", but with the existing reference profile only.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd, never()).deleteProfile(any());
+        verify(mArtd, never()).commitTmpProfile(any());
+    }
+
+    @Test
+    public void testDexoptUsesDmProfile() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).copyAndRewriteProfile(
+                deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mPrebuiltProfile);
+    }
+
+    @Test
+    public void testDexoptDeletesProfileOnFailure() throws Exception {
+        makeProfileNotUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                     any(), any()))
+                .thenThrow(ServiceSpecificException.class);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).deleteProfile(
+                deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
+        verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
+    }
+
+    @Test
+    public void testDexoptNeedsToBeShared() throws Exception {
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
+                .thenReturn(true);
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
+                .thenReturn(true);
+
+        // The ref profile is usable but shouldn't be used.
+        makeProfileUsable(mRefProfile);
+
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+
+        // The existing artifacts are private.
+        when(mArtd.getArtifactsVisibility(
+                     argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).copyAndRewriteProfile(
+                deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+        // It should re-compile anyway.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
+                true /* isOtherReadable */, true /* generateAppImage */);
+
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
+        checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");
+
+        verifyProfileNotUsed(mRefProfile);
+        verifyProfileNotUsed(mPrebuiltProfile);
+    }
+
+    @Test
+    public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception {
+        // Same setup as above, but the existing artifacts are public.
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
+                .thenReturn(true);
+        when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
+                .thenReturn(true);
+
+        makeProfileUsable(mRefProfile);
+        makeProfileNotUsable(mPrebuiltProfile);
+        makeProfileUsable(mDmProfile);
+        when(mArtd.getArtifactsVisibility(
+                     argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        // It should use the default dexopt trigger.
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+        verify(mArtd).getDexoptNeeded(
+                eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+    }
+
+    @Test
+    public void testDexoptUsesProfileForSplit() throws Exception {
+        makeProfileUsable(mSplit0RefProfile);
+        when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
+                eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm64", mSplit0RefProfile,
+                false /* isOtherReadable */, false /* generateAppImage */);
+
+        verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"),
+                eq(mDefaultDexoptTrigger));
+        checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm", mSplit0RefProfile,
+                false /* isOtherReadable */, false /* generateAppImage */);
+    }
+
+    @Test
+    public void testDexoptCancelledBeforeDexopt() throws Exception {
+        mCancellationSignal.cancel();
+
+        var artdCancellationSignal = mock(IArtdCancellationSignal.class);
+        when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
+
+        doAnswer(invocation -> {
+            verify(artdCancellationSignal).cancel();
+            return createArtdDexoptResult(true /* cancelled */);
+        })
+                .when(mArtd)
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        same(artdCancellationSignal));
+
+        // The result should only contain one element: the result of the first file with
+        // DEXOPT_CANCELLED.
+        assertThat(mPrimaryDexopter.dexopt()
+                           .stream()
+                           .map(DexContainerFileDexoptResult::getStatus)
+                           .collect(Collectors.toList()))
+                .containsExactly(DexoptResult.DEXOPT_CANCELLED);
+
+        // It shouldn't continue after being cancelled on the first file.
+        verify(mArtd, times(1)).createCancellationSignal();
+        verify(mArtd, times(1))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    @Test
+    public void testDexoptCancelledDuringDexopt() throws Exception {
+        Semaphore dexoptStarted = new Semaphore(0);
+        Semaphore dexoptCancelled = new Semaphore(0);
+        final long TIMEOUT_SEC = 1;
+
+        var artdCancellationSignal = mock(IArtdCancellationSignal.class);
+        when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
+
+        doAnswer(invocation -> {
+            dexoptStarted.release();
+            assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+            return createArtdDexoptResult(true /* cancelled */);
+        })
+                .when(mArtd)
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        same(artdCancellationSignal));
+        doAnswer(invocation -> {
+            dexoptCancelled.release();
+            return null;
+        })
+                .when(artdCancellationSignal)
+                .cancel();
+
+        Future<List<DexContainerFileDexoptResult>> results =
+                ForkJoinPool.commonPool().submit(() -> { return mPrimaryDexopter.dexopt(); });
+
+        assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+        mCancellationSignal.cancel();
+
+        assertThat(results.get()
+                           .stream()
+                           .map(DexContainerFileDexoptResult::getStatus)
+                           .collect(Collectors.toList()))
+                .containsExactly(DexoptResult.DEXOPT_CANCELLED);
+
+        // It shouldn't continue after being cancelled on the first file.
+        verify(mArtd, times(1)).createCancellationSignal();
+        verify(mArtd, times(1))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    @Test
+    public void testDexoptBaseApk() throws Exception {
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                        .setSplitName(null)
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any());
+        verify(mArtd, never())
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
+                        anyInt(), any(), any());
+    }
+
+    @Test
+    public void testDexoptSplitApk() throws Exception {
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                        .setSplitName("split_0")
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        mPrimaryDexopter.dexopt();
+
+        verify(mArtd, never())
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any());
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
+                        anyInt(), any(), any());
+    }
+
+    @Test
+    public void testDexoptStorageLow() throws Exception {
+        when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l, 0l, 0l, 1l);
+
+        mDexoptParams =
+                new DexoptParams.Builder("install")
+                        .setCompilerFilter("speed-profile")
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
+                        .build();
+        mPrimaryDexopter =
+                new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+        List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+        assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(0).isSkippedDueToStorageLow()).isFalse();
+        assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(results.get(1).isSkippedDueToStorageLow()).isTrue();
+        assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+        assertThat(results.get(2).isSkippedDueToStorageLow()).isTrue();
+        assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+        assertThat(results.get(3).isSkippedDueToStorageLow()).isFalse();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile,
+            boolean isOtherReadable, boolean generateAppImage) throws Exception {
+        artd.dexopt(argThat(artifacts
+                            -> artifacts.permissionSettings.fileFsPermission.isOtherReadable
+                                    == isOtherReadable),
+                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
+                anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == generateAppImage),
+                any());
+    }
+
+    private void checkDexoptWithNoProfile(
+            IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception {
+        artd.dexopt(
+                argThat(artifacts
+                        -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
+                eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private void verifyProfileNotUsed(ProfilePath profile) throws Exception {
+        assertThat(mUsedProfiles)
+                .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality())
+                .doesNotContain(profile);
+    }
+
+    private void makeProfileUsable(ProfilePath profile) throws Exception {
+        lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> {
+            mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+            return true;
+        });
+        lenient()
+                .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+                .thenAnswer(invocation -> {
+                    mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+                    return true;
+                });
+    }
+
+    private void makeProfileNotUsable(ProfilePath profile) throws Exception {
+        lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenReturn(false);
+        lenient()
+                .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+                .thenReturn(false);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
new file mode 100644
index 0000000..adb00ff
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.same;
+
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+import com.android.modules.utils.pm.PackageStateModulesUtils;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+
+import dalvik.system.PathClassLoader;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrimaryDexopterTestBase {
+    protected static final String PKG_NAME = "com.example.foo";
+    protected static final int UID = 12345;
+    protected static final int SHARED_GID = UserHandle.getSharedAppGid(UID);
+
+    @Rule
+    public StaticMockitoRule mockitoRule = new StaticMockitoRule(
+            SystemProperties.class, Constants.class, PackageStateModulesUtils.class);
+
+    @Mock protected PrimaryDexopter.Injector mInjector;
+    @Mock protected IArtd mArtd;
+    @Mock protected UserManager mUserManager;
+    @Mock protected DexUseManagerLocal mDexUseManager;
+    @Mock protected StorageManager mStorageManager;
+    protected PackageState mPkgState;
+    protected AndroidPackage mPkg;
+    protected PackageUserState mPkgUserStateNotInstalled;
+    protected PackageUserState mPkgUserStateInstalled;
+    protected CancellationSignal mCancellationSignal;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+
+        lenient()
+                .when(SystemProperties.get("dalvik.vm.systemuicompilerfilter"))
+                .thenReturn("speed");
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(false);
+        lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+        lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient()
+                .when(mUserManager.getUserHandles(anyBoolean()))
+                .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1), UserHandle.of(2)));
+
+        lenient().when(mDexUseManager.isPrimaryDexUsedByOtherApps(any(), any())).thenReturn(false);
+
+        lenient().when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l);
+
+        mPkgUserStateNotInstalled = createPackageUserState(false /* installed */);
+        mPkgUserStateInstalled = createPackageUserState(true /* installed */);
+        mPkgState = createPackageState();
+        mPkg = mPkgState.getAndroidPackage();
+        mCancellationSignal = new CancellationSignal();
+    }
+
+    private AndroidPackage createPackage() {
+        // This package has the base APK and one split APK that has code.
+        AndroidPackage pkg = mock(AndroidPackage.class);
+        var baseSplit = mock(AndroidPackageSplit.class);
+        lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+        lenient().when(baseSplit.isHasCode()).thenReturn(true);
+        lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+        var split0 = mock(AndroidPackageSplit.class);
+        lenient().when(split0.getName()).thenReturn("split_0");
+        lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+        lenient().when(split0.isHasCode()).thenReturn(true);
+
+        var split1 = mock(AndroidPackageSplit.class);
+        lenient().when(split1.getName()).thenReturn("split_1");
+        lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+        lenient().when(split1.isHasCode()).thenReturn(false);
+
+        var splits = List.of(baseSplit, split0, split1);
+        lenient().when(pkg.getSplits()).thenReturn(splits);
+
+        lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+        lenient().when(pkg.isDebuggable()).thenReturn(false);
+        lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+        lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+        lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        PackageState pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.isSystem()).thenReturn(false);
+        lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+        lenient().when(pkgState.getAppId()).thenReturn(UID);
+        lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(new ArrayList<>());
+        lenient().when(pkgState.getStateForUser(any())).thenReturn(mPkgUserStateNotInstalled);
+        AndroidPackage pkg = createPackage();
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        lenient()
+                .when(PackageStateModulesUtils.isLoadableInOtherProcesses(
+                        same(pkgState), anyBoolean()))
+                .thenReturn(false);
+        return pkgState;
+    }
+
+    private PackageUserState createPackageUserState(boolean isInstalled) {
+        PackageUserState pkgUserState = mock(PackageUserState.class);
+        lenient().when(pkgUserState.isInstalled()).thenReturn(isInstalled);
+        return pkgUserState;
+    }
+
+    protected GetDexoptNeededResult dexoptIsNotNeeded() {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = false;
+        return result;
+    }
+
+    protected GetDexoptNeededResult dexoptIsNeeded() {
+        return dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR);
+    }
+
+    protected GetDexoptNeededResult dexoptIsNeeded(@ArtifactsLocation byte location) {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = true;
+        result.artifactsLocation = location;
+        if (location != ArtifactsLocation.NONE_OR_ERROR) {
+            result.isVdexUsable = true;
+        }
+        return result;
+    }
+
+    protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled, long wallTimeMs,
+            long cpuTimeMs, long sizeBytes, long sizeBeforeBytes) {
+        var result = new ArtdDexoptResult();
+        result.cancelled = cancelled;
+        result.wallTimeMs = wallTimeMs;
+        result.cpuTimeMs = cpuTimeMs;
+        result.sizeBytes = sizeBytes;
+        result.sizeBeforeBytes = sizeBeforeBytes;
+        return result;
+    }
+
+    protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled) {
+        return createArtdDexoptResult(cancelled, 0 /* wallTimeMs */, 0 /* cpuTimeMs */,
+                0 /* sizeBytes */, 0 /* sizeBeforeBytes */);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
new file mode 100644
index 0000000..55fd0b4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.testing.StaticMockitoRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ReasonMappingTest {
+    @Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(SystemProperties.class);
+
+    @Test
+    public void testGetCompilerFilterForReason() {
+        when(SystemProperties.get("pm.dexopt.foo")).thenReturn("speed");
+        assertThat(ReasonMapping.getCompilerFilterForReason("foo")).isEqualTo("speed");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetCompilerFilterForReasonInvalidFilter() throws Exception {
+        when(SystemProperties.get("pm.dexopt.foo")).thenReturn("invalid-filter");
+        ReasonMapping.getCompilerFilterForReason("foo");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetCompilerFilterForReasonInvalidReason() throws Exception {
+        ReasonMapping.getCompilerFilterForReason("foo");
+    }
+
+    @Test
+    public void testGetCompilerFilterForShared() {
+        when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+        assertThat(ReasonMapping.getCompilerFilterForShared()).isEqualTo("speed");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetCompilerFilterForSharedProfileGuidedFilter() throws Exception {
+        when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed-profile");
+        ReasonMapping.getCompilerFilterForShared();
+    }
+
+    @Test
+    public void testGetPriorityClassForReason() throws Exception {
+        assertThat(ReasonMapping.getPriorityClassForReason("install"))
+                .isEqualTo(ArtFlags.PRIORITY_INTERACTIVE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPriorityClassForReasonInvalidReason() throws Exception {
+        ReasonMapping.getPriorityClassForReason("foo");
+    }
+
+    @Test
+    public void testGetConcurrencyForReason() {
+        when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+                .thenReturn(3);
+        assertThat(ReasonMapping.getConcurrencyForReason("bg-dexopt")).isEqualTo(3);
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
new file mode 100644
index 0000000..cd1be03
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.testing.TestingUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateUnserialized;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecondaryDexopterTest {
+    private static final String PKG_NAME = "com.example.foo";
+    private static final int APP_ID = 12345;
+    private static final UserHandle USER_HANDLE = UserHandle.of(2);
+    private static final int UID = USER_HANDLE.getUid(APP_ID);
+    private static final String APP_DATA_DIR = "/data/user/2/" + PKG_NAME;
+    private static final String DEX_1 = APP_DATA_DIR + "/1.apk";
+    private static final String DEX_2 = APP_DATA_DIR + "/2.apk";
+    private static final String DEX_3 = APP_DATA_DIR + "/3.apk";
+
+    private final DexoptParams mDexoptParams =
+            new DexoptParams.Builder("bg-dexopt")
+                    .setCompilerFilter("speed-profile")
+                    .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX)
+                    .build();
+
+    private final ProfilePath mDex1RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_1);
+    private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1);
+    private final ProfilePath mDex2RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_2);
+    private final ProfilePath mDex3RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_3);
+    private final OutputProfile mDex1PrivateOutputProfile =
+            AidlUtils.buildOutputProfileForSecondary(DEX_1, UID, UID, false /* isOtherReadable */);
+
+    private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+
+    private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Mock private SecondaryDexopter.Injector mInjector;
+    @Mock private IArtd mArtd;
+    @Mock private DexUseManagerLocal mDexUseManager;
+    private PackageState mPkgState;
+    private AndroidPackage mPkg;
+    private CancellationSignal mCancellationSignal;
+
+    private SecondaryDexopter mSecondaryDexopter;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient()
+                .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+                .thenReturn(false);
+        lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+        lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+
+        List<DetailedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
+        lenient()
+                .when(mDexUseManager.getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME)))
+                .thenReturn(secondaryDexInfo);
+
+        mPkgState = createPackageState();
+        mPkg = mPkgState.getAndroidPackage();
+        mCancellationSignal = new CancellationSignal();
+
+        prepareProfiles();
+
+        // Dexopt is always needed and successful.
+        lenient()
+                .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+                .thenReturn(dexoptIsNeeded());
+        lenient()
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
+                .thenReturn(createArtdDexoptResult());
+
+        lenient()
+                .when(mArtd.createCancellationSignal())
+                .thenReturn(mock(IArtdCancellationSignal.class));
+
+        mSecondaryDexopter = new SecondaryDexopter(
+                mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+    }
+
+    @Test
+    public void testDexopt() throws Exception {
+        assertThat(mSecondaryDexopter.dexopt())
+                .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
+                .containsExactly(
+                        DexContainerFileDexoptResult.create(DEX_1, true /* isPrimaryAbi */,
+                                "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_2, true /* isPrimaryAbi */,
+                                "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_2, false /* isPrimaryAbi */,
+                                "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */),
+                        DexContainerFileDexoptResult.create(DEX_3, true /* isPrimaryAbi */,
+                                "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */,
+                                false /* isSkippedDueToStorageLow */));
+
+        // It should use profile for dex 1.
+
+        verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile),
+                deepEq(mDex1PrivateOutputProfile), deepEq(List.of(DEX_1)),
+                deepEq(mMergeProfileOptions));
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger));
+        checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64",
+                ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1");
+
+        verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath));
+
+        verify(mArtd).deleteProfile(deepEq(mDex1CurProfile));
+
+        // It should use "speed" for dex 2 for both ISAs and make the artifacts public.
+
+        verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any());
+        verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any(), any());
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(
+                verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(
+                verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+        // It should use "verify" for dex 3 and make the artifacts private.
+
+        verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any());
+        verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any(), any());
+
+        verify(mArtd).getDexoptNeeded(
+                eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger));
+        checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify",
+                null /* classLoaderContext */, false /* isPublic */);
+    }
+
+    private AndroidPackage createPackage() {
+        var pkg = mock(AndroidPackage.class);
+        lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+        lenient().when(pkg.isDebuggable()).thenReturn(false);
+        lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+        lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+        lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+        return pkg;
+    }
+
+    private PackageState createPackageState() {
+        var pkgState = mock(PackageState.class);
+        lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+        lenient().when(pkgState.getAppId()).thenReturn(APP_ID);
+        lenient().when(pkgState.getSeInfo()).thenReturn("se-info");
+        AndroidPackage pkg = createPackage();
+        lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+        return pkgState;
+    }
+
+    private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+        // This should be compiled with profile.
+        var dex1Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex1Info.dexPath()).thenReturn(DEX_1);
+        lenient().when(dex1Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex1Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_1");
+        lenient().when(dex1Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dex1Info.isUsedByOtherApps()).thenReturn(false);
+        lenient().when(dex1Info.isDexFilePublic()).thenReturn(true);
+
+        // This should be compiled without profile because it's used by other apps.
+        var dex2Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex2Info.dexPath()).thenReturn(DEX_2);
+        lenient().when(dex2Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex2Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_2");
+        lenient().when(dex2Info.abiNames()).thenReturn(Set.of("arm64-v8a", "armeabi-v7a"));
+        lenient().when(dex2Info.isUsedByOtherApps()).thenReturn(true);
+        lenient().when(dex2Info.isDexFilePublic()).thenReturn(true);
+
+        // This should be compiled with verify because the class loader context is invalid.
+        var dex3Info = mock(DetailedSecondaryDexInfo.class);
+        lenient().when(dex3Info.dexPath()).thenReturn(DEX_3);
+        lenient().when(dex3Info.userHandle()).thenReturn(USER_HANDLE);
+        lenient().when(dex3Info.classLoaderContext()).thenReturn(null);
+        lenient().when(dex3Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+        lenient().when(dex3Info.isUsedByOtherApps()).thenReturn(false);
+        lenient().when(dex3Info.isDexFilePublic()).thenReturn(false);
+
+        return List.of(dex1Info, dex2Info, dex3Info);
+    }
+
+    private void prepareProfiles() throws Exception {
+        // Profile for dex file 1 is usable.
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex1RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex1RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Profiles for dex file 2 and 3 are also usable, but shouldn't be used.
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex2RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex2RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+        lenient().when(mArtd.isProfileUsable(deepEq(mDex3RefProfile), any())).thenReturn(true);
+        lenient()
+                .when(mArtd.getProfileVisibility(deepEq(mDex3RefProfile)))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
+    }
+
+    private GetDexoptNeededResult dexoptIsNeeded() {
+        var result = new GetDexoptNeededResult();
+        result.isDexoptNeeded = true;
+        result.artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+        result.isVdexUsable = false;
+        return result;
+    }
+
+    private ArtdDexoptResult createArtdDexoptResult() {
+        var result = new ArtdDexoptResult();
+        result.cancelled = false;
+        result.wallTimeMs = 0;
+        result.cpuTimeMs = 0;
+        result.sizeBytes = 0;
+        result.sizeBeforeBytes = 0;
+        return result;
+    }
+
+    private void checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa,
+            ProfilePath profile, String classLoaderContext) throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(false /* isPublic */);
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+                dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+        artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+                eq("speed-profile"), deepEq(profile), any(), isNull() /* dmFile */, anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private void checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa,
+            String compilerFilter, String classLoaderContext, boolean isPublic) throws Exception {
+        PermissionSettings permissionSettings = buildPermissionSettings(isPublic);
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+                dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+        artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+                eq(compilerFilter), isNull(), any(), isNull() /* dmFile */, anyInt(),
+                argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+    }
+
+    private PermissionSettings buildPermissionSettings(boolean isPublic) {
+        FsPermission dirFsPermission = AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */,
+                false /* isOtherReadable */, true /* isOtherExecutable */);
+        FsPermission fileFsPermission =
+                AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */, isPublic);
+        return AidlUtils.buildPermissionSettings(
+                dirFsPermission, fileFsPermission, AidlUtils.buildSeContext("se-info", UID));
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
new file mode 100644
index 0000000..ff09dc4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemProperties;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.Utils;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UtilsTest {
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("arm64");
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86"))).thenReturn("arm");
+
+        // In reality, the preferred ABI should be either the native 64 bit ABI or the native 32 bit
+        // ABI, but we use a different value here to make sure the value is used only if expected.
+        lenient().when(Constants.getPreferredAbi()).thenReturn("x86");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+    }
+
+    @Test
+    public void testCollectionIsEmptyTrue() {
+        assertThat(Utils.isEmpty(List.of())).isTrue();
+    }
+
+    @Test
+    public void testCollectionIsEmptyFalse() {
+        assertThat(Utils.isEmpty(List.of(1))).isFalse();
+    }
+
+    @Test
+    public void testSparseArrayIsEmptyTrue() {
+        assertThat(Utils.isEmpty(new SparseArray<Integer>())).isTrue();
+    }
+
+    @Test
+    public void testSparseArrayIsEmptyFalse() {
+        SparseArray<Integer> array = new SparseArray<>();
+        array.put(1, 1);
+        assertThat(Utils.isEmpty(array)).isFalse();
+    }
+
+    @Test
+    public void testArrayIsEmptyTrue() {
+        assertThat(Utils.isEmpty(new int[0])).isTrue();
+    }
+
+    @Test
+    public void testArrayIsEmptyFalse() {
+        assertThat(Utils.isEmpty(new int[] {1})).isFalse();
+    }
+
+    @Test
+    public void testGetAllAbis() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn("arm64-v8a");
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */),
+                        Utils.Abi.create("arm64-v8a", "arm64", false /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisTranslated() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn("x86");
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */),
+                        Utils.Abi.create("armeabi-v7a", "arm", false /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisPrimaryOnly() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */));
+    }
+
+    @Test
+    public void testGetAllAbisNone() {
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn(null);
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("x86", "x86", true /* isPrimaryAbi */));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetAllAbisInvalidNativeIsa() {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("x86");
+
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        Utils.getAllAbis(pkgState);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetAllAbisUnsupportedTranslation() {
+        lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("");
+
+        var pkgState = mock(PackageState.class);
+        when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+        when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+        Utils.getAllAbis(pkgState);
+    }
+
+    @Test
+    public void testImplies() {
+        assertThat(Utils.implies(false, false)).isTrue();
+        assertThat(Utils.implies(false, true)).isTrue();
+        assertThat(Utils.implies(true, false)).isFalse();
+        assertThat(Utils.implies(true, true)).isTrue();
+    }
+
+    @Test
+    public void testCheck() {
+        Utils.check(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCheckFailed() throws Exception {
+        Utils.check(false);
+    }
+
+    @Test
+    public void testExecuteAndWait() {
+        Executor executor = ForkJoinPool.commonPool();
+        List<String> results = new ArrayList<>();
+        Utils.executeAndWait(executor, () -> {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            results.add("foo");
+        });
+        assertThat(results).containsExactly("foo");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testExecuteAndWaitPropagatesException() {
+        Executor executor = ForkJoinPool.commonPool();
+        Utils.executeAndWait(executor, () -> { throw new IllegalArgumentException(); });
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java
new file mode 100644
index 0000000..aeff58c
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art.model;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexoptParamsTest {
+    @Test
+    public void testBuild() {
+        new DexoptParams.Builder("install").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildEmptyReason() {
+        new DexoptParams.Builder("").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildInvalidCompilerFilter() {
+        new DexoptParams.Builder("install").setCompilerFilter("invalid").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildInvalidPriorityClass() {
+        new DexoptParams.Builder("install").setPriorityClass(101).build();
+    }
+
+    @Test
+    public void testBuildCustomReason() {
+        new DexoptParams.Builder("custom").setCompilerFilter("speed").setPriorityClass(90).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildCustomReasonEmptyCompilerFilter() {
+        new DexoptParams.Builder("custom").setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuildCustomReasonEmptyPriorityClass() {
+        new DexoptParams.Builder("custom").setCompilerFilter("speed").build();
+    }
+
+    @Test
+    public void testSingleSplit() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitNoPrimaryFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitSecondaryFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                        | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSingleSplitDependenciesFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES
+                        | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                .setSplitName("split_0")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSplitNameNoSingleSplitFlag() {
+        new DexoptParams.Builder("install")
+                .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX)
+                .setSplitName("split_0")
+                .build();
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/MockClock.java b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java
new file mode 100644
index 0000000..7b4b23b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import android.annotation.NonNull;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class MockClock {
+    private long mCurrentTimeMs = 0;
+    @NonNull private List<ScheduledExecutor> mExecutors = new ArrayList<>();
+
+    @NonNull
+    public ScheduledExecutor createScheduledExecutor() {
+        var executor = new ScheduledExecutor();
+        mExecutors.add(executor);
+        return executor;
+    }
+
+    public long getCurrentTimeMs() {
+        return mCurrentTimeMs;
+    }
+
+    public void advanceTime(long timeMs) {
+        mCurrentTimeMs += timeMs;
+        for (ScheduledExecutor executor : mExecutors) {
+            executor.notifyUpdate();
+        }
+    }
+
+    public class ScheduledExecutor extends ScheduledThreadPoolExecutor {
+        // The second element of the pair is the scheduled time.
+        @NonNull
+        private PriorityQueue<Pair<RunnableScheduledFuture<?>, Long>> tasks = new PriorityQueue<>(
+                1 /* initialCapacity */, Comparator.comparingLong(pair -> pair.second));
+
+        public ScheduledExecutor() {
+            super(1 /* corePoolSize */);
+        }
+
+        @NonNull
+        public ScheduledFuture<?> schedule(
+                @NonNull Runnable command, long delay, @NonNull TimeUnit unit) {
+            // Use `Long.MAX_VALUE` to prevent the task from being automatically run.
+            var task = (RunnableScheduledFuture<?>) super.schedule(
+                    command, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+            tasks.add(Pair.create(task, getCurrentTimeMs() + unit.toMillis(delay)));
+            return task;
+        }
+
+        public void notifyUpdate() {
+            while (!tasks.isEmpty()) {
+                Pair<RunnableScheduledFuture<?>, Long> pair = tasks.peek();
+                RunnableScheduledFuture<?> task = pair.first;
+                long scheduledTimeMs = pair.second;
+                if (getCurrentTimeMs() >= scheduledTimeMs) {
+                    if (!task.isDone() && !task.isCancelled()) {
+                        task.run();
+                    }
+                    tasks.poll();
+                    // Remove the task from the queue of the executor. Terminate the executor if
+                    // it's shutdown and the queue is empty.
+                    super.remove(task);
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java b/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java
new file mode 100644
index 0000000..350a8cb
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+/** A JUnit rule that invokes a runnable on success. */
+public class OnSuccessRule implements MethodRule {
+    private RunnableThrowingException mRunnable;
+
+    public OnSuccessRule(RunnableThrowingException runnable) {
+        mRunnable = runnable;
+    }
+
+    @Override
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                mRunnable.run();
+            }
+        };
+    }
+
+    public interface RunnableThrowingException {
+        void run() throws Exception;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java b/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java
new file mode 100644
index 0000000..595370b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/StaticMockitoRule.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+/**
+ * Similar to {@link MockitoRule}, but uses {@StaticMockitoSession}, which allows mocking static
+ * methods.
+ */
+public class StaticMockitoRule implements MethodRule {
+    private Class<?>[] mClasses;
+
+    public StaticMockitoRule(Class<?>... classes) {
+        mClasses = classes;
+    }
+
+    @Override
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            public void evaluate() throws Throwable {
+                StaticMockitoSessionBuilder builder =
+                        mockitoSession()
+                                .name(target.getClass().getSimpleName() + "." + method.getName())
+                                .initMocks(target)
+                                .strictness(Strictness.STRICT_STUBS);
+
+                for (Class<?> clazz : mClasses) {
+                    builder.mockStatic(clazz);
+                }
+
+                StaticMockitoSession session = builder.startMocking();
+                Throwable testFailure = evaluateSafely(base);
+                session.finishMocking(testFailure);
+                if (testFailure != null) {
+                    throw testFailure;
+                }
+            }
+
+            private Throwable evaluateSafely(Statement base) {
+                try {
+                    base.evaluate();
+                    return null;
+                } catch (Throwable throwable) {
+                    return throwable;
+                }
+            }
+        };
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
new file mode 100644
index 0000000..36f1ce4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art.testing;
+
+import static org.mockito.Mockito.argThat;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.Truth;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+public final class TestingUtils {
+    private static final String TAG = "TestingUtils";
+
+    private TestingUtils() {}
+
+    /**
+     * Recursively compares two objects using reflection. Returns true if the two objects are equal.
+     * For simplicity, this method only supports types that every field is a primitive type, a
+     * string, a {@link List}, or a supported type.
+     */
+    public static boolean deepEquals(
+            @Nullable Object a, @Nullable Object b, @NonNull StringBuilder errorMsg) {
+        try {
+            if (a == null && b == null) {
+                return true;
+            }
+            if (a == null || b == null) {
+                errorMsg.append(String.format("Nullability mismatch: %s != %s",
+                        a == null ? "null" : "nonnull", b == null ? "null" : "nonnull"));
+                return false;
+            }
+            if (a instanceof List && b instanceof List) {
+                return listDeepEquals((List<?>) a, (List<?>) b, errorMsg);
+            }
+            if (a.getClass() != b.getClass()) {
+                errorMsg.append(
+                        String.format("Type mismatch: %s != %s", a.getClass(), b.getClass()));
+                return false;
+            }
+            if (a.getClass() == String.class) {
+                if (!a.equals(b)) {
+                    errorMsg.append(String.format("%s != %s", a, b));
+                }
+                return a.equals(b);
+            }
+            if (a.getClass().isArray()) {
+                throw new UnsupportedOperationException("Array type is not supported");
+            }
+            for (Field field : a.getClass().getDeclaredFields()) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    continue;
+                }
+                field.setAccessible(true);
+                if (field.getType().isPrimitive()) {
+                    if (!field.get(a).equals(field.get(b))) {
+                        errorMsg.append(String.format("Field %s mismatch: %s != %s",
+                                field.getName(), field.get(a), field.get(b)));
+                        return false;
+                    }
+                } else if (!deepEquals(field.get(a), field.get(b), errorMsg)) {
+                    errorMsg.insert(0, String.format("Field %s mismatch: ", field.getName()));
+                    return false;
+                }
+            }
+            return true;
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Same as above, but ignores any error message. */
+    public static boolean deepEquals(@Nullable Object a, @Nullable Object b) {
+        var errorMsgIgnored = new StringBuilder();
+        return deepEquals(a, b, errorMsgIgnored);
+    }
+
+    /**
+     * A Mockito argument matcher that uses {@link #deepEquals} to compare objects and logs any
+     * mismatch.
+     */
+    public static <T> T deepEq(@Nullable T expected) {
+        return argThat(arg -> {
+            var errorMsg = new StringBuilder();
+            boolean result = deepEquals(arg, expected, errorMsg);
+            if (!result) {
+                Log.e(TAG, errorMsg.toString());
+            }
+            return result;
+        });
+    }
+
+    /**
+     * A Mockito argument matcher that matches a list containing expected in any order.
+     */
+    @SafeVarargs
+    public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrder(
+            @Nullable ItemType... expected) {
+        return argThat(argument -> {
+            try {
+                Truth.assertThat(argument).containsExactlyElementsIn(expected);
+                return true;
+            } catch (AssertionError error) {
+                return false;
+            }
+        });
+    }
+
+    /**
+     * {@link #inAnyOrder(Object[])} but using {@link #deepEquals(Object, Object)}} for comparisons.
+     *
+     * @see #inAnyOrder(Object[])
+     */
+    @SafeVarargs
+    public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrderDeepEquals(
+            @Nullable ItemType... expected) {
+        return argThat(argument -> {
+            try {
+                Truth.assertThat(argument)
+                        .comparingElementsUsing(deepEquality())
+                        .containsExactlyElementsIn(expected);
+                return true;
+            } catch (AssertionError error) {
+                return false;
+            }
+        });
+    }
+
+    /**
+     * A Truth correspondence that uses {@link #deepEquals} to compare objects and reports any
+     * mismatch.
+     */
+    public static <T> Correspondence<T, T> deepEquality() {
+        return Correspondence.<T, T>from(TestingUtils::deepEquals, "deeply equals")
+                .formattingDiffsUsing((actual, expected) -> {
+                    var errorMsg = new StringBuilder();
+                    deepEquals(actual, expected, errorMsg);
+                    return errorMsg.toString();
+                });
+    }
+
+    private static boolean listDeepEquals(
+            @NonNull List<?> a, @NonNull List<?> b, @NonNull StringBuilder errorMsg) {
+        if (a.size() != b.size()) {
+            errorMsg.append(String.format("List length mismatch: %d != %d", a.size(), b.size()));
+            return false;
+        }
+        for (int i = 0; i < a.size(); i++) {
+            if (!deepEquals(a.get(i), b.get(i), errorMsg)) {
+                errorMsg.insert(0, String.format("Element %d mismatch: ", i));
+                return false;
+            }
+        }
+        return true;
+    };
+}
diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java
new file mode 100644
index 0000000..518f0e4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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
+ */
+
+package com.android.server.art.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.internal.progress.ThreadSafeMockingProgress;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TestingUtilsTest {
+    @Before
+    @After
+    public void resetMockito() {
+        ThreadSafeMockingProgress.mockingProgress().reset();
+    }
+
+    @Test
+    public void testDeepEquals() {
+        var a = new Foo();
+        var b = new Foo();
+        assertThat(TestingUtils.deepEquals(a, b)).isTrue();
+    }
+
+    @Test
+    public void testDeepEqualsNull() {
+        assertThat(TestingUtils.deepEquals(null, null)).isTrue();
+    }
+
+    @Test
+    public void testDeepEqualsNullabilityMismatch() {
+        var a = new Foo();
+        assertThat(TestingUtils.deepEquals(a, null)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsTypeMismatch() {
+        var a = new Foo();
+        var b = new Bar();
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsPrimitiveFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mA = 11111111;
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testDeepEqualsStringFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mB = "def";
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void deepEqualsNestedFieldMismatch() {
+        var a = new Foo();
+        var b = new Foo();
+        b.mC.setB(11111111);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testDeepEqualsArrayNotSupported() throws Exception {
+        int[] a = new int[] {1};
+        int[] b = new int[] {2};
+        TestingUtils.deepEquals(a, b);
+    }
+
+    @Test
+    public void testListDeepEquals() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        a.add(2);
+        a.add(3);
+        a.add(4);
+        a.add(5);
+        var b = List.of(1, 2, 3, 4, 5);
+        assertThat(TestingUtils.deepEquals(a, b)).isTrue();
+    }
+
+    @Test
+    public void testListDeepEqualsSizeMismatch() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        var b = new ArrayList<Integer>();
+        b.add(1);
+        b.add(2);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test
+    public void testListDeepEqualsElementMismatch() throws Exception {
+        var a = new ArrayList<Integer>();
+        a.add(1);
+        var b = new ArrayList<Integer>();
+        b.add(2);
+        assertThat(TestingUtils.deepEquals(a, b)).isFalse();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testDeepEqualsOtherContainerNotSupported() throws Exception {
+        var a = new HashSet<Integer>();
+        a.add(1);
+        var b = new HashSet<Integer>();
+        b.add(2);
+        TestingUtils.deepEquals(a, b);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Test
+    public void testInAnyOrderDuplicates() {
+        testInAnyOrderInternal(List.of(1, 1), List.of(1), false, TestingUtils::inAnyOrder);
+        testInAnyOrderInternal(List.of(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrder);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Test
+    public void testInAnyOrderDeepEqualsDuplicates() {
+        testInAnyOrderInternal(
+                Arrays.asList(1, 1), List.of(1), false, TestingUtils::inAnyOrderDeepEquals);
+        testInAnyOrderInternal(
+                Arrays.asList(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrderDeepEquals);
+    }
+
+    private void testInAnyOrderInternal(@NonNull List<Integer> first, @NonNull List<Integer> second,
+            boolean expected, @NonNull Consumer<Integer[]> inAnyOrderBlock) {
+        inAnyOrderBlock.accept(first.toArray(new Integer[0]));
+        var matchers = ThreadSafeMockingProgress.mockingProgress()
+                               .getArgumentMatcherStorage()
+                               .pullLocalizedMatchers();
+        assertThat(matchers).hasSize(1);
+        // noinspection unchecked
+        var matcher = (ArgumentMatcher<List<Integer>>) matchers.get(0).getMatcher();
+        assertThat(matcher.matches(second)).isEqualTo(expected);
+        ThreadSafeMockingProgress.mockingProgress().reset();
+    }
+}
+
+class Foo {
+    public int mA = 1234567;
+    public String mB = "abc";
+    public Bar mC = new Bar();
+}
+
+class Bar {
+    public static int sA = 10000000;
+    private int mB = 7654321;
+    public void setB(int b) {
+        mB = b;
+    }
+}
diff --git a/libartservice/service/proguard.flags b/libartservice/service/proguard.flags
new file mode 100644
index 0000000..8ef413f
--- /dev/null
+++ b/libartservice/service/proguard.flags
@@ -0,0 +1,10 @@
+# Proto field names are used by MessageLiteToString.toString through reflection.
+-keepclassmembers class * extends
+    com.android.server.art.jarjar.com.google.protobuf.GeneratedMessageLite {
+  *** get*();
+  *** set*(***);
+  *** has*();
+}
+
+# A job service is referenced by the framework through reflection.
+-keep class * extends android.app.job.JobService { *; }
diff --git a/libartservice/service/proto/common.proto b/libartservice/service/proto/common.proto
new file mode 100644
index 0000000..c8bb565
--- /dev/null
+++ b/libartservice/service/proto/common.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+// Wrapper message for `int32`, to distinguish between the absence of a field
+// and its default value.
+message Int32Value {
+    int32 value = 1;
+}
diff --git a/libartservice/service/proto/dex_use.proto b/libartservice/service/proto/dex_use.proto
new file mode 100644
index 0000000..1dd962d
--- /dev/null
+++ b/libartservice/service/proto/dex_use.proto
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+import "art/libartservice/service/proto/common.proto";
+
+// The protobuf representation of `DexUseManagerLocal.DexUse`. See classes in
+// java/com/android/server/art/DexUseManagerLocal.java for details.
+// This proto is persisted on disk and both forward and backward compatibility are considerations.
+message DexUseProto {
+    repeated PackageDexUseProto package_dex_use = 1;
+}
+
+message PackageDexUseProto {
+    string owning_package_name = 1;
+    repeated PrimaryDexUseProto primary_dex_use = 2;
+    repeated SecondaryDexUseProto secondary_dex_use = 3;
+}
+
+message PrimaryDexUseProto {
+    string dex_file = 1;
+    repeated PrimaryDexUseRecordProto record = 2;
+}
+
+message PrimaryDexUseRecordProto {
+    string loading_package_name = 1;
+    bool isolated_process = 2;
+    int64 last_used_at_ms = 3;
+}
+
+message SecondaryDexUseProto {
+    string dex_file = 1;
+    Int32Value user_id = 2;  // Must be explicitly set.
+    repeated SecondaryDexUseRecordProto record = 3;
+}
+
+message SecondaryDexUseRecordProto {
+    string loading_package_name = 1;
+    bool isolated_process = 2;
+    string class_loader_context = 3;
+    string abi_name = 4;
+    int64 last_used_at_ms = 5;
+}
diff --git a/libarttools/Android.bp b/libarttools/Android.bp
index 3df40a5..13fa205 100644
--- a/libarttools/Android.bp
+++ b/libarttools/Android.bp
@@ -49,12 +49,18 @@
 art_cc_defaults {
     name: "art_libarttools_tests_defaults",
     srcs: [
+        "tools/art_exec_test.cc",
+        "tools/cmdline_builder_test.cc",
+        "tools/system_properties_test.cc",
         "tools/tools_test.cc",
     ],
     shared_libs: [
         "libbase",
         "libarttools",
     ],
+    static_libs: [
+        "libgmock",
+    ],
 }
 
 // Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target.
@@ -76,3 +82,39 @@
         "art_libarttools_tests_defaults",
     ],
 }
+
+// A defaults that contains libprocessgroup and all its dependencies.
+cc_defaults {
+    name: "art_libprocessgroup_defaults",
+    shared_libs: [
+        "libbase",
+        "libcgrouprc",
+    ],
+    static_libs: [
+        "libjsoncpp",
+        "libprocessgroup",
+    ],
+}
+
+cc_binary {
+    name: "art_exec",
+    defaults: [
+        "art_defaults",
+        "art_libprocessgroup_defaults",
+    ],
+    srcs: [
+        "tools/art_exec.cc",
+    ],
+    shared_libs: [
+        "libartbase",
+        "libbase",
+    ],
+    static_libs: [
+        "libc++fs",
+        "libcap",
+    ],
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
diff --git a/libarttools/tools/art_exec.cc b/libarttools/tools/art_exec.cc
new file mode 100644
index 0000000..7fecda6
--- /dev/null
+++ b/libarttools/tools/art_exec.cc
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 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 <stdlib.h>
+#include <sys/capability.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "base/macros.h"
+#include "base/scoped_cap.h"
+#include "fmt/format.h"
+#include "processgroup/processgroup.h"
+#include "system/thread_defs.h"
+
+namespace {
+
+using ::android::base::ConsumePrefix;
+using ::android::base::Join;
+using ::android::base::ParseInt;
+using ::android::base::Result;
+using ::android::base::Split;
+
+using ::fmt::literals::operator""_format;  // NOLINT
+
+constexpr const char* kUsage =
+    R"(A wrapper binary that configures the process and executes a command.
+
+By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
+be passed to keep some more file descriptors open.
+
+Usage: art_exec [OPTIONS]... -- [COMMAND]...
+
+Supported options:
+  --help: Print this text.
+  --set-task-profile=PROFILES: Apply a set of task profiles (see
+      https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
+      comma-separated list of task profile names.
+  --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
+      PRIORITY is "background".
+  --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
+      with some root capabilities but not as the root user.
+  --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
+  --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
+      multiple environment variables.
+)";
+
+constexpr int kErrorUsage = 100;
+constexpr int kErrorOther = 101;
+
+struct Options {
+  int command_pos = -1;
+  std::vector<std::string> task_profiles;
+  std::optional<int> priority = std::nullopt;
+  bool drop_capabilities = false;
+  std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
+  std::unordered_map<std::string, std::string> envs;
+};
+
+[[noreturn]] void Usage(const std::string& error_msg) {
+  LOG(ERROR) << error_msg;
+  std::cerr << error_msg << "\n" << kUsage << "\n";
+  exit(kErrorUsage);
+}
+
+Options ParseOptions(int argc, char** argv) {
+  Options options;
+  for (int i = 1; i < argc; i++) {
+    std::string_view arg = argv[i];
+    if (arg == "--help") {
+      std::cerr << kUsage << "\n";
+      exit(0);
+    } else if (ConsumePrefix(&arg, "--set-task-profile=")) {
+      options.task_profiles = Split(std::string(arg), ",");
+      if (options.task_profiles.empty()) {
+        Usage("Empty task profile list");
+      }
+    } else if (ConsumePrefix(&arg, "--set-priority=")) {
+      if (arg == "background") {
+        options.priority = ANDROID_PRIORITY_BACKGROUND;
+      } else {
+        Usage("Unknown priority " + std::string(arg));
+      }
+    } else if (arg == "--drop-capabilities") {
+      options.drop_capabilities = true;
+    } else if (ConsumePrefix(&arg, "--keep-fds=")) {
+      for (const std::string& fd_str : Split(std::string(arg), ":")) {
+        int fd;
+        if (!ParseInt(fd_str, &fd)) {
+          Usage("Invalid fd " + fd_str);
+        }
+        options.keep_fds.insert(fd);
+      }
+    } else if (ConsumePrefix(&arg, "--env=")) {
+      size_t pos = arg.find('=');
+      if (pos == std::string_view::npos) {
+        Usage("Malformed environment variable. Must contain '='");
+      }
+      options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
+          std::string(arg.substr(pos + 1));
+    } else if (arg == "--") {
+      if (i + 1 >= argc) {
+        Usage("Missing command after '--'");
+      }
+      options.command_pos = i + 1;
+      return options;
+    } else {
+      Usage("Unknown option " + std::string(arg));
+    }
+  }
+  Usage("Missing '--'");
+}
+
+Result<void> DropInheritableCaps() {
+  art::ScopedCap cap(cap_get_proc());
+  if (cap.Get() == nullptr) {
+    return ErrnoErrorf("Failed to call cap_get_proc");
+  }
+  if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
+    return ErrnoErrorf("Failed to call cap_clear_flag");
+  }
+  if (cap_set_proc(cap.Get()) != 0) {
+    return ErrnoErrorf("Failed to call cap_set_proc");
+  }
+  return {};
+}
+
+Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
+  std::vector<int> open_fds;
+  std::error_code ec;
+  for (const std::filesystem::directory_entry& dir_entry :
+       std::filesystem::directory_iterator("/proc/self/fd", ec)) {
+    int fd;
+    if (!ParseInt(dir_entry.path().filename(), &fd)) {
+      return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
+    }
+    open_fds.push_back(fd);
+  }
+  if (ec) {
+    return Errorf("Failed to list open FDs: {}", ec.message());
+  }
+  for (int fd : open_fds) {
+    if (keep_fds.find(fd) == keep_fds.end()) {
+      if (close(fd) != 0) {
+        Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
+        if (std::filesystem::exists("/proc/self/fd/{}"_format(fd))) {
+          return error;
+        }
+      }
+    }
+  }
+  return {};
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  android::base::InitLogging(argv);
+
+  Options options = ParseOptions(argc, argv);
+
+  if (auto result = CloseFds(options.keep_fds); !result.ok()) {
+    LOG(ERROR) << "Failed to close open FDs: " << result.error();
+    return kErrorOther;
+  }
+
+  if (!options.task_profiles.empty()) {
+    if (!SetTaskProfiles(/*tid=*/0, options.task_profiles)) {
+      LOG(ERROR) << "Failed to set task profile";
+      return kErrorOther;
+    }
+  }
+
+  if (options.priority.has_value()) {
+    if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
+      PLOG(ERROR) << "Failed to setpriority";
+      return kErrorOther;
+    }
+  }
+
+  if (options.drop_capabilities) {
+    if (auto result = DropInheritableCaps(); !result.ok()) {
+      LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
+      return kErrorOther;
+    }
+  }
+
+  for (const auto& [key, value] : options.envs) {
+    setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
+  }
+
+  execv(argv[options.command_pos], argv + options.command_pos);
+
+  std::vector<const char*> command_args(argv + options.command_pos, argv + argc);
+  PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
+  UNREACHABLE();
+}
diff --git a/libarttools/tools/art_exec_test.cc b/libarttools/tools/art_exec_test.cc
new file mode 100644
index 0000000..43f417e
--- /dev/null
+++ b/libarttools/tools/art_exec_test.cc
@@ -0,0 +1,249 @@
+/*
+ * 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 <sys/capability.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <csignal>
+#include <filesystem>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "base/scoped_cap.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "system/thread_defs.h"
+
+namespace art {
+namespace {
+
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+using ::android::base::Split;
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+// clang-tidy incorrectly complaints about the using declaration while the user-defined literal is
+// actually being used.
+using ::fmt::literals::operator""_format;  // NOLINT
+
+constexpr uid_t kRoot = 0;
+constexpr uid_t kNobody = 9999;
+
+// This test executes a few Linux system commands such as "ls", which are linked against system
+// libraries. In many ART gtests we set LD_LIBRARY_PATH to make the test binaries link to libraries
+// from the ART module first, and if that setting is propagated to the system commands they may also
+// try to link to those libraries instead of the system ones they are built against. This is
+// particularly noticeable when 32-bit tests run on a 64-bit system. Hence we need to set
+// LD_LIBRARY_PATH to an empty string here.
+// TODO(b/247108425): Remove this when ART gtests no longer use LD_LIBRARY_PATH.
+constexpr const char* kEmptyLdLibraryPath = "--env=LD_LIBRARY_PATH=";
+
+std::string GetArtBin(const std::string& name) { return "{}/bin/{}"_format(GetArtRoot(), name); }
+
+std::string GetBin(const std::string& name) { return "{}/bin/{}"_format(GetAndroidRoot(), name); }
+
+// Executes the command, waits for it to finish, and keeps it in a waitable state until the current
+// scope exits.
+std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait(
+    std::vector<std::string>& args) {
+  std::vector<char*> execv_args;
+  execv_args.reserve(args.size() + 1);
+  for (std::string& arg : args) {
+    execv_args.push_back(arg.data());
+  }
+  execv_args.push_back(nullptr);
+
+  pid_t pid = fork();
+  if (pid == 0) {
+    execv(execv_args[0], execv_args.data());
+    UNREACHABLE();
+  } else if (pid > 0) {
+    siginfo_t info;
+    CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0);
+    CHECK_EQ(info.si_code, CLD_EXITED);
+    CHECK_EQ(info.si_status, 0);
+    std::function<void()> cleanup([=] {
+      siginfo_t info;
+      CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0);
+    });
+    return std::make_pair(pid, make_scope_guard(std::move(cleanup)));
+  } else {
+    LOG(FATAL) << "Failed to call fork";
+    UNREACHABLE();
+  }
+}
+
+// Grants the current process the given root capability.
+void SetCap(cap_flag_t flag, cap_value_t value) {
+  ScopedCap cap(cap_get_proc());
+  CHECK_NE(cap.Get(), nullptr);
+  cap_value_t caps[]{value};
+  CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0);
+  CHECK_EQ(cap_set_proc(cap.Get()), 0);
+}
+
+// Returns true if the given process has the given root capability.
+bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) {
+  ScopedCap cap(cap_get_pid(pid));
+  CHECK_NE(cap.Get(), nullptr);
+  cap_flag_value_t flag_value;
+  CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0);
+  return flag_value == CAP_SET;
+}
+
+class ArtExecTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    testing::Test::SetUp();
+    if (!kIsTargetAndroid) {
+      GTEST_SKIP() << "art_exec is for device only";
+    }
+    if (getuid() != kRoot) {
+      GTEST_SKIP() << "art_exec requires root";
+    }
+    art_exec_bin_ = GetArtBin("art_exec");
+  }
+
+  std::string art_exec_bin_;
+};
+
+TEST_F(ArtExecTest, Command) {
+  std::string error_msg;
+  int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg);
+  ASSERT_EQ(ret, 123) << error_msg;
+}
+
+TEST_F(ArtExecTest, SetTaskProfiles) {
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--set-task-profile=ProcessCapacityHigh",
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "cat /proc/self/cgroup > " + filename};
+  auto [pid, scope_guard] = ScopedExecAndWait(args);
+  std::string cgroup;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup));
+  EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n"));
+}
+
+TEST_F(ArtExecTest, SetPriority) {
+  std::vector<std::string> args{
+      art_exec_bin_, "--set-priority=background", kEmptyLdLibraryPath, "--", GetBin("true")};
+  auto [pid, scope_guard] = ScopedExecAndWait(args);
+  EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND);
+}
+
+TEST_F(ArtExecTest, DropCapabilities) {
+  // Switch to a non-root user, but still keep the CAP_FOWNER capability available and inheritable.
+  // The order of the following calls matters.
+  CHECK_EQ(cap_setuid(kNobody), 0);
+  SetCap(CAP_INHERITABLE, CAP_FOWNER);
+  SetCap(CAP_EFFECTIVE, CAP_FOWNER);
+  ASSERT_EQ(cap_set_ambient(CAP_FOWNER, CAP_SET), 0);
+
+  // Make sure the test is set up correctly (i.e., the child process should normally have the
+  // inherited root capability: CAP_FOWNER).
+  {
+    std::vector<std::string> args{art_exec_bin_, kEmptyLdLibraryPath, "--", GetBin("true")};
+    auto [pid, scope_guard] = ScopedExecAndWait(args);
+    ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
+  }
+
+  {
+    std::vector<std::string> args{
+        art_exec_bin_, "--drop-capabilities", kEmptyLdLibraryPath, "--", GetBin("true")};
+    auto [pid, scope_guard] = ScopedExecAndWait(args);
+    EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
+  }
+}
+
+TEST_F(ArtExecTest, CloseFds) {
+  std::unique_ptr<File> file1(OS::OpenFileForReading("/dev/zero"));
+  std::unique_ptr<File> file2(OS::OpenFileForReading("/dev/zero"));
+  std::unique_ptr<File> file3(OS::OpenFileForReading("/dev/zero"));
+  ASSERT_NE(file1, nullptr);
+  ASSERT_NE(file2, nullptr);
+  ASSERT_NE(file3, nullptr);
+
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--keep-fds={}:{}"_format(file3->Fd(), file2->Fd()),
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "ls /proc/self/fd > " + filename};
+
+  ScopedExecAndWait(args);
+
+  std::string open_fds;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds));
+
+  EXPECT_THAT(Split(open_fds, "\n"),
+              AllOf(Not(Contains(std::to_string(file1->Fd()))),
+                    Contains(std::to_string(file2->Fd())),
+                    Contains(std::to_string(file3->Fd()))));
+}
+
+TEST_F(ArtExecTest, Env) {
+  std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+  ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+  ASSERT_GE(scratch_file.GetFd(), 0);
+
+  std::vector<std::string> args{art_exec_bin_,
+                                "--env=FOO=BAR",
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                "env > " + filename};
+
+  ScopedExecAndWait(args);
+
+  std::string envs;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &envs));
+
+  EXPECT_THAT(Split(envs, "\n"), Contains("FOO=BAR"));
+}
+
+}  // namespace
+}  // namespace art
diff --git a/libarttools/tools/cmdline_builder.h b/libarttools/tools/cmdline_builder.h
new file mode 100644
index 0000000..fd11ee8
--- /dev/null
+++ b/libarttools/tools/cmdline_builder.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
+#define ART_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+
+namespace art {
+namespace tools {
+
+namespace internal {
+
+constexpr bool ContainsOneFormatSpecifier(std::string_view format, char specifier) {
+  int count = 0;
+  size_t pos = 0;
+  while ((pos = format.find('%', pos)) != std::string_view::npos) {
+    if (pos == format.length() - 1) {
+      // Invalid trailing '%'.
+      return false;
+    }
+    if (format[pos + 1] == specifier) {
+      count++;
+    } else if (format[pos + 1] != '%') {
+      // "%%" is okay. Otherwise, it's a wrong specifier.
+      return false;
+    }
+    pos += 2;
+  }
+  return count == 1;
+}
+
+}  // namespace internal
+
+// A util class that builds cmdline arguments.
+class CmdlineBuilder {
+ public:
+  // Returns all arguments.
+  const std::vector<std::string>& Get() const { return elements_; }
+
+  // Adds an argument as-is.
+  CmdlineBuilder& Add(std::string_view arg) {
+    elements_.push_back(std::string(arg));
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(std::string_view arg) { return Add("--runtime-arg").Add(arg); }
+
+  // Adds a string value formatted by the format string.
+  //
+  // Usage: Add("--flag=%s", "value")
+  CmdlineBuilder& Add(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    return Add(android::base::StringPrintf(arg_format, value.c_str()));
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    return AddRuntime(android::base::StringPrintf(arg_format, value.c_str()));
+  }
+
+  // Adds an integer value formatted by the format string.
+  //
+  // Usage: Add("--flag=%d", 123)
+  CmdlineBuilder& Add(const char* arg_format, int value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 'd'),
+                               "'arg' must be a string literal that contains '%d'"))) {
+    return Add(android::base::StringPrintf(arg_format, value));
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntime(const char* arg_format, int value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 'd'),
+                               "'arg' must be a string literal that contains '%d'"))) {
+    return AddRuntime(android::base::StringPrintf(arg_format, value));
+  }
+
+  // Adds a string value formatted by the format string if the value is non-empty. Does nothing
+  // otherwise.
+  //
+  // Usage: AddIfNonEmpty("--flag=%s", "value")
+  CmdlineBuilder& AddIfNonEmpty(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    if (!value.empty()) {
+      Add(android::base::StringPrintf(arg_format, value.c_str()));
+    }
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntimeIfNonEmpty(const char* arg_format, const std::string& value)
+      __attribute__((enable_if(internal::ContainsOneFormatSpecifier(arg_format, 's'),
+                               "'arg' must be a string literal that contains '%s'"))) {
+    if (!value.empty()) {
+      AddRuntime(android::base::StringPrintf(arg_format, value.c_str()));
+    }
+    return *this;
+  }
+
+  // Adds an argument as-is if the boolean value is true. Does nothing otherwise.
+  CmdlineBuilder& AddIf(bool value, std::string_view arg) {
+    if (value) {
+      Add(arg);
+    }
+    return *this;
+  }
+
+  // Same as above but adds a runtime argument.
+  CmdlineBuilder& AddRuntimeIf(bool value, std::string_view arg) {
+    if (value) {
+      AddRuntime(arg);
+    }
+    return *this;
+  }
+
+  // Concatenates this builder with another. Returns the concatenated result and nullifies the input
+  // builder.
+  CmdlineBuilder& Concat(CmdlineBuilder&& other) {
+    elements_.reserve(elements_.size() + other.elements_.size());
+    std::move(other.elements_.begin(), other.elements_.end(), std::back_inserter(elements_));
+    other.elements_.clear();
+    return *this;
+  }
+
+ private:
+  std::vector<std::string> elements_;
+};
+
+}  // namespace tools
+}  // namespace art
+
+#endif  // ART_LIBARTTOOLS_TOOLS_CMDLINE_BUILDER_H_
diff --git a/libarttools/tools/cmdline_builder_test.cc b/libarttools/tools/cmdline_builder_test.cc
new file mode 100644
index 0000000..5551860
--- /dev/null
+++ b/libarttools/tools/cmdline_builder_test.cc
@@ -0,0 +1,135 @@
+/*
+ * 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 "cmdline_builder.h"
+
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+class CmdlineBuilderTest : public testing::Test {
+ protected:
+  CmdlineBuilder args_;
+};
+
+TEST_F(CmdlineBuilderTest, ContainsOneFormatSpecifier) {
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%s", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=[%s]", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%s%%", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=[%s%%]", 's'));
+  EXPECT_TRUE(internal::ContainsOneFormatSpecifier("--flag=%%%s", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%s", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%d", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%s%d", 's'));
+  EXPECT_FALSE(internal::ContainsOneFormatSpecifier("--flag=%%s", 's'));
+}
+
+TEST_F(CmdlineBuilderTest, Add) {
+  args_.Add("--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntime) {
+  args_.AddRuntime("--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddString) {
+  args_.Add("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeString) {
+  args_.AddRuntime("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddInt) {
+  args_.Add("--flag=[%d]", 123);
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[123]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeInt) {
+  args_.AddRuntime("--flag=[%d]", 123);
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[123]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfNonEmpty) {
+  args_.AddIfNonEmpty("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfNonEmptyEmpty) {
+  args_.AddIfNonEmpty("--flag=[%s]", "");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfNonEmpty) {
+  args_.AddRuntimeIfNonEmpty("--flag=[%s]", "foo");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag=[foo]"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfNonEmptyEmpty) {
+  args_.AddRuntimeIfNonEmpty("--flag=[%s]", "");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddIfTrue) {
+  args_.AddIf(true, "--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddIfFalse) {
+  args_.AddIf(false, "--flag");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfTrue) {
+  args_.AddRuntimeIf(true, "--flag");
+  EXPECT_THAT(args_.Get(), ElementsAre("--runtime-arg", "--flag"));
+}
+
+TEST_F(CmdlineBuilderTest, AddRuntimeIfFalse) {
+  args_.AddRuntimeIf(false, "--flag");
+  EXPECT_THAT(args_.Get(), IsEmpty());
+}
+
+TEST_F(CmdlineBuilderTest, Concat) {
+  args_.Add("--flag1");
+  args_.Add("--flag2");
+
+  CmdlineBuilder other;
+  other.Add("--flag3");
+  other.Add("--flag4");
+
+  args_.Concat(std::move(other));
+  EXPECT_THAT(args_.Get(), ElementsAre("--flag1", "--flag2", "--flag3", "--flag4"));
+  EXPECT_THAT(other.Get(), IsEmpty());
+}
+
+}  // namespace
+}  // namespace tools
+}  // namespace art
diff --git a/libarttools/tools/system_properties.h b/libarttools/tools/system_properties.h
new file mode 100644
index 0000000..06b7bcb
--- /dev/null
+++ b/libarttools/tools/system_properties.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+#define ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+
+#include <string>
+
+#include "android-base/parsebool.h"
+#include "android-base/properties.h"
+
+namespace art {
+namespace tools {
+
+// A class for getting system properties with fallback lookup support. Different from
+// android::base::GetProperty, this class is mockable.
+class SystemProperties {
+ public:
+  virtual ~SystemProperties() = default;
+
+  // Returns the current value of the system property `key`, or `default_value` if the property
+  // doesn't have a value.
+  std::string Get(const std::string& key, const std::string& default_value) const {
+    std::string value = GetProperty(key);
+    if (!value.empty()) {
+      return value;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a string
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return "default":
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/"default")
+  template <typename... Args>
+  std::string Get(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return Get(key, Get(fallback_key, args...));
+  }
+
+  // Returns the current value of the system property `key` with zero or more fallback keys, or an
+  // empty string if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1". If it doesn't have a value, return an empty string:
+  //  GetOrEmpty("key_1")
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return an empty
+  // string:
+  //  GetOrEmpty("key_1", "key_2", "key_3")
+  template <typename... Args>
+  std::string GetOrEmpty(const std::string& key, Args... fallback_keys) const {
+    return Get(key, fallback_keys..., /*default_value=*/"");
+  }
+
+  // Returns the current value of the boolean system property `key`, or `default_value` if the
+  // property doesn't have a value. See `android::base::ParseBool` for how the value is parsed.
+  bool GetBool(const std::string& key, bool default_value) const {
+    android::base::ParseBoolResult result = android::base::ParseBool(GetProperty(key));
+    if (result != android::base::ParseBoolResult::kError) {
+      return result == android::base::ParseBoolResult::kTrue;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a bool
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return true:
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/true)
+  template <typename... Args>
+  bool GetBool(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return GetBool(key, GetBool(fallback_key, args...));
+  }
+
+ protected:
+  // The single source of truth of system properties. Can be mocked in unit tests.
+  virtual std::string GetProperty(const std::string& key) const {
+    return android::base::GetProperty(key, /*default_value=*/"");
+  }
+};
+
+}  // namespace tools
+}  // namespace art
+
+#endif  // ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
diff --git a/libarttools/tools/system_properties_test.cc b/libarttools/tools/system_properties_test.cc
new file mode 100644
index 0000000..80300f0
--- /dev/null
+++ b/libarttools/tools/system_properties_test.cc
@@ -0,0 +1,97 @@
+/*
+ * 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 "system_properties.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::Return;
+
+class MockSystemProperties : public SystemProperties {
+ public:
+  MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class SystemPropertiesTest : public testing::Test {
+ protected:
+  MockSystemProperties system_properties_;
+};
+
+TEST_F(SystemPropertiesTest, Get) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.Get("key_1", "key_2", "key_3", /*default_value=*/"default"),
+            "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "default");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmpty) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1", "key_2", "key_3"), "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "");
+}
+
+TEST_F(SystemPropertiesTest, GetBoolTrue) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("true"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolFalse) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), false);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("true"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", "key_2", "key_3", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), true);
+}
+
+}  // namespace
+}  // namespace tools
+}  // namespace art
diff --git a/libprofile/profile/profile_boot_info_test.cc b/libprofile/profile/profile_boot_info_test.cc
index 9939f0a..3c5829c 100644
--- a/libprofile/profile/profile_boot_info_test.cc
+++ b/libprofile/profile/profile_boot_info_test.cc
@@ -66,6 +66,7 @@
   ScratchFile profile;
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files2;
+  dex_files2.reserve(dex_files.size());
   for (const std::unique_ptr<const DexFile>& file : dex_files) {
     dex_files2.push_back(file.get());
   }
@@ -104,6 +105,7 @@
   ProfileBootInfo loaded_info;
   std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex");
   std::vector<const DexFile*> dex_files2;
+  dex_files2.reserve(dex_files.size());
   for (const std::unique_ptr<const DexFile>& file : dex_files) {
     dex_files2.push_back(file.get());
   }
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index bb48713..83da564 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2388,8 +2388,8 @@
 }
 
 bool ProfileCompilationInfo::UpdateProfileKeys(
-    const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* updated) {
-  *updated = false;
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* matched) {
+  *matched = false;
   for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
     for (const std::unique_ptr<DexFileData>& dex_data : info_) {
       if (dex_data->checksum == dex_file->GetLocationChecksum() &&
@@ -2409,8 +2409,8 @@
           // form the old key.
           dex_data->profile_key = MigrateAnnotationInfo(new_profile_key, dex_data->profile_key);
           profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
-          *updated = true;
         }
+        *matched = true;
       }
     }
   }
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 76cbf9a..27902ad 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -646,9 +646,9 @@
   // If the new profile key would collide with an existing key (for a different dex)
   // the method returns false. Otherwise it returns true.
   //
-  // `updated` is set to true if any profile key has been updated by this method.
+  // `matched` is set to true if any profile has matched any input dex file.
   bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
-                         /*out*/ bool* updated);
+                         /*out*/ bool* matched);
 
   // Checks if the profile is empty.
   bool IsEmpty() const;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 2ee34f2..6d10467 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -956,9 +956,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files
-  bool updated = false;
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
-  ASSERT_TRUE(updated);
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -984,9 +984,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0, Hotness::kFlagHot, annotation);
 
   // Update the profile keys based on the original dex files
-  bool updated = false;
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
-  ASSERT_TRUE(updated);
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -1001,7 +1001,33 @@
   }
 }
 
-TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoUpdate) {
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkMatchedButNoUpdate) {
+  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
+
+  // Both the checksum and the location match the original dex file.
+  ProfileCompilationInfo info;
+  AddMethod(&info, dex1, /*method_idx=*/0);
+
+  // No update should happen, but this should be considered as a happy case.
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_TRUE(matched);
+
+  // Verify that we find the methods when searched with the original dex files.
+  for (const std::unique_ptr<const DexFile>& dex : dex_files) {
+    ProfileCompilationInfo::MethodHotness loaded_hotness =
+        GetMethod(info, dex.get(), /*method_idx=*/ 0);
+    ASSERT_TRUE(loaded_hotness.IsHot());
+  }
+
+  // Release the ownership as this is held by the test class;
+  for (std::unique_ptr<const DexFile>& dex : dex_files) {
+    UNUSED(dex.release());
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoMatch) {
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
 
@@ -1009,9 +1035,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files.
-  bool updated = false;
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
-  ASSERT_FALSE(updated);
+  bool matched = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_FALSE(matched);
 
   // Verify that we did not perform any update and that we cannot find anything with the new
   // location.
@@ -1043,9 +1069,9 @@
   // This will cause the rename to fail because an existing entry would already have that name.
   AddMethod(&info, dex1_renamed, /*method_idx=*/ 0);
 
-  bool updated = false;
-  ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &updated));
-  ASSERT_FALSE(updated);
+  bool matched = false;
+  ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &matched));
+  ASSERT_FALSE(matched);
 
   // Release the ownership as this is held by the test class;
   for (std::unique_ptr<const DexFile>& dex : dex_files) {
diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h
index 4d2b733..9c9aca9 100644
--- a/profman/include/profman/profman_result.h
+++ b/profman/include/profman/profman_result.h
@@ -57,7 +57,7 @@
   // The return codes of running profman with `--copy-and-update-profile-key`.
   enum CopyAndUpdateResult {
     kCopyAndUpdateSuccess = 0,
-    kCopyAndUpdateNoUpdate = 21,
+    kCopyAndUpdateNoMatch = 21,
     kCopyAndUpdateErrorFailedToUpdateProfile = 22,
     kCopyAndUpdateErrorFailedToSaveProfile = 23,
     kCopyAndUpdateErrorFailedToLoadProfile = 24,
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index 4fc8143..f7c4255 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -2068,8 +2068,8 @@
   argv_str.push_back("--copy-and-update-profile-key");
   std::string error;
 
-  // Must return kCopyAndUpdateNoUpdate.
-  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoUpdate) << error;
+  // Must return kCopyAndUpdateNoMatch.
+  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoMatch) << error;
 
   // Verify that the content is the same.
   std::string output_content;
diff --git a/profman/profman.cc b/profman/profman.cc
index c2b98a2..9406733 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -1895,8 +1895,8 @@
       // Open the dex files to look up classes and methods.
       std::vector<std::unique_ptr<const DexFile>> dex_files;
       OpenApkFilesFromLocations(&dex_files);
-      bool updated = false;
-      if (!profile.UpdateProfileKeys(dex_files, &updated)) {
+      bool matched = false;
+      if (!profile.UpdateProfileKeys(dex_files, &matched)) {
         return ProfmanResult::kCopyAndUpdateErrorFailedToUpdateProfile;
       }
       bool result = use_fds
@@ -1905,7 +1905,7 @@
       if (!result) {
         return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile;
       }
-      return updated ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoUpdate;
+      return matched ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoMatch;
     } else {
       return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile;
     }
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc
index dd389f8..1021429 100644
--- a/runtime/exec_utils.cc
+++ b/runtime/exec_utils.cc
@@ -68,6 +68,7 @@
   // Convert the args to char pointers.
   const char* program = arg_vector[0].c_str();
   std::vector<char*> args;
+  args.reserve(arg_vector.size() + 1);
   for (const auto& arg : arg_vector) {
     args.push_back(const_cast<char*>(arg.c_str()));
   }
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index d746ade..8466753 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -23,7 +23,6 @@
 #include <unistd.h>
 
 #include "android-base/strings.h"
-
 #include "art_method-inl.h"
 #include "base/compiler_filter.h"
 #include "base/enums.h"
@@ -32,6 +31,7 @@
 #include "base/stl_util.h"
 #include "base/systrace.h"
 #include "base/time_utils.h"
+#include "base/unix_file/fd_file.h"
 #include "class_table-inl.h"
 #include "dex/dex_file_loader.h"
 #include "dex_reference_collection.h"
@@ -871,10 +871,23 @@
     {
       ProfileCompilationInfo info(Runtime::Current()->GetArenaPool(),
                                   /*for_boot_image=*/ options_.GetProfileBootClassPath());
-      if (!info.Load(filename, /*clear_if_invalid=*/ true)) {
-        LOG(WARNING) << "Could not forcefully load profile " << filename;
-        continue;
+      if (OS::FileExists(filename.c_str())) {
+        if (!info.Load(filename, /*clear_if_invalid=*/true)) {
+          LOG(WARNING) << "Could not forcefully load profile " << filename;
+          continue;
+        }
+      } else {
+        // Create a file if it doesn't exist.
+        unix_file::FdFile file(filename.c_str(),
+                               O_WRONLY | O_TRUNC | O_CREAT,
+                               S_IRUSR | S_IWUSR,
+                               /*check_usage=*/false);
+        if (!file.IsValid()) {
+          LOG(WARNING) << "Could not create profile " << filename;
+          continue;
+        }
       }
+
       uint64_t last_save_number_of_methods = info.GetNumberOfMethods();
       uint64_t last_save_number_of_classes = info.GetNumberOfResolvedClasses();
       VLOG(profiler) << "last_save_number_of_methods=" << last_save_number_of_methods
diff --git a/runtime/jit/profiling_info_test.cc b/runtime/jit/profiling_info_test.cc
index ce0a30f..0bd21aa 100644
--- a/runtime/jit/profiling_info_test.cc
+++ b/runtime/jit/profiling_info_test.cc
@@ -72,6 +72,7 @@
       Hotness::Flag flags) {
     ProfileCompilationInfo info;
     std::vector<ProfileMethodInfo> profile_methods;
+    profile_methods.reserve(methods.size());
     ScopedObjectAccess soa(Thread::Current());
     for (ArtMethod* method : methods) {
       profile_methods.emplace_back(
diff --git a/runtime/metrics/statsd.cc b/runtime/metrics/statsd.cc
index 9a11530..7002f22 100644
--- a/runtime/metrics/statsd.cc
+++ b/runtime/metrics/statsd.cc
@@ -271,6 +271,9 @@
       return statsd::ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_CMDLINE;
     case CompilationReason::kVdex:
       return statsd::ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_VDEX;
+    case CompilationReason::kBootAfterMainlineUpdate:
+      return statsd::
+          ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE;
   }
 }
 
diff --git a/test/2005-pause-all-redefine-multithreaded/pause-all.cc b/test/2005-pause-all-redefine-multithreaded/pause-all.cc
index 9928411..37d6c4d 100644
--- a/test/2005-pause-all-redefine-multithreaded/pause-all.cc
+++ b/test/2005-pause-all-redefine-multithreaded/pause-all.cc
@@ -41,10 +41,12 @@
                                                     jobjectArray new_fields,
                                                     jstring default_val) {
   std::vector<jthread> threads;
+  threads.reserve(env->GetArrayLength(threads_arr));
   for (jint i = 0; i < env->GetArrayLength(threads_arr); i++) {
     threads.push_back(env->GetObjectArrayElement(threads_arr, i));
   }
   std::vector<jfieldID> fields;
+  fields.reserve(env->GetArrayLength(new_fields));
   for (jint i = 0; i < env->GetArrayLength(new_fields); i++) {
     fields.push_back(env->FromReflectedField(env->GetObjectArrayElement(new_fields, i)));
   }
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 5b1a448..37a8e6c 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -39,8 +39,12 @@
           File.class, Method.class);
       testAddMethodToProfile(file, appMethod);
 
+      // Delete the file to check that the runtime can save the profile even if the file doesn't
+      // exist.
+      file.delete();
+
       // Test that the profile saves a boot class path method with a profiling info.
-      Method bootMethod = File.class.getDeclaredMethod("delete");
+      Method bootMethod = File.class.getDeclaredMethod("exists");
       if (bootMethod.getDeclaringClass().getClassLoader() != Object.class.getClassLoader()) {
         System.out.println("Class loader does not match boot class");
       }
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 311b029..671cff8 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -266,6 +266,7 @@
 
     std::vector<std::string> GetLines() const {
       std::vector<std::string> ret;
+      ret.reserve(lines_.size());
       for (const std::unique_ptr<Elem>& e : lines_) {
         ret.push_back(e->Print());
       }
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 0baa9fe..706531e 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -392,6 +392,7 @@
 static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) {
   std::vector<jclass> classes;
   jint len = env->GetArrayLength(targets);
+  classes.reserve(len);
   for (jint i = 0; i < len; i++) {
     classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
   }
diff --git a/test/ti-agent/suspension_helper.cc b/test/ti-agent/suspension_helper.cc
index b685cb2..2b1ab67 100644
--- a/test/ti-agent/suspension_helper.cc
+++ b/test/ti-agent/suspension_helper.cc
@@ -37,6 +37,7 @@
 static std::vector<jthread> CopyToVector(JNIEnv* env, jobjectArray thrs) {
   jsize len = env->GetArrayLength(thrs);
   std::vector<jthread> ret;
+  ret.reserve(len);
   for (jsize i = 0; i < len; i++) {
     ret.push_back(reinterpret_cast<jthread>(env->GetObjectArrayElement(thrs, i)));
   }
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index 5b72479..2ab9317 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -226,12 +226,10 @@
     "art_standalone_artd_tests",
     "art_standalone_cmdline_tests",
     "art_standalone_compiler_tests",
-    # Temporarily disable this test as it is failing with ART module prebuilts (see b/243510263).
-    ### "art_standalone_dex2oat_tests",
+    "art_standalone_dex2oat_tests",
     "art_standalone_dexdump_tests",
     "art_standalone_dexlist_tests",
-    # Temporarily disable this test as it is failing with ART module prebuilts (see b/243507635).
-    ### "art_standalone_libartbase_tests",
+    "art_standalone_libartbase_tests",
     "art_standalone_libartpalette_tests",
     "art_standalone_libartservice_tests",
     "art_standalone_libarttools_tests",
diff --git a/tools/dexanalyze/dexanalyze_experiments.cc b/tools/dexanalyze/dexanalyze_experiments.cc
index b124f43..384ab37 100644
--- a/tools/dexanalyze/dexanalyze_experiments.cc
+++ b/tools/dexanalyze/dexanalyze_experiments.cc
@@ -459,6 +459,7 @@
     }
     // Count uses of top 16n.
     std::vector<size_t> uses;
+    uses.reserve(types_accessed.size());
     for (auto&& p : types_accessed) {
       uses.push_back(p.second);
     }