Remove HWASanUntag am: 7cb6c665ac am: 26fd2d7669 am: d1fcb83662

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

Change-Id: I8b029313d07cde601be174be901553d4379b345c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 38ff5ed..d9d5431 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1351,6 +1351,9 @@
       ]
     },
     {
+      "name": "art_standalone_dex2oat_tests[com.google.android.art.apex]"
+    },
+    {
       "name": "art_standalone_dexdump_tests[com.google.android.art.apex]"
     },
     {
@@ -1360,6 +1363,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]",
       "options": [
         {
@@ -2765,6 +2771,9 @@
       "name": "art_standalone_compiler_tests"
     },
     {
+      "name": "art_standalone_dex2oat_tests"
+    },
+    {
       "name": "art_standalone_dexdump_tests"
     },
     {
@@ -2774,6 +2783,9 @@
       "name": "art_standalone_dexoptanalyzer_tests"
     },
     {
+      "name": "art_standalone_libartbase_tests"
+    },
+    {
       "name": "art_standalone_libartpalette_tests"
     },
     {
@@ -4168,6 +4180,9 @@
       "name": "art_standalone_compiler_tests"
     },
     {
+      "name": "art_standalone_dex2oat_tests"
+    },
+    {
       "name": "art_standalone_dexdump_tests"
     },
     {
@@ -4177,6 +4192,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 4c551c9..1288ebc 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -27,11 +27,18 @@
     defaults: ["art_defaults"],
     srcs: [
         "artd.cc",
+        "file_utils.cc",
+        "path_utils.cc",
+    ],
+    header_libs: [
+        "art_cmdlineparser_headers",
+        "profman_headers",
     ],
     shared_libs: [
         "libarttools", // Contains "libc++fs".
         "libbase",
         "libbinder_ndk",
+        "libselinux",
     ],
     static_libs: [
         "artd-aidl-ndk",
@@ -45,6 +52,7 @@
         "artd_main.cc",
     ],
     shared_libs: [
+        "libart",
         "libartbase",
     ],
     apex_available: [
@@ -56,8 +64,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",
     ],
 }
 
@@ -83,4 +100,5 @@
         "art_standalone_gtest_defaults",
         "art_artd_tests_defaults",
     ],
+    test_config_template: "art_standalone_artd_tests.xml",
 }
diff --git a/artd/README.md b/artd/README.md
new file mode 100644
index 0000000..a2dfc09
--- /dev/null
+++ b/artd/README.md
@@ -0,0 +1,16 @@
+## artd
+
+artd is a component of ART Service. It is a shim service to do tasks that
+require elevated permissions that are not available to system_server, such as
+manipulation of the file system and invoking dex2oat. It publishes a binder
+interface that is internal to ART service's Java code. When it invokes other
+binaries, it passes input and output files as FDs and drops capability before
+exec.
+
+### System properties
+
+artd can be controlled by the system properties listed below. Note that the list
+doesn't include options passed to dex2oat and other processes.
+
+- `dalvik.vm.artd-verbose`: Log verbosity of the artd process. The syntax is the
+  same as the runtime's `-verbose` flag.
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..afed965 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -16,37 +16,1079 @@
 
 #include "artd.h"
 
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/statfs.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 <unordered_set>
+#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/logging.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "cmdline_types.h"
+#include "exec_utils.h"
+#include "file_utils.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"
 
+#ifdef __BIONIC__
+#include <linux/incrementalfs.h>
+#endif
+
 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::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 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) << ART_FORMAT("Failed to get the file size of '{}': {}", 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) << ART_FORMAT("Failed to remove '{}': {}", 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;
+  }
+  if ((aidl_value & static_cast<int32_t>(DexoptTrigger::NEED_EXTRACTION)) != 0) {
+    trigger.needExtraction = 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 {};
+}
+
+Result<void> SetLogVerbosity() {
+  std::string options = android::base::GetProperty("dalvik.vm.artd-verbose", /*default_value=*/"");
+  if (options.empty()) {
+    return {};
+  }
+
+  CmdlineType<LogVerbosity> parser;
+  CmdlineParseResult<LogVerbosity> result = parser.Parse(options);
+  if (!result.IsSuccess()) {
+    return Error() << result.GetMessage();
+  }
+
+  gLogVerbosity = result.ReleaseValue();
+  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::optional<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,
+                                                     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);
+  }
+
+  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(
+        ART_FORMAT("Failed to open profile '{}': {}", 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) << ART_FORMAT("profman returned code {}", result.value());
+
+  if (result.value() != ProfmanResult::kSkipCompilationSmallDelta &&
+      result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) {
+    return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", 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(
+        ART_FORMAT("Failed to open src profile '{}': {}", 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) << ART_FORMAT("profman returned code {}", result.value());
+
+  if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch) {
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+
+  if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) {
+    return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", 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(ART_FORMAT(
+        "Failed to move '{}' to '{}': {}", 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;
+  std::filesystem::remove(profile_path, ec);
+  if (ec) {
+    LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", 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(ART_FORMAT("Does not support DM file, got '{}'", 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(ART_FORMAT(
+          "Failed to open profile '{}': {}", 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(ART_FORMAT("Does not support DM file, got '{}'", 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) << ART_FORMAT("profman returned code {}", 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(ART_FORMAT("profman returned an unexpected code: {}", 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.value());
+    if (context == nullptr) {
+      return Fatal(
+          ART_FORMAT("Class loader context '{}' is invalid", 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(ART_FORMAT(
+          "Outputs cannot be other-readable because the dex file '{}' is not other-readable",
+          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(ART_FORMAT(
+          "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})",
+          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()) {
+    std::string swap_file_path = ART_FORMAT("{}.swap", oat_path);
+    swap_file =
+        OR_RETURN_NON_FATAL(NewFile::Create(swap_file_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);
+    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(ART_FORMAT(
+          "Outputs cannot be other-readable because the profile '{}' is not other-readable",
+          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);
+
+  // For being surfaced in crash reports on crashes.
+  args.Add("--comments=%s", in_dexoptOptions.comments);
+
+  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) << ART_FORMAT("dex2oat returned code {}", result.value());
+
+  if (result.value() != 0) {
+    return NonFatal(ART_FORMAT("dex2oat returned an unexpected code: {}", 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();
+}
+
+ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep,
+                            const std::vector<ArtifactsPath>& in_artifactsToKeep,
+                            const std::vector<VdexPath>& in_vdexFilesToKeep,
+                            int64_t* _aidl_return) {
+  std::unordered_set<std::string> files_to_keep;
+  for (const ProfilePath& profile : in_profilesToKeep) {
+    files_to_keep.insert(OR_RETURN_FATAL(BuildProfileOrDmPath(profile)));
+  }
+  for (const ArtifactsPath& artifacts : in_artifactsToKeep) {
+    std::string oat_path = OR_RETURN_FATAL(BuildOatPath(artifacts));
+    files_to_keep.insert(OatPathToVdexPath(oat_path));
+    files_to_keep.insert(OatPathToArtPath(oat_path));
+    files_to_keep.insert(std::move(oat_path));
+  }
+  for (const VdexPath& vdex : in_vdexFilesToKeep) {
+    files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex)));
+  }
+  *_aidl_return = 0;
+  for (const std::string& file : OR_RETURN_NON_FATAL(ListManagedFiles())) {
+    if (files_to_keep.find(file) == files_to_keep.end()) {
+      LOG(INFO) << ART_FORMAT("Cleaning up obsolete file '{}'", file);
+      *_aidl_return += GetSizeAndDeleteFile(file);
+    }
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::isIncrementalFsPath(const std::string& in_dexFile [[maybe_unused]],
+                                        bool* _aidl_return) {
+#ifdef __BIONIC__
+  OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+  struct statfs st;
+  if (statfs(in_dexFile.c_str(), &st) != 0) {
+    PLOG(ERROR) << ART_FORMAT("Failed to statfs '{}'", in_dexFile);
+    *_aidl_return = false;
+    return ScopedAStatus::ok();
+  }
+  *_aidl_return = st.f_type == INCFS_MAGIC_NUMBER;
+  return ScopedAStatus::ok();
+#else
+  *_aidl_return = false;
+  return ScopedAStatus::ok();
+#endif
+}
+
 Result<void> Artd::Start() {
+  OR_RETURN(SetLogVerbosity());
+
   ScopedAStatus status = ScopedAStatus::fromStatus(
       AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
   if (!status.isOk()) {
@@ -58,5 +1100,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 = ART_FORMAT("dalvik.vm.isa.{}.features", instruction_set);
+  args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop));
+  std::string variant_prop = ART_FORMAT("dalvik.vm.isa.{}.variant", 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..f90110d 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -17,18 +17,219 @@
 #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::optional<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;
+
+  ndk::ScopedAStatus cleanup(
+      const std::vector<aidl::com::android::server::art::ProfilePath>& in_profilesToKeep,
+      const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifactsToKeep,
+      const std::vector<aidl::com::android::server::art::VdexPath>& in_vdexFilesToKeep,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus isIncrementalFsPath(const std::string& in_dexFile,
+                                         bool* _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..44ddae9 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -16,34 +16,1943 @@
 
 #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/ArtConstants.h"
+#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 "base/macros.h"
+#include "exec_utils.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "oat_file.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::ArtConstants;
+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;
+
+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 = ART_FORMAT("/proc/self/fd/{}", 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);
+
+    // Use an arbitrary existing directory as Android expand.
+    android_expand_ = scratch_path_ + "/mnt/expand";
+    std::filesystem::create_directories(android_expand_);
+    setenv("ANDROID_EXPAND", android_expand_.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_ = ART_FORMAT("PCL[{}:{}]", 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),
+              std::move(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_;
+  std::string android_expand_;
+  MockFunction<android::base::LogFunction> mock_logger_;
+  ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+  ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
+  ScopedUnsetEnvironmentVariable android_expand_env_ =
+      ScopedUnsetEnvironmentVariable("ANDROID_EXPAND");
+  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, ConstantsAreInSync) { EXPECT_EQ(ArtConstants::REASON_VDEX, kReasonVdex); }
+
 TEST_F(ArtdTest, isAlive) {
   bool result = false;
   artd_->isAlive(&result);
   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,
+      .comments = "my-comments",
+  };
+
+  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:", _))),
+                                            Contains(Flag("--comments=", "my-comments")))),
+                          _,
+                          _))
+      .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) {
+  auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+  EXPECT_CALL(mock_logger_, Call).Times(0);
+
+  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")));
+
+  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+  auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path());
+  auto scoped_unroot = ScopedUnroot();
+
+  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");
+}
+
+TEST_F(ArtdTest, cleanup) {
+  std::vector<std::string> gc_removed_files;
+  std::vector<std::string> gc_kept_files;
+
+  auto CreateGcRemovedFile = [&](const std::string& path) {
+    CreateFile(path);
+    gc_removed_files.push_back(path);
+  };
+
+  auto CreateGcKeptFile = [&](const std::string& path) {
+    CreateFile(path);
+    gc_kept_files.push_back(path);
+  };
+
+  // Unmanaged files.
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.txt");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.txt");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.tmp");
+
+  // Files to keep.
+  CreateGcKeptFile(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof");
+  CreateGcKeptFile(android_data_ + "/misc/profiles/cur/3/com.android.foo/primary.prof");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex");
+  CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.vdex");
+  CreateGcKeptFile(
+      android_expand_ +
+      "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.art");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.vdex");
+  CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.art");
+
+  // Files to remove.
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/2/com.android.foo/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/3/com.android.bar/primary.prof");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/extra.odex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.dex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.vdex");
+  CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.art");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.odex");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.vdex");
+  CreateGcRemovedFile(
+      android_expand_ +
+      "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.vdex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/2.odex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.odex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.vdex");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art");
+  CreateGcRemovedFile(android_data_ +
+                      "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art.123456.tmp");
+  CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.bar/aaa/oat/arm64/1.vdex");
+
+  int64_t aidl_return;
+  ASSERT_TRUE(
+      artd_
+          ->cleanup(
+              {
+                  PrimaryCurProfilePath{
+                      .userId = 1, .packageName = "com.android.foo", .profileName = "primary"},
+                  PrimaryCurProfilePath{
+                      .userId = 3, .packageName = "com.android.foo", .profileName = "primary"},
+              },
+              {
+                  ArtifactsPath{.dexPath = "/system/app/Foo/Foo.apk",
+                                .isa = "arm64",
+                                .isInDalvikCache = true},
+                  ArtifactsPath{
+                      .dexPath =
+                          android_expand_ +
+                          "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/base.apk",
+                      .isa = "arm64",
+                      .isInDalvikCache = false},
+                  ArtifactsPath{.dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/2.apk",
+                                .isa = "arm64",
+                                .isInDalvikCache = false},
+              },
+              {
+                  VdexPath{ArtifactsPath{
+                      .dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/1.apk",
+                      .isa = "arm64",
+                      .isInDalvikCache = false}},
+              },
+              &aidl_return)
+          .isOk());
+
+  for (const std::string& path : gc_removed_files) {
+    EXPECT_FALSE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be removed", path);
+  }
+
+  for (const std::string& path : gc_kept_files) {
+    EXPECT_TRUE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be kept", path);
+  }
+}
+
 }  // 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/ArtConstants.aidl b/artd/binder/com/android/server/art/ArtConstants.aidl
new file mode 100644
index 0000000..e9f702e
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtConstants.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Constants used by ART Service Java code that must be kept in sync with those in ART native code.
+ *
+ * @hide
+ */
+parcelable ArtConstants {
+    /**
+     * A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with
+     * {@code kReasonVdex} in art/runtime/oat_file.h.
+     *
+     * This isn't a valid reason to feed into DexoptParams.
+     */
+    const @utf8InCpp String REASON_VDEX = "vdex";
+}
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..305445e
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptOptions.aidl
@@ -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;
+
+/**
+ * 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;
+    /** --comments */
+    @utf8InCpp String comments;
+}
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..79621a9
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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,
+    NEED_EXTRACTION = 1 << 4,
+}
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..a130e96 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -16,8 +16,164 @@
 
 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,
+            @nullable @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();
+
+    /**
+     * Deletes all files that are managed by artd, except those specified in the arguments. Returns
+     * the size of the freed space, in bytes.
+     *
+     * For each entry in `artifactsToKeep`, all three kinds of artifacts (ODEX, VDEX, ART) are
+     * kept. For each entry in `vdexFilesToKeep`, only the VDEX file will be kept. Note that VDEX
+     * files included in `artifactsToKeep` don't have to be listed in `vdexFilesToKeep`.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long cleanup(in List<com.android.server.art.ProfilePath> profilesToKeep,
+            in List<com.android.server.art.ArtifactsPath> artifactsToKeep,
+            in List<com.android.server.art.VdexPath> vdexFilesToKeep);
+
+    /**
+     * Returns whether the dex file is in Incremental FS.
+     *
+     * Throws fatal errors. On non-fatal errors, logs the error and returns false.
+     */
+    boolean isIncrementalFsPath(@utf8InCpp String dexFile);
 }
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..f355853
--- /dev/null
+++ b/artd/file_utils.cc
@@ -0,0 +1,244 @@
+/*
+ * 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/macros.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::make_scope_guard;
+using ::android::base::Result;
+
+void UnlinkIfExists(const std::string& path) {
+  std::error_code ec;
+  std::filesystem::remove(path, ec);
+  if (ec) {
+    LOG(WARNING) << ART_FORMAT("Failed to remove file '{}': {}", 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) {
+    // If this fails because the temp file doesn't exist, it could be that the file is deleted by
+    // `Artd::cleanup` if that method is run simultaneously. At the time of writing, this should
+    // never happen because `Artd::cleanup` is only called at the end of the backgrond dexopt job.
+    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) << ART_FORMAT("Failed to move old file '{}' back from temporary path '{}': {}",
+                                   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 ART_FORMAT("{}.{}.tmp", 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..d504bb2
--- /dev/null
+++ b/artd/path_utils.cc
@@ -0,0 +1,282 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#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 "base/macros.h"
+#include "file_utils.h"
+#include "oat_file_assistant.h"
+#include "tools/tools.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::Error;
+using ::android::base::Result;
+
+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> GetAndroidExpandOrError() {
+  std::string error_msg;
+  std::string result = GetAndroidExpandSafe(&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<std::vector<std::string>> ListManagedFiles() {
+  std::string android_data = OR_RETURN(GetAndroidDataOrError());
+  std::string android_expand = OR_RETURN(GetAndroidExpandOrError());
+
+  // See `art::tools::Glob` for the syntax.
+  std::vector<std::string> patterns = {
+      // Profiles for primary dex files.
+      android_data + "/misc/profiles/**",
+      // Artifacts for primary dex files.
+      android_data + "/dalvik-cache/**",
+  };
+
+  for (const std::string& data_root : {android_data, android_expand + "/*"}) {
+    // Artifacts for primary dex files.
+    patterns.push_back(data_root + "/app/*/*/oat/**");
+    // Profiles and artifacts for secondary dex files. Those files are in app data directories, so
+    // we use more granular patterns to avoid accidentally deleting apps' files.
+    for (const char* user_dir : {"/user", "/user_de"}) {
+      std::string secondary_oat_dir = data_root + user_dir + "/*/*/**/oat";
+      for (const char* maybe_tmp_suffix : {"", ".*.tmp"}) {
+        patterns.push_back(secondary_oat_dir + "/*.prof" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.odex" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.vdex" + maybe_tmp_suffix);
+        patterns.push_back(secondary_oat_dir + "/*/*.art" + maybe_tmp_suffix);
+      }
+    }
+  }
+
+  return tools::Glob(patterns);
+}
+
+Result<void> ValidateDexPath(const std::string& dex_path) {
+  OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+  return {};
+}
+
+Result<std::string> BuildArtBinPath(const std::string& binary_name) {
+  return ART_FORMAT("{}/bin/{}", 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 ART_FORMAT("{}/misc/profiles/ref/{}/{}.prof",
+                    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 ART_FORMAT("{}/misc/profiles/cur/{}/{}/{}.prof",
+                    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 ART_FORMAT(
+      "{}/oat/{}.prof", 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 ART_FORMAT(
+      "{}/oat/{}.cur.prof", 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) << ART_FORMAT("Unexpected writable profile path type {}", 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) << ART_FORMAT("Unexpected profile path type {}", 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..1063f91
--- /dev/null
+++ b/artd/path_utils.h
@@ -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.
+ */
+
+#ifndef ART_ARTD_PATH_UTILS_H_
+#define ART_ARTD_PATH_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace artd {
+
+// Returns all existing files that are managed by artd.
+android::base::Result<std::vector<std::string>> ListManagedFiles();
+
+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..77652f0
--- /dev/null
+++ b/artd/path_utils_test.cc
@@ -0,0 +1,263 @@
+/*
+ * 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 ::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, 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/apex/Android.bp b/build/apex/Android.bp
index 1fe46ee..d5349b6 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -279,6 +279,7 @@
     ],
     binaries: [
         "art_boot",
+        "art_exec",
         "artd",
     ],
     multilib: {
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 891fd0f..c2549cc 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -551,6 +551,7 @@
 
     # Check binaries for ART.
     self._checker.check_executable('art_boot')
+    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..a2828fe 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
@@ -34,7 +35,7 @@
 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;->gettid()I
+HSPLandroid/system/Os;->gettid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLandroid/system/Os;->getuid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLandroid/system/Os;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
 HSPLandroid/system/Os;->ioctlInt(Ljava/io/FileDescriptor;I)I
@@ -140,12 +141,12 @@
 HSPLcom/android/okhttp/Headers$Builder;->build()Lcom/android/okhttp/Headers;
 HSPLcom/android/okhttp/Headers$Builder;->checkNameAndValue(Ljava/lang/String;Ljava/lang/String;)V
 HSPLcom/android/okhttp/Headers$Builder;->get(Ljava/lang/String;)Ljava/lang/String;
-HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;
+HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/List;Ljava/util/ArrayList;
 HSPLcom/android/okhttp/Headers$Builder;->set(Ljava/lang/String;Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;
 HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;)V
 HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;Lcom/android/okhttp/Headers$1;)V
 HSPLcom/android/okhttp/Headers;->get(Ljava/lang/String;)Ljava/lang/String;
-HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/Headers;->name(I)Ljava/lang/String;
 HSPLcom/android/okhttp/Headers;->newBuilder()Lcom/android/okhttp/Headers$Builder;
 HSPLcom/android/okhttp/Headers;->size()I
@@ -181,7 +182,7 @@
 HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;)V
 HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;Lcom/android/okhttp/HttpUrl$1;)V
 HSPLcom/android/okhttp/HttpUrl;->access$200(Ljava/lang/String;IILjava/lang/String;)I
-HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String;
+HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;Ljava/lang/String;ZZZZ)Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->decodeHexDigit(C)I
 HSPLcom/android/okhttp/HttpUrl;->defaultPort(Ljava/lang/String;)I
@@ -201,7 +202,7 @@
 HSPLcom/android/okhttp/HttpUrl;->newBuilder()Lcom/android/okhttp/HttpUrl$Builder;
 HSPLcom/android/okhttp/HttpUrl;->pathSegmentsToString(Ljava/lang/StringBuilder;Ljava/util/List;)V
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Lcom/android/okhttp/okio/Buffer;Ljava/lang/String;IIZ)V
-HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String;
+HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;Z)Ljava/lang/String;
 HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/util/List;Z)Ljava/util/List;
 HSPLcom/android/okhttp/HttpUrl;->port()I
@@ -470,8 +471,8 @@
 HSPLcom/android/okhttp/internal/http/HttpEngine;->writingRequestHeaders()V
 HSPLcom/android/okhttp/internal/http/HttpMethod;->permitsRequestBody(Ljava/lang/String;)Z
 HSPLcom/android/okhttp/internal/http/HttpMethod;->requiresRequestBody(Ljava/lang/String;)Z
-HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
-HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I
+HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Lcom/android/okhttp/internal/http/OkHeaders$1;Lcom/android/okhttp/internal/http/OkHeaders$1;
+HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I+]Ljava/util/Comparator;Ljava/lang/String$CaseInsensitiveComparator;
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Headers;)J
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Request;)J
 HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Response;)J
@@ -681,17 +682,17 @@
 HSPLcom/android/okhttp/okio/Buffer;->readShort()S
 HSPLcom/android/okhttp/okio/Buffer;->readString(JLjava/nio/charset/Charset;)Ljava/lang/String;
 HSPLcom/android/okhttp/okio/Buffer;->readUtf8()Ljava/lang/String;
-HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String;
-HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String;
+HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
+HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->size()J
-HSPLcom/android/okhttp/okio/Buffer;->skip(J)V
+HSPLcom/android/okhttp/okio/Buffer;->skip(J)V+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment;
 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([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;
 HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;)Lcom/android/okhttp/okio/Buffer;
-HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer;
+HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/Buffer;->writeUtf8CodePoint(I)Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/ByteString;-><init>([B)V
 HSPLcom/android/okhttp/okio/ByteString;->hex()Ljava/lang/String;
@@ -756,15 +757,15 @@
 HSPLcom/android/okhttp/okio/RealBufferedSource;->buffer()Lcom/android/okhttp/okio/Buffer;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->close()V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->exhausted()Z
-HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J
-HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J+]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
+HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)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;->inputStream()Ljava/io/InputStream;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J
-HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J
+HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readIntLe()I
 HSPLcom/android/okhttp/okio/RealBufferedSource;->readShort()S
-HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String;
-HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z
+HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource;
+HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z+]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2;
 HSPLcom/android/okhttp/okio/RealBufferedSource;->require(J)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->skip(J)V
 HSPLcom/android/okhttp/okio/RealBufferedSource;->timeout()Lcom/android/okhttp/okio/Timeout;
@@ -805,10 +806,10 @@
 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
@@ -880,7 +881,7 @@
 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;->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;
@@ -1062,10 +1063,10 @@
 HSPLcom/android/org/kxml2/io/KXmlParser;->read([C)V
 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;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Map;Ljava/util/HashMap;
+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
@@ -1111,13 +1112,13 @@
 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;->open(Ljava/lang/String;)V+]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
 HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLdalvik/system/CloseGuard;->setEnabled(Z)V
 HSPLdalvik/system/CloseGuard;->setReporter(Ldalvik/system/CloseGuard$Reporter;)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,10 @@
 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$Callback;->onZipEntryAccess(Ljava/lang/String;)V
+HSPLdalvik/system/ZipPathValidator;->clearCallback()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,25 +1213,24 @@
 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
 HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;I)V
-HSPLjava/io/BufferedOutputStream;->flush()V
-HSPLjava/io/BufferedOutputStream;->flushBuffer()V
+HSPLjava/io/BufferedOutputStream;->flush()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream;
+HSPLjava/io/BufferedOutputStream;->flushBuffer()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream;
 HSPLjava/io/BufferedOutputStream;->write(I)V
 HSPLjava/io/BufferedOutputStream;->write([BII)V
 HSPLjava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
@@ -1247,7 +1253,7 @@
 HSPLjava/io/BufferedWriter;->newLine()V
 HSPLjava/io/BufferedWriter;->write(I)V
 HSPLjava/io/BufferedWriter;->write(Ljava/lang/String;II)V
-HSPLjava/io/BufferedWriter;->write([CII)V
+HSPLjava/io/BufferedWriter;->write([CII)V+]Ljava/io/BufferedWriter;Ljava/io/BufferedWriter;
 HSPLjava/io/ByteArrayInputStream;-><init>([B)V
 HSPLjava/io/ByteArrayInputStream;-><init>([BII)V
 HSPLjava/io/ByteArrayInputStream;->available()I
@@ -1285,23 +1291,23 @@
 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;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/ObjectInputStream$BlockDataInputStream;
 HSPLjava/io/DataInputStream;->readInt()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
 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
 HSPLjava/io/DataOutputStream;->incCount(I)V
 HSPLjava/io/DataOutputStream;->write(I)V
-HSPLjava/io/DataOutputStream;->write([BII)V
+HSPLjava/io/DataOutputStream;->write([BII)V+]Ljava/io/OutputStream;missing_types
 HSPLjava/io/DataOutputStream;->writeBoolean(Z)V
 HSPLjava/io/DataOutputStream;->writeByte(I)V
-HSPLjava/io/DataOutputStream;->writeInt(I)V
+HSPLjava/io/DataOutputStream;->writeInt(I)V+]Ljava/io/OutputStream;missing_types
 HSPLjava/io/DataOutputStream;->writeLong(J)V
 HSPLjava/io/DataOutputStream;->writeShort(I)V
 HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;)V
@@ -1311,14 +1317,14 @@
 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;
@@ -1331,19 +1337,19 @@
 HSPLjava/io/File;->getCanonicalPath()Ljava/lang/String;
 HSPLjava/io/File;->getFreeSpace()J
 HSPLjava/io/File;->getName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/io/File;->getParent()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/io/File;->getParentFile()Ljava/io/File;+]Ljava/io/File;Ljava/io/File;
+HSPLjava/io/File;->getParent()Ljava/lang/String;
+HSPLjava/io/File;->getParentFile()Ljava/io/File;
 HSPLjava/io/File;->getPath()Ljava/lang/String;
 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;->isDirectory()Z
 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;
@@ -1373,7 +1379,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,17 +1389,17 @@
 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;
@@ -1409,7 +1415,7 @@
 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/PushbackInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/FileInputStream;
 HSPLjava/io/FilterInputStream;->read([B)I
 HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/FilterInputStream;->reset()V
@@ -1441,15 +1447,15 @@
 HSPLjava/io/InterruptedIOException;-><init>()V
 HSPLjava/io/InterruptedIOException;-><init>(Ljava/lang/String;)V
 HSPLjava/io/ObjectInputStream$BlockDataInputStream;-><init>(Ljava/io/ObjectInputStream;Ljava/io/InputStream;)V
-HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V
 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;->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;->read([BIIZ)I
+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;->readFloat()F
@@ -1466,13 +1472,14 @@
 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
@@ -1482,62 +1489,62 @@
 HSPLjava/io/ObjectInputStream$HandleTable;->grow()V
 HSPLjava/io/ObjectInputStream$HandleTable;->lookupException(I)Ljava/lang/ClassNotFoundException;
 HSPLjava/io/ObjectInputStream$HandleTable;->lookupObject(I)Ljava/lang/Object;
-HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V
+HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V+]Ljava/io/ObjectInputStream$HandleTable$HandleList;Ljava/io/ObjectInputStream$HandleTable$HandleList;
 HSPLjava/io/ObjectInputStream$HandleTable;->setObject(ILjava/lang/Object;)V
 HSPLjava/io/ObjectInputStream$HandleTable;->size()I
 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;->close()V+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
 HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
 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;->clear()V+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;
 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;->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;->readInt()I
+HSPLjava/io/ObjectInputStream;->readInt()I+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 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;+]Ljava/io/ObjectInputStream;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;->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;->readShort()S
-HSPLjava/io/ObjectInputStream;->readStreamHeader()V
+HSPLjava/io/ObjectInputStream;->readStreamHeader()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 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;->readUTF()Ljava/lang/String;
+HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;
+HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
 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;->verifySubclass()V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;-><init>(Ljava/io/OutputStream;)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->close()V
-HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->drain()V
+HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->drain()V+]Ljava/io/OutputStream;Ljava/io/ByteArrayOutputStream;
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->flush()V
-HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->getUTFLength(Ljava/lang/String;)J
+HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->getUTFLength(Ljava/lang/String;)J+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->setBlockDataMode(Z)Z
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->warnIfClosed()V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->write([BIIZ)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBlockHeader(I)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeByte(I)V
-HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBytes(Ljava/lang/String;)V
+HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBytes(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeFloat(F)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeInt(I)V
 HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeLong(J)V
@@ -1567,7 +1574,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
@@ -1580,18 +1587,18 @@
 HSPLjava/io/ObjectOutputStream;->writeEnum(Ljava/lang/Enum;Ljava/io/ObjectStreamClass;Z)V
 HSPLjava/io/ObjectOutputStream;->writeFields()V
 HSPLjava/io/ObjectOutputStream;->writeFloat(F)V
-HSPLjava/io/ObjectOutputStream;->writeHandle(I)V
+HSPLjava/io/ObjectOutputStream;->writeHandle(I)V+]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
 HSPLjava/io/ObjectOutputStream;->writeInt(I)V
 HSPLjava/io/ObjectOutputStream;->writeLong(J)V
 HSPLjava/io/ObjectOutputStream;->writeNonProxyDesc(Ljava/io/ObjectStreamClass;Z)V
 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;->writeObject0(Ljava/lang/Object;Z)V+]Ljava/io/ObjectOutputStream$ReplaceTable;Ljava/io/ObjectOutputStream$ReplaceTable;]Ljava/lang/Object;megamorphic_types]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;]Ljava/lang/Class;Ljava/lang/Class;
+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
-HSPLjava/io/ObjectOutputStream;->writeString(Ljava/lang/String;Z)V
+HSPLjava/io/ObjectOutputStream;->writeString(Ljava/lang/String;Z)V+]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;
 HSPLjava/io/ObjectOutputStream;->writeTypeString(Ljava/lang/String;)V
 HSPLjava/io/ObjectOutputStream;->writeUTF(Ljava/lang/String;)V
 HSPLjava/io/ObjectStreamClass$1;-><init>(Ljava/io/ObjectStreamClass;)V
@@ -1623,7 +1630,7 @@
 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;->setPrimFieldValues(Ljava/lang/Object;[B)V
+HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V+]Lsun/misc/Unsafe;Lsun/misc/Unsafe;
 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;->hashCode()I
@@ -1666,7 +1673,7 @@
 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,7 +1684,7 @@
 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;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;
 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;
@@ -1689,7 +1696,7 @@
 HSPLjava/io/ObjectStreamClass;->hasWriteObjectData()Z
 HSPLjava/io/ObjectStreamClass;->hasWriteObjectMethod()Z
 HSPLjava/io/ObjectStreamClass;->hasWriteReplaceMethod()Z
-HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/io/ObjectStreamClass;->invokeReadObject(Ljava/lang/Object;Ljava/io/ObjectInputStream;)V
 HSPLjava/io/ObjectStreamClass;->invokeReadResolve(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/io/ObjectStreamClass;->invokeWriteObject(Ljava/lang/Object;Ljava/io/ObjectOutputStream;)V
@@ -1703,7 +1710,7 @@
 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+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;
 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
@@ -1748,25 +1755,25 @@
 HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;)V
 HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;Z)V
 HSPLjava/io/PrintWriter;->append(C)Ljava/io/PrintWriter;
-HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;
+HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;+]Ljava/io/PrintWriter;Lcom/android/internal/util/FastPrintWriter;
 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;->newLine()V
+HSPLjava/io/PrintWriter;->newLine()V+]Ljava/io/Writer;missing_types
 HSPLjava/io/PrintWriter;->print(C)V
 HSPLjava/io/PrintWriter;->print(I)V
 HSPLjava/io/PrintWriter;->print(J)V
-HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->print(Z)V
 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/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 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;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter;
 HSPLjava/io/PrintWriter;->write(Ljava/lang/String;II)V
 HSPLjava/io/PrintWriter;->write([CII)V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
 HSPLjava/io/PushbackInputStream;-><init>(Ljava/io/InputStream;I)V
@@ -1797,7 +1804,7 @@
 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
@@ -1824,33 +1831,33 @@
 HSPLjava/io/StringReader;->close()V
 HSPLjava/io/StringReader;->ensureOpen()V
 HSPLjava/io/StringReader;->read()I
-HSPLjava/io/StringReader;->read([CII)I
+HSPLjava/io/StringReader;->read([CII)I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/io/StringWriter;-><init>()V
 HSPLjava/io/StringWriter;-><init>(I)V
 HSPLjava/io/StringWriter;->append(C)Ljava/io/StringWriter;
 HSPLjava/io/StringWriter;->append(C)Ljava/io/Writer;
-HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter;
-HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;
+HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter;+]Ljava/io/StringWriter;Ljava/io/StringWriter;
+HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;+]Ljava/io/StringWriter;Ljava/io/StringWriter;
 HSPLjava/io/StringWriter;->close()V
 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(Ljava/lang/String;II)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+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 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;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]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;->normalize(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
@@ -1866,49 +1873,58 @@
 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(J)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;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 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/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/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;,Landroid/icu/impl/FormattedStringBuilder;,Ljava/nio/HeapCharBuffer;
+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;->appendCodePoint(I)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/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;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/AbstractStringBuilder;->appendNull()Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->charAt(I)C
+HSPLjava/lang/AbstractStringBuilder;->charAt(I)C+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;
+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(IC)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 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;
+HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/ArrayIndexOutOfBoundsException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Boolean;-><init>(Z)V
 HSPLjava/lang/Boolean;->booleanValue()Z
 HSPLjava/lang/Boolean;->compare(ZZ)I
 HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Boolean;)I
 HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Object;)I
-HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z
+HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Boolean;Ljava/lang/Boolean;
 HSPLjava/lang/Boolean;->getBoolean(Ljava/lang/String;)Z
 HSPLjava/lang/Boolean;->hashCode()I
 HSPLjava/lang/Boolean;->hashCode(Z)I
@@ -1944,10 +1960,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
@@ -2005,12 +2021,12 @@
 HSPLjava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
 HSPLjava/lang/Class;->forName(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;
 HSPLjava/lang/Class;->getAccessFlags()I
-HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
+HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getCanonicalName()Ljava/lang/String;
 HSPLjava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;+]Ljava/lang/Class;Ljava/lang/Class;
 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;
+HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getConstructors()[Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getDeclaredConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
 HSPLjava/lang/Class;->getDeclaredConstructors()[Ljava/lang/reflect/Constructor;
@@ -2023,12 +2039,12 @@
 HSPLjava/lang/Class;->getField(Ljava/lang/String;)Ljava/lang/reflect/Field;
 HSPLjava/lang/Class;->getFields()[Ljava/lang/reflect/Field;
 HSPLjava/lang/Class;->getGenericInterfaces()[Ljava/lang/reflect/Type;
-HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;
+HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;+]Ljava/lang/Class;Ljava/lang/Class;
 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;->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;
@@ -2037,9 +2053,9 @@
 HSPLjava/lang/Class;->getPublicMethodRecursive(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
 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;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
-HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;
+HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;+]Ljava/lang/Class;Ljava/lang/Class;
 HSPLjava/lang/Class;->getTypeName()Ljava/lang/String;
 HSPLjava/lang/Class;->getTypeParameters()[Ljava/lang/reflect/TypeVariable;
 HSPLjava/lang/Class;->isAnnotation()Z
@@ -2051,7 +2067,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;
@@ -2083,13 +2099,13 @@
 HSPLjava/lang/Daemons$Daemon;->stop()V
 HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$fgetprogressCounter(Ljava/lang/Daemons$FinalizerDaemon;)Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerDaemon;
-HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;megamorphic_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference;
+HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;missing_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference;
 HSPLjava/lang/Daemons$FinalizerDaemon;->runInternal()V
 HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V
 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 +2118,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 +2132,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 +2154,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;missing_types
 HSPLjava/lang/Error;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Exception;-><init>()V
 HSPLjava/lang/Exception;-><init>(Ljava/lang/String;)V
@@ -2148,7 +2164,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
@@ -2176,13 +2192,14 @@
 HSPLjava/lang/InheritableThreadLocal;->childValue(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/lang/InheritableThreadLocal;->createMap(Ljava/lang/Thread;Ljava/lang/Object;)V
 HSPLjava/lang/InheritableThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap;
+HSPLjava/lang/InstantiationException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Integer;-><init>(I)V
 HSPLjava/lang/Integer;->bitCount(I)I
 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,18 +2241,18 @@
 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;missing_types]Ljava/util/Iterator;missing_types]Ljava/lang/Iterable;missing_types
 HSPLjava/lang/LinkageError;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/Long;-><init>(J)V
 HSPLjava/lang/Long;->bitCount(J)I
 HSPLjava/lang/Long;->compare(JJ)I
 HSPLjava/lang/Long;->compareTo(Ljava/lang/Long;)I
-HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I
+HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Long;Ljava/lang/Long;
 HSPLjava/lang/Long;->compareUnsigned(JJ)I
 HSPLjava/lang/Long;->decode(Ljava/lang/String;)Ljava/lang/Long;
 HSPLjava/lang/Long;->divideUnsigned(JJ)J
 HSPLjava/lang/Long;->doubleValue()D
-HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Long;Ljava/lang/Long;
+HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z
 HSPLjava/lang/Long;->formatUnsignedLong0(JI[BII)V
 HSPLjava/lang/Long;->getChars(JI[B)I
 HSPLjava/lang/Long;->getChars(JI[C)I
@@ -2249,6 +2266,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
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;)J
 HSPLjava/lang/Long;->parseLong(Ljava/lang/String;I)J
 HSPLjava/lang/Long;->reverse(J)J
@@ -2297,7 +2315,7 @@
 HSPLjava/lang/Math;->nextAfter(DD)D
 HSPLjava/lang/Math;->powerOfTwoD(I)D
 HSPLjava/lang/Math;->powerOfTwoF(I)F
-HSPLjava/lang/Math;->random()D
+HSPLjava/lang/Math;->random()D+]Ljava/util/Random;Ljava/util/Random;
 HSPLjava/lang/Math;->randomLongInternal()J
 HSPLjava/lang/Math;->round(D)J
 HSPLjava/lang/Math;->round(F)I
@@ -2318,6 +2336,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
@@ -2381,12 +2400,15 @@
 HSPLjava/lang/StackTraceElement;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/lang/StackTraceElement;->isNativeMethod()Z
 HSPLjava/lang/StackTraceElement;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement;
-HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/String$CaseInsensitiveComparator;Ljava/lang/String$CaseInsensitiveComparator;
 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 +2417,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;->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;->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+]Ljava/lang/String;Ljava/lang/String;
+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,8 +2431,7 @@
 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;
@@ -2418,14 +2440,14 @@
 HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
 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
 HSPLjava/lang/String;->regionMatches(ILjava/lang/String;II)Z
-HSPLjava/lang/String;->regionMatches(ZILjava/lang/String;II)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/String;->regionMatches(ZILjava/lang/String;II)Z
 HSPLjava/lang/String;->replace(CC)Ljava/lang/String;
-HSPLjava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
+HSPLjava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/CharSequence;Ljava/lang/String;
 HSPLjava/lang/String;->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/String;->replaceFirst(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String;
@@ -2435,7 +2457,7 @@
 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;
@@ -2446,7 +2468,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 +2477,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,10 +2490,11 @@
 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
@@ -2483,8 +2507,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 +2537,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 +2548,20 @@
 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;->checkBoundsBeginEnd(II[B)V
+HSPLjava/lang/StringUTF16;->checkBoundsOffCount(II[B)V
+HSPLjava/lang/StringUTF16;->getChar([BI)C
+HSPLjava/lang/StringUTF16;->getChars(II[B)I
+HSPLjava/lang/StringUTF16;->getChars([BII[CI)V
+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
@@ -2573,7 +2611,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
@@ -2581,6 +2619,7 @@
 HSPLjava/lang/Thread;->join(J)V
 HSPLjava/lang/Thread;->nextThreadID()J
 HSPLjava/lang/Thread;->nextThreadNum()I
+HSPLjava/lang/Thread;->onSpinWait()V
 HSPLjava/lang/Thread;->run()V
 HSPLjava/lang/Thread;->setContextClassLoader(Ljava/lang/ClassLoader;)V
 HSPLjava/lang/Thread;->setDaemon(Z)V
@@ -2604,7 +2643,7 @@
 HSPLjava/lang/ThreadGroup;->checkAccess()V
 HSPLjava/lang/ThreadGroup;->checkParentAccess(Ljava/lang/ThreadGroup;)Ljava/lang/Void;
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;)I
-HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I
+HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I+]Ljava/lang/Thread;missing_types
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;)I
 HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;IZ)I
 HSPLjava/lang/ThreadGroup;->getMaxPriority()I
@@ -2621,13 +2660,13 @@
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;-><init>(Ljava/lang/ThreadLocal;Ljava/lang/Object;)V
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->cleanSomeSlots(II)Z
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntries()V
-HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I
+HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I+]Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntry(Ljava/lang/ThreadLocal;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntryAfterMiss(Ljava/lang/ThreadLocal;ILjava/lang/ThreadLocal$ThreadLocalMap$Entry;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->nextIndex(II)I
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->prevIndex(II)I
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->rehash()V
-HSPLjava/lang/ThreadLocal$ThreadLocalMap;->remove(Ljava/lang/ThreadLocal;)V
+HSPLjava/lang/ThreadLocal$ThreadLocalMap;->remove(Ljava/lang/ThreadLocal;)V+]Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->replaceStaleEntry(Ljava/lang/ThreadLocal;Ljava/lang/Object;I)V
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->resize()V
 HSPLjava/lang/ThreadLocal$ThreadLocalMap;->set(Ljava/lang/ThreadLocal;Ljava/lang/Object;)V
@@ -2636,13 +2675,13 @@
 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;->setInitialValue()Ljava/lang/Object;
+HSPLjava/lang/ThreadLocal;->remove()V+]Ljava/lang/ThreadLocal;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;,Ljava/lang/ThreadLocal;
+HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;missing_types
+HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;missing_types
 HSPLjava/lang/ThreadLocal;->withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;
 HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>()V
 HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>(Ljava/lang/Throwable$PrintStreamOrWriter-IA;)V
@@ -2651,12 +2690,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;-><init>()V
+HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Lcom/android/internal/util/FastPrintWriter;,Ljava/io/PrintWriter;
+HSPLjava/lang/Throwable;-><init>()V+]Ljava/lang/Throwable;missing_types
 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;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,9 +2703,9 @@
 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
@@ -2699,7 +2738,7 @@
 HSPLjava/lang/UnsupportedOperationException;-><init>()V
 HSPLjava/lang/UnsupportedOperationException;-><init>(Ljava/lang/String;)V
 HSPLjava/lang/VMClassLoader;->getResource(Ljava/lang/String;)Ljava/net/URL;
-HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List;
+HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List;+]Llibcore/io/ClassPathURLStreamHandler;Llibcore/io/ClassPathURLStreamHandler;
 HSPLjava/lang/invoke/FieldVarHandle;-><init>(Ljava/lang/reflect/Field;Ljava/lang/Class;)V
 HSPLjava/lang/invoke/FieldVarHandle;->create(Ljava/lang/reflect/Field;)Ljava/lang/invoke/FieldVarHandle;
 HSPLjava/lang/invoke/MethodHandle;-><init>(JILjava/lang/invoke/MethodType;)V
@@ -2790,7 +2829,7 @@
 HSPLjava/lang/reflect/AccessibleObject;->getAnnotations()[Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/AccessibleObject;->isAccessible()Z
 HSPLjava/lang/reflect/AccessibleObject;->setAccessible(Z)V
-HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V
+HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V+]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/reflect/Array;->get(Ljava/lang/Object;I)Ljava/lang/Object;
 HSPLjava/lang/reflect/Array;->getLength(Ljava/lang/Object;)I
 HSPLjava/lang/reflect/Array;->newArray(Ljava/lang/Class;I)Ljava/lang/Object;+]Ljava/lang/Class;Ljava/lang/Class;
@@ -2833,22 +2872,22 @@
 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;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/lang/reflect/Field;->getType()Ljava/lang/Class;
 HSPLjava/lang/reflect/Field;->hashCode()I
 HSPLjava/lang/reflect/Field;->isAnnotationPresent(Ljava/lang/Class;)Z
 HSPLjava/lang/reflect/Field;->isEnumConstant()Z
-HSPLjava/lang/reflect/Field;->isSynthetic()Z
+HSPLjava/lang/reflect/Field;->isSynthetic()Z+]Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;
 HSPLjava/lang/reflect/InvocationTargetException;-><init>(Ljava/lang/Throwable;)V
 HSPLjava/lang/reflect/InvocationTargetException;->getCause()Ljava/lang/Throwable;
 HSPLjava/lang/reflect/Method$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
 HSPLjava/lang/reflect/Method$1;->compare(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)I
 HSPLjava/lang/reflect/Method;->equalNameAndParameters(Ljava/lang/reflect/Method;)Z
-HSPLjava/lang/reflect/Method;->equals(Ljava/lang/Object;)Z
+HSPLjava/lang/reflect/Method;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;Ljava/lang/Class;]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;
 HSPLjava/lang/reflect/Method;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/Method;->getDeclaredAnnotations()[Ljava/lang/annotation/Annotation;
 HSPLjava/lang/reflect/Method;->getDeclaringClass()Ljava/lang/Class;
@@ -2897,9 +2936,9 @@
 HSPLjava/lang/reflect/Proxy;->getMethodsRecursive([Ljava/lang/Class;Ljava/util/List;)V
 HSPLjava/lang/reflect/Proxy;->getProxyClass0(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class;
 HSPLjava/lang/reflect/Proxy;->intersectExceptions([Ljava/lang/Class;[Ljava/lang/Class;)[Ljava/lang/Class;
-HSPLjava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/InvocationHandler;Llibcore/reflect/AnnotationFactory;
 HSPLjava/lang/reflect/Proxy;->isProxyClass(Ljava/lang/Class;)Z
-HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;
+HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;+][Ljava/lang/Class;[Ljava/lang/Class;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/lang/reflect/Proxy;->validateReturnTypes(Ljava/util/List;)V
 HSPLjava/lang/reflect/WeakCache$CacheKey;-><init>(Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V
 HSPLjava/lang/reflect/WeakCache$CacheKey;->equals(Ljava/lang/Object;)Z
@@ -2912,7 +2951,7 @@
 HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetreverseMap(Ljava/lang/reflect/WeakCache;)Ljava/util/concurrent/ConcurrentMap;
 HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetvalueFactory(Ljava/lang/reflect/WeakCache;)Ljava/util/function/BiFunction;
 HSPLjava/lang/reflect/WeakCache;->expungeStaleEntries()V
-HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/function/BiFunction;Ljava/lang/reflect/Proxy$KeyFactory;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/function/Supplier;Ljava/lang/reflect/WeakCache$CacheValue;
 HSPLjava/math/BigDecimal;-><init>(I)V
 HSPLjava/math/BigDecimal;-><init>(J)V
 HSPLjava/math/BigDecimal;-><init>(Ljava/lang/String;)V
@@ -2937,7 +2976,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 +2985,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;
@@ -2959,8 +3001,6 @@
 HSPLjava/math/BigDecimal;->valueOf(JI)Ljava/math/BigDecimal;
 HSPLjava/math/BigDecimal;->zeroValueOf(I)Ljava/math/BigDecimal;
 HSPLjava/math/BigInteger$UnsafeHolder;-><clinit>()V
-HSPLjava/math/BigInteger$UnsafeHolder;->putMag(Ljava/math/BigInteger;[I)V
-HSPLjava/math/BigInteger$UnsafeHolder;->putSign(Ljava/math/BigInteger;I)V
 HSPLjava/math/BigInteger;-><init>(I[B)V
 HSPLjava/math/BigInteger;-><init>(I[BII)V
 HSPLjava/math/BigInteger;-><init>(I[I)V
@@ -2996,8 +3036,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 +3048,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
@@ -3034,7 +3076,7 @@
 HSPLjava/math/MutableBigInteger;->divide(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
 HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;)Ljava/math/MutableBigInteger;
 HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
-HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;+]Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;
 HSPLjava/math/MutableBigInteger;->divideOneWord(ILjava/math/MutableBigInteger;)I
 HSPLjava/math/MutableBigInteger;->getLowestSetBit()I
 HSPLjava/math/MutableBigInteger;->getMagnitudeArray()[I
@@ -3044,6 +3086,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 +3114,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
@@ -3124,7 +3167,7 @@
 HSPLjava/net/DatagramSocket;->getImpl()Ljava/net/DatagramSocketImpl;
 HSPLjava/net/DatagramSocket;->isBound()Z
 HSPLjava/net/DatagramSocket;->isClosed()Z
-HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V
+HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V+]Ljava/net/DatagramSocket;Ljava/net/DatagramSocket;,Ljava/net/MulticastSocket;]Ljava/net/DatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl;
 HSPLjava/net/DatagramSocket;->send(Ljava/net/DatagramPacket;)V
 HSPLjava/net/DatagramSocket;->setReuseAddress(Z)V
 HSPLjava/net/DatagramSocket;->setSoTimeout(I)V
@@ -3140,8 +3183,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 +3219,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;
@@ -3287,7 +3330,7 @@
 HSPLjava/net/PlainDatagramSocketImpl;->bind0(ILjava/net/InetAddress;)V
 HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketClose()V
 HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketCreate()V
-HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V
+HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V+]Ljava/net/DatagramPacket;Ljava/net/DatagramPacket;]Ljava/net/PlainDatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl;
 HSPLjava/net/PlainDatagramSocketImpl;->receive0(Ljava/net/DatagramPacket;)V
 HSPLjava/net/PlainDatagramSocketImpl;->send(Ljava/net/DatagramPacket;)V
 HSPLjava/net/PlainDatagramSocketImpl;->socketSetOption(ILjava/lang/Object;)V
@@ -3379,12 +3422,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 +3477,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 +3512,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 +3546,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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 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/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;->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
@@ -3518,13 +3561,13 @@
 HSPLjava/nio/Bits;->getFloat(Ljava/nio/ByteBuffer;IZ)F
 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;->getIntB(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 +3591,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 +3602,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+]Ljava/nio/Buffer;megamorphic_types
 HSPLjava/nio/Buffer;->capacity()I
 HSPLjava/nio/Buffer;->checkBounds(III)V
 HSPLjava/nio/Buffer;->checkIndex(I)I
@@ -3590,6 +3633,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
 HSPLjava/nio/ByteBuffer;->compareTo(Ljava/nio/ByteBuffer;)I
 HSPLjava/nio/ByteBuffer;->equals(BB)Z
 HSPLjava/nio/ByteBuffer;->equals(Ljava/lang/Object;)Z
@@ -3602,7 +3646,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;
@@ -3610,7 +3654,7 @@
 HSPLjava/nio/ByteBuffer;->wrap([BII)Ljava/nio/ByteBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
 HSPLjava/nio/ByteBufferAsCharBuffer;->duplicate()Ljava/nio/CharBuffer;
-HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C
+HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C+]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;]Ljava/nio/ByteBufferAsCharBuffer;Ljava/nio/ByteBufferAsCharBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;->get([CII)Ljava/nio/CharBuffer;
 HSPLjava/nio/ByteBufferAsCharBuffer;->isDirect()Z
 HSPLjava/nio/ByteBufferAsCharBuffer;->ix(I)I
@@ -3624,7 +3668,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
@@ -3644,7 +3690,7 @@
 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 +3698,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
@@ -3663,10 +3709,11 @@
 HSPLjava/nio/DirectByteBuffer;->asCharBuffer()Ljava/nio/CharBuffer;
 HSPLjava/nio/DirectByteBuffer;->asFloatBuffer()Ljava/nio/FloatBuffer;
 HSPLjava/nio/DirectByteBuffer;->asIntBuffer()Ljava/nio/IntBuffer;
-HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 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
@@ -3680,7 +3727,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
@@ -3692,7 +3739,7 @@
 HSPLjava/nio/DirectByteBuffer;->put(IB)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->put(JB)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;
-HSPLjava/nio/DirectByteBuffer;->put([BII)Ljava/nio/ByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->put([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putDouble(JD)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putFloat(JF)Ljava/nio/ByteBuffer;
 HSPLjava/nio/DirectByteBuffer;->putFloatUnchecked(IF)V
@@ -3705,6 +3752,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;
 HSPLjava/nio/FloatBuffer;-><init>(IIII)V
 HSPLjava/nio/FloatBuffer;-><init>(IIII[FI)V
 HSPLjava/nio/FloatBuffer;->limit(I)Ljava/nio/Buffer;
@@ -3721,25 +3769,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+]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 +3804,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;
@@ -3787,7 +3835,7 @@
 HSPLjava/nio/MappedByteBuffer;->mappingOffset()J
 HSPLjava/nio/NIOAccess;->getBaseArray(Ljava/nio/Buffer;)Ljava/lang/Object;
 HSPLjava/nio/NIOAccess;->getBaseArrayOffset(Ljava/nio/Buffer;)I
-HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V
+HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V+]Ljava/nio/DirectByteBuffer$MemoryRef;Ljava/nio/DirectByteBuffer$MemoryRef;
 HSPLjava/nio/ShortBuffer;-><init>(IIII)V
 HSPLjava/nio/ShortBuffer;-><init>(IIII[SI)V
 HSPLjava/nio/ShortBuffer;->get([S)Ljava/nio/ShortBuffer;
@@ -3824,9 +3872,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;->blockedOn(Lsun/nio/ch/Interruptible;)V+]Ljava/lang/Thread;Landroid/os/HandlerThread;
-HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)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 +3907,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;
@@ -3878,7 +3925,7 @@
 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;
@@ -3888,7 +3935,7 @@
 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;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
 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;->replacement()Ljava/lang/String;
 HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
@@ -3901,8 +3948,8 @@
 HSPLjava/nio/charset/CharsetEncoder;->canEncode(Ljava/nio/CharBuffer;)Z
 HSPLjava/nio/charset/CharsetEncoder;->charset()Ljava/nio/charset/Charset;
 HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;)Ljava/nio/ByteBuffer;
-HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult;
-HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 HSPLjava/nio/charset/CharsetEncoder;->implFlush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;
 HSPLjava/nio/charset/CharsetEncoder;->implOnMalformedInput(Ljava/nio/charset/CodingErrorAction;)V
 HSPLjava/nio/charset/CharsetEncoder;->implOnUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)V
@@ -3913,7 +3960,7 @@
 HSPLjava/nio/charset/CharsetEncoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetEncoder;
 HSPLjava/nio/charset/CharsetEncoder;->replaceWith([B)Ljava/nio/charset/CharsetEncoder;
 HSPLjava/nio/charset/CharsetEncoder;->replacement()[B
-HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder;
+HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;
 HSPLjava/nio/charset/CharsetEncoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction;
 HSPLjava/nio/charset/CoderResult;->isError()Z
 HSPLjava/nio/charset/CoderResult;->isOverflow()Z
@@ -3944,6 +3991,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 +4003,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
@@ -4001,7 +4049,7 @@
 HSPLjava/security/MessageDigest$Delegate;->engineReset()V
 HSPLjava/security/MessageDigest$Delegate;->engineUpdate(B)V
 HSPLjava/security/MessageDigest$Delegate;->engineUpdate(Ljava/nio/ByteBuffer;)V
-HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V
+HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V+]Ljava/security/MessageDigestSpi;missing_types
 HSPLjava/security/MessageDigest;-><init>(Ljava/lang/String;)V
 HSPLjava/security/MessageDigest;->digest()[B
 HSPLjava/security/MessageDigest;->digest([B)[B
@@ -4033,20 +4081,20 @@
 HSPLjava/security/Provider$Service;->getAlgorithm()Ljava/lang/String;
 HSPLjava/security/Provider$Service;->getAttribute(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Provider$Service;->getClassName()Ljava/lang/String;
-HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class;
+HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class;+]Ljava/lang/ref/Reference;Ljava/lang/ref/WeakReference;
 HSPLjava/security/Provider$Service;->getKeyClass(Ljava/lang/String;)Ljava/lang/Class;
 HSPLjava/security/Provider$Service;->getProvider()Ljava/security/Provider;
 HSPLjava/security/Provider$Service;->getType()Ljava/lang/String;
 HSPLjava/security/Provider$Service;->hasKeyAttributes()Z
 HSPLjava/security/Provider$Service;->isValid()Z
-HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/security/Provider;missing_types]Ljava/util/Map;Ljava/util/HashMap;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor;
 HSPLjava/security/Provider$Service;->supportsKeyClass(Ljava/security/Key;)Z
 HSPLjava/security/Provider$Service;->supportsKeyFormat(Ljava/security/Key;)Z
 HSPLjava/security/Provider$Service;->supportsParameter(Ljava/lang/Object;)Z
 HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V
 HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;ZLjava/security/Provider$ServiceKey-IA;)V
 HSPLjava/security/Provider$ServiceKey;->equals(Ljava/lang/Object;)Z
-HSPLjava/security/Provider$ServiceKey;->hashCode()I
+HSPLjava/security/Provider$ServiceKey;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;
 HSPLjava/security/Provider$ServiceKey;->matches(Ljava/lang/String;Ljava/lang/String;)Z
 HSPLjava/security/Provider$UString;-><init>(Ljava/lang/String;)V
 HSPLjava/security/Provider$UString;->equals(Ljava/lang/Object;)Z
@@ -4059,11 +4107,11 @@
 HSPLjava/security/Provider;->ensureLegacyParsed()V
 HSPLjava/security/Provider;->getEngineName(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Provider;->getName()Ljava/lang/String;
-HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
+HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Ljava/security/Provider$ServiceKey;Ljava/security/Provider$ServiceKey;]Ljava/util/Map;Ljava/util/LinkedHashMap;
 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
@@ -4080,7 +4128,7 @@
 HSPLjava/security/SecureRandom;->setSeed(J)V
 HSPLjava/security/SecureRandomSpi;-><init>()V
 HSPLjava/security/Security;->addProvider(Ljava/security/Provider;)I
-HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;
+HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;+]Lsun/security/jca/GetInstance$Instance;Lsun/security/jca/GetInstance$Instance;
 HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)[Ljava/lang/Object;
 HSPLjava/security/Security;->getProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/security/Security;->getProvider(Ljava/lang/String;)Ljava/security/Provider;
@@ -4205,7 +4253,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
@@ -4226,7 +4273,7 @@
 HSPLjava/text/Collator;->setDecomposition(I)V
 HSPLjava/text/Collator;->setStrength(I)V
 HSPLjava/text/DateFormat;-><init>()V
-HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
+HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/DateFormat;Ljava/text/SimpleDateFormat;
 HSPLjava/text/DateFormat;->format(Ljava/util/Date;)Ljava/lang/String;
 HSPLjava/text/DateFormat;->get(IIILjava/util/Locale;)Ljava/text/DateFormat;
 HSPLjava/text/DateFormat;->getDateInstance(ILjava/util/Locale;)Ljava/text/DateFormat;
@@ -4255,10 +4302,10 @@
 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;
+HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/DontCareFieldPosition;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
 HSPLjava/text/DecimalFormat;->getDecimalFormatSymbols()Ljava/text/DecimalFormatSymbols;
 HSPLjava/text/DecimalFormat;->getIcuFieldPosition(Ljava/text/FieldPosition;)Ljava/text/FieldPosition;
@@ -4282,20 +4329,21 @@
 HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormat;->setParseIntegerOnly(Z)V
 HSPLjava/text/DecimalFormat;->toPattern()Ljava/lang/String;
-HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V
+HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V+]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
 HSPLjava/text/DecimalFormatSymbols;-><init>(Ljava/util/Locale;)V
 HSPLjava/text/DecimalFormatSymbols;->clone()Ljava/lang/Object;
+HSPLjava/text/DecimalFormatSymbols;->findNonFormatChar(Ljava/lang/String;C)C
 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;->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;->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
@@ -4307,6 +4355,7 @@
 HSPLjava/text/DecimalFormatSymbols;->setInternationalCurrencySymbol(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormatSymbols;->setMinusSign(C)V
 HSPLjava/text/DecimalFormatSymbols;->setMonetaryDecimalSeparator(C)V
+HSPLjava/text/DecimalFormatSymbols;->setMonetaryGroupingSeparator(C)V
 HSPLjava/text/DecimalFormatSymbols;->setNaN(Ljava/lang/String;)V
 HSPLjava/text/DecimalFormatSymbols;->setPatternSeparator(C)V
 HSPLjava/text/DecimalFormatSymbols;->setPerMill(C)V
@@ -4327,12 +4376,12 @@
 HSPLjava/text/FieldPosition;->setEndIndex(I)V
 HSPLjava/text/Format;-><init>()V
 HSPLjava/text/Format;->clone()Ljava/lang/Object;
-HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;
+HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/Format;Ljava/text/SimpleDateFormat;
 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 +4390,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 +4401,7 @@
 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;->getInstance(Ljava/util/Locale;Ljava/text/NumberFormat$Style;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,16 +4427,16 @@
 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/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/FieldPosition;
+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
@@ -4398,12 +4447,12 @@
 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
+HSPLjava/text/SimpleDateFormat;->subParse(Ljava/lang/String;IIIZ[ZLjava/text/ParsePosition;ZLjava/text/CalendarBuilder;)I+]Ljava/lang/String;Ljava/lang/String;]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;->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;->zeroPaddingNumber(IIILjava/lang/StringBuffer;)V
 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
@@ -4421,6 +4470,7 @@
 HSPLjava/time/Clock$SystemClock;->instant()Ljava/time/Instant;
 HSPLjava/time/Clock$SystemClock;->millis()J
 HSPLjava/time/Clock;-><init>()V
+HSPLjava/time/Clock;->currentInstant()Ljava/time/Instant;
 HSPLjava/time/Clock;->systemDefaultZone()Ljava/time/Clock;
 HSPLjava/time/Clock;->systemUTC()Ljava/time/Clock;
 HSPLjava/time/DayOfWeek;->getValue()I
@@ -4511,7 +4561,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;
@@ -4615,7 +4665,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 +4810,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+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;megamorphic_types]Ljava/util/Iterator;megamorphic_types
 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;->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/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 +4854,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;
@@ -4823,7 +4872,7 @@
 HSPLjava/util/AbstractMap$SimpleEntry;->getKey()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleEntry;->getValue()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
-HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V
+HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V+]Ljava/util/Map$Entry;Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getKey()Ljava/lang/Object;
 HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getValue()Ljava/lang/Object;
@@ -4832,12 +4881,11 @@
 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;->equals(Ljava/lang/Object;)Z+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/lang/Object;missing_types]Ljava/util/AbstractMap;Ljava/util/LinkedHashMap;]Ljava/util/Map;Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;
+HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/AbstractMap;->hashCode()I
 HSPLjava/util/AbstractMap;->isEmpty()Z+]Ljava/util/AbstractMap;missing_types
-HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V
-HSPLjava/util/AbstractMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/util/Map;Ljava/util/Collections$SingletonMap;]Ljava/util/Iterator;Ljava/util/Collections$1;]Ljava/util/Set;Ljava/util/Collections$SingletonSet;
 HSPLjava/util/AbstractMap;->size()I
 HSPLjava/util/AbstractMap;->toString()Ljava/lang/String;
 HSPLjava/util/AbstractMap;->values()Ljava/util/Collection;
@@ -4850,37 +4898,43 @@
 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
 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;Ljava/util/ArrayDeque;
 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
+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;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/ArrayDeque$$ExternalSyntheticLambda1;
 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;
@@ -4888,13 +4942,15 @@
 HSPLjava/util/ArrayDeque;->pollLast()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->pop()Ljava/lang/Object;
 HSPLjava/util/ArrayDeque;->push(Ljava/lang/Object;)V
-HSPLjava/util/ArrayDeque;->remove()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->remove()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
 HSPLjava/util/ArrayDeque;->remove(Ljava/lang/Object;)Z
-HSPLjava/util/ArrayDeque;->removeFirst()Ljava/lang/Object;
+HSPLjava/util/ArrayDeque;->removeFirst()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;
 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,75 +4959,94 @@
 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$1;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$SubList$1;Ljava/util/ArrayList$SubList$1;
+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;Landroid/net/Uri$PathSegments;]Ljava/util/Collection;Ljava/util/Collections$EmptyList;,Landroid/net/Uri$PathSegments;
 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
+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;->contains(Ljava/lang/Object;)Z
+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+]Ljava/lang/Object;Ljava/util/ArrayList;
+HSPLjava/util/ArrayList;->equalsArrayList(Ljava/util/ArrayList;)Z
+HSPLjava/util/ArrayList;->equalsRange(Ljava/util/List;II)Z
+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;missing_types
 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;->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
 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;->set(ILjava/lang/Object;)Ljava/lang/Object;+]Ljava/util/ArrayList;Ljava/util/ArrayList;
+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
+HSPLjava/util/Arrays$ArrayList;->indexOf(Ljava/lang/Object;)I
 HSPLjava/util/Arrays$ArrayList;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Arrays$ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Arrays$ArrayList;->size()I
@@ -5035,7 +5110,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
 HSPLjava/util/Arrays;->rangeCheck(III)V
 HSPLjava/util/Arrays;->sort([C)V
 HSPLjava/util/Arrays;->sort([F)V
@@ -5052,15 +5127,14 @@
 HSPLjava/util/Arrays;->stream([III)Ljava/util/stream/IntStream;
 HSPLjava/util/Arrays;->stream([Ljava/lang/Object;)Ljava/util/stream/Stream;
 HSPLjava/util/Arrays;->stream([Ljava/lang/Object;II)Ljava/util/stream/Stream;
-HSPLjava/util/Arrays;->toString([B)Ljava/lang/String;
+HSPLjava/util/Arrays;->toString([B)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Arrays;->toString([F)Ljava/lang/String;
 HSPLjava/util/Arrays;->toString([I)Ljava/lang/String;
 HSPLjava/util/Arrays;->toString([J)Ljava/lang/String;
-HSPLjava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
 HSPLjava/util/Base64$Decoder;->decode(Ljava/lang/String;)[B
 HSPLjava/util/Base64$Decoder;->decode([B)[B
 HSPLjava/util/Base64$Decoder;->decode0([BII[B)I
-HSPLjava/util/Base64$Decoder;->outLength([BII)I
 HSPLjava/util/Base64;->getDecoder()Ljava/util/Base64$Decoder;
 HSPLjava/util/Base64;->getEncoder()Ljava/util/Base64$Encoder;
 HSPLjava/util/Base64;->getMimeDecoder()Ljava/util/Base64$Decoder;
@@ -5073,7 +5147,7 @@
 HSPLjava/util/BitSet;->checkRange(II)V
 HSPLjava/util/BitSet;->clear()V
 HSPLjava/util/BitSet;->clear(I)V
-HSPLjava/util/BitSet;->clone()Ljava/lang/Object;
+HSPLjava/util/BitSet;->clone()Ljava/lang/Object;+][J[J
 HSPLjava/util/BitSet;->ensureCapacity(I)V
 HSPLjava/util/BitSet;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/BitSet;->expandTo(I)V
@@ -5093,6 +5167,7 @@
 HSPLjava/util/BitSet;->size()I
 HSPLjava/util/BitSet;->toString()Ljava/lang/String;
 HSPLjava/util/BitSet;->trimToSize()V
+HSPLjava/util/BitSet;->valueOf(Ljava/nio/ByteBuffer;)Ljava/util/BitSet;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
 HSPLjava/util/BitSet;->valueOf([J)Ljava/util/BitSet;
 HSPLjava/util/BitSet;->wordIndex(I)I
 HSPLjava/util/Calendar;-><init>()V
@@ -5105,7 +5180,8 @@
 HSPLjava/util/Calendar;->compareTo(Ljava/util/Calendar;)I
 HSPLjava/util/Calendar;->complete()V
 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 +5203,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;->setTime(Ljava/util/Date;)V+]Ljava/util/Date;Ljava/util/Date;]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;->setWeekCountData(Ljava/util/Locale;)V+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/lang/Integer;Ljava/lang/Integer;
 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;,Ljava/util/HashMap$EntrySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/HashMap$EntryIterator;
 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
@@ -5199,6 +5275,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
@@ -5223,11 +5300,12 @@
 HSPLjava/util/Collections$SynchronizedCollection;->isEmpty()Z
 HSPLjava/util/Collections$SynchronizedCollection;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$SynchronizedCollection;->remove(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$SynchronizedCollection;->size()I
+HSPLjava/util/Collections$SynchronizedCollection;->size()I+]Ljava/util/Collection;Ljava/util/ArrayList;
 HSPLjava/util/Collections$SynchronizedCollection;->toArray()[Ljava/lang/Object;
-HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;Ljava/util/ArrayList;
+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$SynchronizedList;->get(I)Ljava/lang/Object;+]Ljava/util/List;Ljava/util/ArrayList;
 HSPLjava/util/Collections$SynchronizedMap;-><init>(Ljava/util/Map;)V
 HSPLjava/util/Collections$SynchronizedMap;->clear()V
 HSPLjava/util/Collections$SynchronizedMap;->containsKey(Ljava/lang/Object;)Z
@@ -5237,7 +5315,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;
@@ -5246,18 +5324,18 @@
 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;->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;->hasNext()Z+]Ljava/util/Iterator;megamorphic_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+]Ljava/util/Collection;Ljava/util/ArrayList;,Ljava/util/TreeMap$KeySet;
 HSPLjava/util/Collections$UnmodifiableCollection;->iterator()Ljava/util/Iterator;
 HSPLjava/util/Collections$UnmodifiableCollection;->size()I+]Ljava/util/Collection;missing_types
 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
@@ -5271,12 +5349,12 @@
 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;->hasNext()Z
 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$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;
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry;->getValue()Ljava/lang/Object;
 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
@@ -5297,7 +5375,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
@@ -5309,7 +5387,7 @@
 HSPLjava/util/Collections;->emptySet()Ljava/util/Set;
 HSPLjava/util/Collections;->enumeration(Ljava/util/Collection;)Ljava/util/Enumeration;
 HSPLjava/util/Collections;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z
-HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I
+HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/Comparable;Ljava/lang/Integer;
 HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I
 HSPLjava/util/Collections;->list(Ljava/util/Enumeration;)Ljava/util/ArrayList;
 HSPLjava/util/Collections;->max(Ljava/util/Collection;)Ljava/lang/Object;
@@ -5329,7 +5407,7 @@
 HSPLjava/util/Collections;->singletonList(Ljava/lang/Object;)Ljava/util/List;
 HSPLjava/util/Collections;->singletonMap(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;
 HSPLjava/util/Collections;->sort(Ljava/util/List;)V
-HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/List;Ljava/util/ArrayList;
+HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V
 HSPLjava/util/Collections;->swap(Ljava/util/List;II)V
 HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;)Ljava/util/Collection;
 HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;Ljava/lang/Object;)Ljava/util/Collection;
@@ -5338,8 +5416,8 @@
 HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;)Ljava/util/Set;
 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;->unmodifiableList(Ljava/util/List;)Ljava/util/List;+]Ljava/lang/Object;missing_types
+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;
@@ -5370,17 +5448,19 @@
 HSPLjava/util/Comparator;->comparingLong(Ljava/util/function/ToLongFunction;)Ljava/util/Comparator;
 HSPLjava/util/Comparator;->lambda$comparing$77a9974f$1(Ljava/util/function/Function;Ljava/lang/Object;Ljava/lang/Object;)I
 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/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;+]Ljava/util/Comparator;Ljava/util/Comparator$$ExternalSyntheticLambda5;
 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;
-HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency;
+HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency;+]Ljava/util/Locale;Ljava/util/Locale;]Landroid/icu/util/Currency;Landroid/icu/util/Currency;
 HSPLjava/util/Currency;->getSymbol(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/Date;-><init>()V
 HSPLjava/util/Date;-><init>(J)V
@@ -5405,16 +5485,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;
@@ -5479,62 +5562,64 @@
 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
+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;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;+]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
 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/String;,Ljava/lang/StringBuilder;]Ljava/lang/Appendable;Ljava/lang/StringBuilder;
 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
 HSPLjava/util/Formatter$FormatSpecifier;->checkFloat()V
-HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V
 HSPLjava/util/Formatter$FormatSpecifier;->checkInteger()V
-HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V
 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;->getZero(Ljava/util/Locale;)C
 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;->leadingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;
 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/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]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/String;,Ljava/lang/StringBuilder;
 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
 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
 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;->printBoolean(Ljava/lang/Object;Ljava/util/Locale;)V
+HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Character;Ljava/lang/Character;
 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;->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;->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
 HSPLjava/util/Formatter$FormatSpecifier;->trailingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;
+HSPLjava/util/Formatter$FormatSpecifier;->trailingZeros(Ljava/lang/StringBuilder;I)V
 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;
@@ -5547,29 +5632,29 @@
 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/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;->toString()Ljava/lang/String;+]Ljava/lang/Object;Ljava/lang/StringBuilder;
-HSPLjava/util/GregorianCalendar;-><init>()V
+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;
+HSPLjava/util/GregorianCalendar;-><init>()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;-><init>(IIIIII)V
 HSPLjava/util/GregorianCalendar;-><init>(IIIIIII)V
 HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;)V
-HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V
+HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 HSPLjava/util/GregorianCalendar;->add(II)V
 HSPLjava/util/GregorianCalendar;->adjustDstOffsetForInvalidWallClock(JLjava/util/TimeZone;I)I
 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;->computeFields()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
+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;]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone;
 HSPLjava/util/GregorianCalendar;->computeTime()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;
 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 +5662,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 +5696,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;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/stream/ReferencePipeline$4$1;
+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;
@@ -5631,7 +5719,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,10 +5729,12 @@
 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
-HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V
+HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V+]Ljava/util/HashMap;Ljava/util/HashMap;
 HSPLjava/util/HashMap;->afterNodeAccess(Ljava/util/HashMap$Node;)V
 HSPLjava/util/HashMap;->afterNodeInsertion(Z)V
 HSPLjava/util/HashMap;->afterNodeRemoval(Ljava/util/HashMap$Node;)V
@@ -5656,33 +5746,36 @@
 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;megamorphic_types]Ljava/util/Map;missing_types]Ljava/util/Iterator;missing_types]Ljava/util/Set;missing_types
 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;->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;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types
 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;
-HSPLjava/util/HashMap;->resize()[Ljava/util/HashMap$Node;+]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode;
+HSPLjava/util/HashMap;->resize()[Ljava/util/HashMap$Node;
 HSPLjava/util/HashMap;->size()I
 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
@@ -5696,7 +5789,7 @@
 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;->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;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;
 HSPLjava/util/HashSet;->size()I+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
 HSPLjava/util/HashSet;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/HashSet;->writeObject(Ljava/io/ObjectOutputStream;)V
@@ -5748,6 +5841,7 @@
 HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/util/Map$Entry;
 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 +5877,30 @@
 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$ListN;-><init>([Ljava/lang/Object;)V
+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;->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+]Ljava/lang/Object;Ljava/lang/String;
 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;missing_types]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
 HSPLjava/util/JumboEnumSet$EnumSetIterator;-><init>(Ljava/util/JumboEnumSet;)V
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->hasNext()Z
 HSPLjava/util/JumboEnumSet$EnumSetIterator;->next()Ljava/lang/Enum;
@@ -5829,16 +5924,16 @@
 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 +5952,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,7 +5962,7 @@
 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
@@ -5878,7 +5974,7 @@
 HSPLjava/util/LinkedList;-><init>()V
 HSPLjava/util/LinkedList;-><init>(Ljava/util/Collection;)V
 HSPLjava/util/LinkedList;->add(ILjava/lang/Object;)V
-HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z
+HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z+]Ljava/util/LinkedList;missing_types
 HSPLjava/util/LinkedList;->addAll(ILjava/util/Collection;)Z
 HSPLjava/util/LinkedList;->addAll(Ljava/util/Collection;)Z
 HSPLjava/util/LinkedList;->addFirst(Ljava/lang/Object;)V
@@ -5905,10 +6001,11 @@
 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;
-HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object;
+HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object;+]Ljava/util/LinkedList;Ljava/util/LinkedList;
 HSPLjava/util/LinkedList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/LinkedList;->removeFirst()Ljava/lang/Object;
 HSPLjava/util/LinkedList;->removeLast()Ljava/lang/Object;
@@ -5919,11 +6016,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+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;,Ljava/util/ArrayList$SubList$1;]Ljava/util/List;Ljava/util/LinkedList;,Ljava/util/ArrayList$SubList;
 HSPLjava/util/List;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/Locale$Builder;-><init>()V
 HSPLjava/util/Locale$Builder;->build()Ljava/util/Locale;
@@ -5932,12 +6031,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,15 +6045,15 @@
 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;->getDefault(Ljava/util/Locale$Category;)Ljava/util/Locale;+]Ljava/util/Locale$Category;Ljava/util/Locale$Category;
 HSPLjava/util/Locale;->getDisplayCountry(Ljava/util/Locale;)Ljava/lang/String;
 HSPLjava/util/Locale;->getDisplayLanguage()Ljava/lang/String;
 HSPLjava/util/Locale;->getDisplayLanguage(Ljava/util/Locale;)Ljava/lang/String;
@@ -5966,22 +6065,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;->toString()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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
 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;->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
@@ -5989,7 +6091,7 @@
 HSPLjava/util/Objects;->checkIndex(II)I
 HSPLjava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z+]Ljava/lang/Object;megamorphic_types
 HSPLjava/util/Objects;->hash([Ljava/lang/Object;)I
-HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types
+HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;,Ljava/lang/Long;
 HSPLjava/util/Objects;->nonNull(Ljava/lang/Object;)Z
 HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
@@ -6046,11 +6148,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
+HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;[Ljava/lang/Object;ILjava/util/Comparator;)V
 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;
@@ -6065,7 +6165,7 @@
 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
@@ -6099,7 +6199,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 +6209,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 +6217,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 +6230,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 +6243,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
@@ -6187,6 +6280,7 @@
 HSPLjava/util/ServiceLoader;->parseLine(Ljava/lang/Class;Ljava/net/URL;Ljava/io/BufferedReader;ILjava/util/List;)I
 HSPLjava/util/ServiceLoader;->reload()V
 HSPLjava/util/Set;->of(Ljava/lang/Object;)Ljava/util/Set;
+HSPLjava/util/Set;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Set;->of([Ljava/lang/Object;)Ljava/util/Set;
 HSPLjava/util/Set;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/SimpleTimeZone;-><init>(ILjava/lang/String;)V
@@ -6205,6 +6299,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 +6329,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
 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 +6353,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 +6371,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 +6388,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
@@ -6311,16 +6408,16 @@
 HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator;
 HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator;
 HSPLjava/util/TreeMap$EntrySet;-><init>(Ljava/util/TreeMap;)V
-HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;
+HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap$EntrySet;->size()I
 HSPLjava/util/TreeMap$KeyIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$KeyIterator;Ljava/util/TreeMap$KeyIterator;
+HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;
 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
@@ -6369,12 +6466,12 @@
 HSPLjava/util/TreeMap;->buildFromSorted(IIIILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->buildFromSorted(ILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)V
 HSPLjava/util/TreeMap;->ceilingEntry(Ljava/lang/Object;)Ljava/util/Map$Entry;
-HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
 HSPLjava/util/TreeMap;->clear()V
 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+]Ljava/util/Comparator;missing_types]Ljava/lang/Comparable;missing_types
 HSPLjava/util/TreeMap;->computeRedLevel(I)I
 HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z
 HSPLjava/util/TreeMap;->deleteEntry(Ljava/util/TreeMap$TreeMapEntry;)V
@@ -6389,8 +6486,8 @@
 HSPLjava/util/TreeMap;->floorKey(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/TreeMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
 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;->getEntryUsingComparator(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
+HSPLjava/util/TreeMap;->getEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;+]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;missing_types
+HSPLjava/util/TreeMap;->getEntryUsingComparator(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;+]Ljava/util/Comparator;missing_types
 HSPLjava/util/TreeMap;->getFirstEntry()Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->getFloorEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
 HSPLjava/util/TreeMap;->getHigherEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
@@ -6398,7 +6495,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,7 +6505,7 @@
 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;missing_types]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;->rightOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
@@ -6426,7 +6523,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 +6531,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;->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;->floor(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/TreeSet;->isEmpty()Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+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
@@ -6447,12 +6544,13 @@
 HSPLjava/util/UUID;->digits(JI)Ljava/lang/String;
 HSPLjava/util/UUID;->equals(Ljava/lang/Object;)Z
 HSPLjava/util/UUID;->fromString(Ljava/lang/String;)Ljava/util/UUID;
+HSPLjava/util/UUID;->fromStringJava8(Ljava/lang/String;)Ljava/util/UUID;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Long;Ljava/lang/Long;
 HSPLjava/util/UUID;->getLeastSignificantBits()J
 HSPLjava/util/UUID;->getMostSignificantBits()J
 HSPLjava/util/UUID;->hashCode()I
 HSPLjava/util/UUID;->nameUUIDFromBytes([B)Ljava/util/UUID;
 HSPLjava/util/UUID;->randomUUID()Ljava/util/UUID;
-HSPLjava/util/UUID;->toString()Ljava/lang/String;
+HSPLjava/util/UUID;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLjava/util/Vector$1;-><init>(Ljava/util/Vector;)V
 HSPLjava/util/Vector$1;->hasMoreElements()Z
 HSPLjava/util/Vector$1;->nextElement()Ljava/lang/Object;
@@ -6464,6 +6562,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,9 +6570,7 @@
 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
@@ -6494,7 +6591,7 @@
 HSPLjava/util/WeakHashMap$EntrySet;-><init>(Ljava/util/WeakHashMap;)V
 HSPLjava/util/WeakHashMap$EntrySet;->iterator()Ljava/util/Iterator;
 HSPLjava/util/WeakHashMap$HashIterator;-><init>(Ljava/util/WeakHashMap;)V
-HSPLjava/util/WeakHashMap$HashIterator;->hasNext()Z
+HSPLjava/util/WeakHashMap$HashIterator;->hasNext()Z+]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap$HashIterator;->nextEntry()Ljava/util/WeakHashMap$Entry;
 HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;)V
 HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;Ljava/util/WeakHashMap$KeyIterator-IA;)V
@@ -6513,19 +6610,18 @@
 HSPLjava/util/WeakHashMap;->clear()V
 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;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/WeakHashMap;->expungeStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue;
+HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry;
 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;missing_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;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+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 +6654,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 +6662,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
@@ -6589,7 +6685,7 @@
 HSPLjava/util/concurrent/ConcurrentHashMap$BaseIterator;->remove()V
 HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->size()I
-HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/Iterator;Ljava/util/concurrent/ConcurrentHashMap$ValueIterator;,Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;]Ljava/util/concurrent/ConcurrentHashMap$CollectionView;Ljava/util/concurrent/ConcurrentHashMap$KeySetView;,Ljava/util/concurrent/ConcurrentHashMap$ValuesView;
 HSPLjava/util/concurrent/ConcurrentHashMap$CounterCell;-><init>(J)V
 HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;->next()Ljava/lang/Object;
@@ -6599,7 +6695,7 @@
 HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;->find(ILjava/lang/Object;)Ljava/util/concurrent/ConcurrentHashMap$Node;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V
-HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;Ljava/lang/Object;)V
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->iterator()Ljava/util/Iterator;
 HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->spliterator()Ljava/util/Spliterator;
@@ -6627,8 +6723,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;
@@ -6638,16 +6735,17 @@
 HSPLjava/util/concurrent/ConcurrentHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/ConcurrentHashMap;->putAll(Ljava/util/Map;)V
 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;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/util/locale/BaseLocale$Key;
 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;,Ljava/lang/reflect/Proxy$Key1;,Lsun/util/locale/BaseLocale;
 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
 HSPLjava/util/concurrent/ConcurrentHashMap;->spread(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->sumCount()J
-HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node;
+HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/ConcurrentHashMap;->tableSizeFor(I)I
 HSPLjava/util/concurrent/ConcurrentHashMap;->transfer([Ljava/util/concurrent/ConcurrentHashMap$Node;[Ljava/util/concurrent/ConcurrentHashMap$Node;)V
 HSPLjava/util/concurrent/ConcurrentHashMap;->treeifyBin([Ljava/util/concurrent/ConcurrentHashMap$Node;I)V
@@ -6686,7 +6784,7 @@
 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
@@ -6723,9 +6821,9 @@
 HSPLjava/util/concurrent/CopyOnWriteArrayList$$ExternalSyntheticLambda2;->test(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;-><init>([Ljava/lang/Object;I)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->hasNext()Z
-HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->next()Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V
-HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V
+HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArrayList;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(ILjava/lang/Object;)V
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(Ljava/lang/Object;)Z
@@ -6740,19 +6838,19 @@
 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;->isEmpty()Z
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;
+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+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->lambda$removeAll$0(Ljava/util/Collection;Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(I)Ljava/lang/Object;
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;[Ljava/lang/Object;I)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->removeAll(Ljava/util/Collection;)Z
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->setArray([Ljava/lang/Object;)V
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types
 HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray()[Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;[Ljava/util/logging/Handler;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
 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;
@@ -6781,7 +6879,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 +6887,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;
@@ -6812,6 +6910,7 @@
 HSPLjava/util/concurrent/Executors;->unconfigurableExecutorService(Ljava/util/concurrent/ExecutorService;)Ljava/util/concurrent/ExecutorService;
 HSPLjava/util/concurrent/Executors;->unconfigurableScheduledExecutorService(Ljava/util/concurrent/ScheduledExecutorService;)Ljava/util/concurrent/ScheduledExecutorService;
 HSPLjava/util/concurrent/ForkJoinPool;->managedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V
+HSPLjava/util/concurrent/ForkJoinPool;->unmanagedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V+]Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;
 HSPLjava/util/concurrent/ForkJoinTask;-><init>()V
 HSPLjava/util/concurrent/FutureTask$WaitNode;-><init>()V
 HSPLjava/util/concurrent/FutureTask;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V
@@ -6819,7 +6918,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+]Ljava/util/concurrent/FutureTask;missing_types
 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 +6926,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+]Ljava/util/concurrent/Callable;missing_types]Ljava/util/concurrent/FutureTask;missing_types
 HSPLjava/util/concurrent/FutureTask;->runAndReset()Z
 HSPLjava/util/concurrent/FutureTask;->set(Ljava/lang/Object;)V
 HSPLjava/util/concurrent/FutureTask;->setException(Ljava/lang/Throwable;)V
@@ -6863,12 +6962,12 @@
 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;->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;->signalNotEmpty()V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotFull()V
-HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I
+HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
 HSPLjava/util/concurrent/LinkedBlockingQueue;->take()Ljava/lang/Object;
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>()V
 HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>(ILjava/util/Comparator;)V
@@ -6894,49 +6993,49 @@
 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;
 HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->grow()V
 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;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
 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;->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;->remove(Ljava/lang/Object;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
 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;->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;->cancel(Z)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types
 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/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+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types
+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;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
 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 +7045,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+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
 HSPLjava/util/concurrent/Semaphore$FairSync;-><init>(I)V
 HSPLjava/util/concurrent/Semaphore$FairSync;->tryAcquireShared(I)I
 HSPLjava/util/concurrent/Semaphore$NonfairSync;-><init>(I)V
@@ -6966,24 +7065,24 @@
 HSPLjava/util/concurrent/Semaphore;->tryAcquire(IJLjava/util/concurrent/TimeUnit;)Z
 HSPLjava/util/concurrent/Semaphore;->tryAcquire(JLjava/util/concurrent/TimeUnit;)Z
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;-><init>(Ljava/lang/Object;)V
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->block()Z
 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;->forgetWaiter()V
 HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isCancelled()Z
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryCancel()V
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isReleasable()Z
 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$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object;
 HSPLjava/util/concurrent/SynchronousQueue$Transferer;-><init>()V
 HSPLjava/util/concurrent/SynchronousQueue;-><init>()V
 HSPLjava/util/concurrent/SynchronousQueue;-><init>(Z)V
 HSPLjava/util/concurrent/SynchronousQueue;->isEmpty()Z
-HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/SynchronousQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
+HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack;
+HSPLjava/util/concurrent/SynchronousQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
 HSPLjava/util/concurrent/SynchronousQueue;->size()I
 HSPLjava/util/concurrent/SynchronousQueue;->take()Ljava/lang/Object;
 HSPLjava/util/concurrent/ThreadLocalRandom;-><clinit>()V
@@ -7025,13 +7124,13 @@
 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;->execute(Ljava/lang/Runnable;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->finalize()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getCorePoolSize()I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getMaximumPoolSize()I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getQueue()Ljava/util/concurrent/BlockingQueue;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getRejectedExecutionHandler()Ljava/util/concurrent/RejectedExecutionHandler;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->getTask()Ljava/lang/Runnable;+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->getTask()Ljava/lang/Runnable;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->getThreadFactory()Ljava/util/concurrent/ThreadFactory;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptIdleWorkers()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptIdleWorkers(Z)V
@@ -7042,13 +7141,13 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->onShutdown()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartAllCoreThreads()I
 HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartCoreThread()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V
-HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V
-HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z
+HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/Iterator;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateAtLeast(II)Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateLessThan(II)Z
 HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateOf(I)I
-HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;]Ljava/lang/Runnable;missing_types]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;]Ljava/lang/Runnable;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setCorePoolSize(I)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setKeepAliveTime(JLjava/util/concurrent/TimeUnit;)V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->setMaximumPoolSize(I)V
@@ -7058,9 +7157,9 @@
 HSPLjava/util/concurrent/ThreadPoolExecutor;->shutdownNow()Ljava/util/List;
 HSPLjava/util/concurrent/ThreadPoolExecutor;->terminated()V
 HSPLjava/util/concurrent/ThreadPoolExecutor;->toString()Ljava/lang/String;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/LinkedBlockingQueue;,Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;]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/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,14 +7191,15 @@
 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
+HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->incrementAndGet(Ljava/lang/Object;)I
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->set(Ljava/lang/Object;I)V
 HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater;-><init>()V
@@ -7118,7 +7218,7 @@
 HSPLjava/util/concurrent/atomic/AtomicLong;->set(J)V
 HSPLjava/util/concurrent/atomic/AtomicLong;->toString()Ljava/lang/String;
 HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
-HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
 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;
@@ -7138,21 +7238,23 @@
 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
+HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->getAndSet(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->valueCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
 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,86 +7262,80 @@
 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
 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;->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;->canReacquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Z)V+]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;->enableWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)I
+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;->signal()V
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signalAll()V
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters()V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;-><init>()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;->clearStatus()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->getAndUnsetStatus(I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setStatusRelaxed(I)V
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;-><init>()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;->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;->acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V
+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;->cleanQueue()V
 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;->getFirstQueuedThread()Ljava/lang/Thread;
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getState()I
 HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedPredecessors()Z
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedThreads()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;->isEnqueued(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;->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$ExclusiveNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNextIfShared(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
 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;
-HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V
-HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V
+HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/LockSupport;->setCurrentBlocker(Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;-><init>()V
+HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$FairSync;Ljava/util/concurrent/locks/ReentrantLock$FairSync;
 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;->isHeldExclusively()Z
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lockInterruptibly()V+]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;->tryLock()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 +7347,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,12 +7359,11 @@
 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;->getReadHoldCount()I
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I
+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;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
 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;->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;
@@ -7277,8 +7372,7 @@
 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;->getReadHoldCount()I
 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/ReentrantReadWriteLock$ReadLock;
 HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/Lock;
@@ -7288,7 +7382,6 @@
 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/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;
@@ -7296,21 +7389,19 @@
 HSPLjava/util/function/Function;->lambda$identity$2(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/jar/Attributes$Name;-><init>(Ljava/lang/String;)V
 HSPLjava/util/jar/Attributes$Name;->equals(Ljava/lang/Object;)Z
+HSPLjava/util/jar/Attributes$Name;->hash(Ljava/lang/String;)I
 HSPLjava/util/jar/Attributes$Name;->hashCode()I
-HSPLjava/util/jar/Attributes$Name;->isAlpha(C)Z
-HSPLjava/util/jar/Attributes$Name;->isDigit(C)Z
-HSPLjava/util/jar/Attributes$Name;->isValid(C)Z
-HSPLjava/util/jar/Attributes$Name;->isValid(Ljava/lang/String;)Z
 HSPLjava/util/jar/Attributes$Name;->toString()Ljava/lang/String;
 HSPLjava/util/jar/Attributes;-><init>()V
 HSPLjava/util/jar/Attributes;-><init>(I)V
 HSPLjava/util/jar/Attributes;->entrySet()Ljava/util/Set;
 HSPLjava/util/jar/Attributes;->get(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLjava/util/jar/Attributes;->getValue(Ljava/util/jar/Attributes$Name;)Ljava/lang/String;
-HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Ljava/util/LinkedHashMap;
+HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;
 HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[B)V
-HSPLjava/util/jar/Attributes;->size()I
+HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[BLjava/lang/String;I)I+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream;
+HSPLjava/util/jar/Attributes;->size()I+]Ljava/util/Map;Ljava/util/LinkedHashMap;
 HSPLjava/util/jar/JarEntry;-><init>(Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/jar/JarFile$JarFileEntry;-><init>(Ljava/util/jar/JarFile;Ljava/util/zip/ZipEntry;)V
 HSPLjava/util/jar/JarFile;-><init>(Ljava/io/File;ZI)V
@@ -7330,7 +7421,6 @@
 HSPLjava/util/jar/JarVerifier$VerifierStream;->close()V
 HSPLjava/util/jar/JarVerifier$VerifierStream;->read()I
 HSPLjava/util/jar/JarVerifier$VerifierStream;->read([BII)I
-HSPLjava/util/jar/JarVerifier;-><init>([B)V
 HSPLjava/util/jar/JarVerifier;->beginEntry(Ljava/util/jar/JarEntry;Lsun/security/util/ManifestEntryVerifier;)V
 HSPLjava/util/jar/JarVerifier;->doneWithMeta()V
 HSPLjava/util/jar/JarVerifier;->mapSignersToCertArray([Ljava/security/CodeSigner;)[Ljava/security/cert/Certificate;
@@ -7342,7 +7432,7 @@
 HSPLjava/util/jar/Manifest$FastInputStream;-><init>(Ljava/io/InputStream;I)V
 HSPLjava/util/jar/Manifest$FastInputStream;->fill()V
 HSPLjava/util/jar/Manifest$FastInputStream;->peek()B
-HSPLjava/util/jar/Manifest$FastInputStream;->readLine([B)I
+HSPLjava/util/jar/Manifest$FastInputStream;->readLine([B)I+]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream;
 HSPLjava/util/jar/Manifest$FastInputStream;->readLine([BII)I
 HSPLjava/util/jar/Manifest;-><init>()V
 HSPLjava/util/jar/Manifest;-><init>(Ljava/io/InputStream;)V
@@ -7361,7 +7451,7 @@
 HSPLjava/util/logging/FileHandler$MeteredStream;-><init>(Ljava/util/logging/FileHandler;Ljava/io/OutputStream;I)V
 HSPLjava/util/logging/FileHandler$MeteredStream;->close()V
 HSPLjava/util/logging/FileHandler$MeteredStream;->flush()V
-HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V
+HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V+]Ljava/io/OutputStream;Ljava/io/BufferedOutputStream;
 HSPLjava/util/logging/FileHandler;->-$$Nest$mrotate(Ljava/util/logging/FileHandler;)V
 HSPLjava/util/logging/FileHandler;-><clinit>()V
 HSPLjava/util/logging/FileHandler;-><init>(Ljava/lang/String;IIZ)V
@@ -7370,7 +7460,7 @@
 HSPLjava/util/logging/FileHandler;->isParentWritable(Ljava/nio/file/Path;)Z
 HSPLjava/util/logging/FileHandler;->open(Ljava/io/File;Z)V
 HSPLjava/util/logging/FileHandler;->openFiles()V
-HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/FileHandler;Ljava/util/logging/FileHandler;
 HSPLjava/util/logging/FileHandler;->rotate()V
 HSPLjava/util/logging/Formatter;-><init>()V
 HSPLjava/util/logging/Formatter;->getHead(Ljava/util/logging/Handler;)Ljava/lang/String;
@@ -7381,7 +7471,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
@@ -7448,7 +7538,7 @@
 HSPLjava/util/logging/LogManager;->parseClassNames(Ljava/lang/String;)[Ljava/lang/String;
 HSPLjava/util/logging/LogManager;->reset()V
 HSPLjava/util/logging/LogManager;->resetLogger(Ljava/util/logging/Logger;)V
-HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V
+HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V+]Ljava/lang/Object;Ljava/util/logging/Level;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
 HSPLjava/util/logging/LogRecord;->defaultThreadID()I
 HSPLjava/util/logging/LogRecord;->getLevel()Ljava/util/logging/Level;
 HSPLjava/util/logging/LogRecord;->getLoggerName()Ljava/lang/String;
@@ -7469,12 +7559,12 @@
 HSPLjava/util/logging/Logger;->addHandler(Ljava/util/logging/Handler;)V
 HSPLjava/util/logging/Logger;->checkPermission()V
 HSPLjava/util/logging/Logger;->demandLogger(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)Ljava/util/logging/Logger;
-HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger;
 HSPLjava/util/logging/Logger;->doSetParent(Ljava/util/logging/Logger;)V
 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,8 +7576,8 @@
 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;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)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+]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;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
 HSPLjava/util/logging/Logger;->removeChildLogger(Ljava/util/logging/LogManager$LoggerWeakRef;)V
@@ -7506,36 +7596,39 @@
 HSPLjava/util/logging/StreamHandler;-><init>()V
 HSPLjava/util/logging/StreamHandler;->close()V
 HSPLjava/util/logging/StreamHandler;->configure()V
-HSPLjava/util/logging/StreamHandler;->flush()V
+HSPLjava/util/logging/StreamHandler;->flush()V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;
 HSPLjava/util/logging/StreamHandler;->flushAndClose()V
 HSPLjava/util/logging/StreamHandler;->isLoggable(Ljava/util/logging/LogRecord;)Z
-HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V
+HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;]Ljava/util/logging/StreamHandler;Ljava/util/logging/FileHandler;
 HSPLjava/util/logging/StreamHandler;->setEncoding(Ljava/lang/String;)V
 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+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/regex/Matcher;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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;
+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(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;->lookingAt()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
+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;->replaceAll(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
 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/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder;
 HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;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
@@ -7553,13 +7646,12 @@
 HSPLjava/util/regex/Pattern;->pattern()Ljava/lang/String;
 HSPLjava/util/regex/Pattern;->quote(Ljava/lang/String;)Ljava/lang/String;
 HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;)[Ljava/lang/String;
-HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;
+HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;+]Ljava/util/List;Ljava/util/ArrayList$SubList;]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Pattern;Ljava/util/regex/Pattern;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;]Ljava/util/ArrayList;Ljava/util/ArrayList;
 HSPLjava/util/regex/Pattern;->toString()Ljava/lang/String;
 HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/Spliterator;IZ)V
-HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V
+HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/SortedOps$OfRef;,Ljava/util/stream/ReferencePipeline$3;,Ljava/util/stream/ReferencePipeline$4;
 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;->copyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V+]Ljava/util/Spliterator;Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/HashMap$KeySpliterator;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;]Ljava/util/stream/Sink;Ljava/util/stream/ReferencePipeline$4$1;,Ljava/util/stream/IntPipeline$4$1;]Ljava/util/stream/StreamOpFlag;Ljava/util/stream/StreamOpFlag;
 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;
@@ -7568,7 +7660,7 @@
 HSPLjava/util/stream/AbstractPipeline;->isParallel()Z
 HSPLjava/util/stream/AbstractPipeline;->onClose(Ljava/lang/Runnable;)Ljava/util/stream/BaseStream;
 HSPLjava/util/stream/AbstractPipeline;->sequential()Ljava/util/stream/BaseStream;
-HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator;
+HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
 HSPLjava/util/stream/AbstractPipeline;->sourceStageSpliterator()Ljava/util/Spliterator;
 HSPLjava/util/stream/AbstractPipeline;->spliterator()Ljava/util/Spliterator;
 HSPLjava/util/stream/AbstractPipeline;->wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;
@@ -7576,6 +7668,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 +7678,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 +7691,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 +7751,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 +7778,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 +7802,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 +7835,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 +7873,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;
@@ -7793,7 +7893,7 @@
 HSPLjava/util/stream/ReduceOps;->makeInt(ILjava/util/function/IntBinaryOperator;)Ljava/util/stream/TerminalOp;
 HSPLjava/util/stream/ReduceOps;->makeLong(JLjava/util/function/LongBinaryOperator;)Ljava/util/stream/TerminalOp;
 HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/function/BinaryOperator;)Ljava/util/stream/TerminalOp;
-HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/stream/Collector;)Ljava/util/stream/TerminalOp;
+HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/stream/Collector;)Ljava/util/stream/TerminalOp;+]Ljava/util/stream/Collector;Ljava/util/stream/Collectors$CollectorImpl;
 HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;-><init>()V
 HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;->applyAsLong(Ljava/lang/Object;)J
 HSPLjava/util/stream/ReferencePipeline$2$1;-><init>(Ljava/util/stream/ReferencePipeline$2;Ljava/util/stream/Sink;)V
@@ -7818,7 +7918,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 +7940,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 +7967,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 +8016,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 +8069,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,23 +8086,24 @@
 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;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 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
 HSPLjava/util/zip/ZipEntry;->getCompressedSize()J
+HSPLjava/util/zip/ZipEntry;->getCrc()J
 HSPLjava/util/zip/ZipEntry;->getMethod()I
 HSPLjava/util/zip/ZipEntry;->getName()Ljava/lang/String;
 HSPLjava/util/zip/ZipEntry;->getSize()J
@@ -8028,10 +8137,11 @@
 HSPLjava/util/zip/ZipFile;->ensureOpenOrZipException()V
 HSPLjava/util/zip/ZipFile;->entries()Ljava/util/Enumeration;
 HSPLjava/util/zip/ZipFile;->finalize()V
-HSPLjava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
+HSPLjava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;+]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder;
 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;Ldalvik/system/ZipPathValidator$1;]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 +8150,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;
@@ -8078,11 +8188,13 @@
 HSPLjavax/crypto/Cipher;->tryCombinations(Ljavax/crypto/Cipher$InitParams;Ljava/security/Provider;[Ljava/lang/String;)Ljavax/crypto/Cipher$CipherSpiAndProvider;
 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 +8362,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 +8383,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;->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,12 +8408,12 @@
 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;->toJavaFormatString(D)Ljava/lang/String;
@@ -8313,15 +8425,19 @@
 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;->getAndBitwiseAndInt(Ljava/lang/Object;JI)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/Unsafe;->getAndSetInt(Ljava/lang/Object;JI)I
 HSPLjdk/internal/misc/Unsafe;->getAndSetLong(Ljava/lang/Object;JJ)J
 HSPLjdk/internal/misc/Unsafe;->getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;
@@ -8329,15 +8445,26 @@
 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;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 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;->putIntOpaque(Ljava/lang/Object;JI)V
 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;->putReferenceOpaque(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+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/Unsafe;->weakCompareAndSetInt(Ljava/lang/Object;JII)Z
+HSPLjdk/internal/misc/Unsafe;->weakCompareAndSetReference(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
 HSPLjdk/internal/misc/VM;->getSavedProperty(Ljava/lang/String;)Ljava/lang/String;
 HSPLjdk/internal/reflect/Reflection;->getCallerClass()Ljava/lang/Class;
 HSPLjdk/internal/util/ArraysSupport;->mismatch([B[BI)I
@@ -8369,8 +8496,10 @@
 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;->getMonetaryGroupSeparator()Ljava/lang/String;
+HSPLlibcore/icu/DecimalFormatData;->getMonetarySeparator()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNaN()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getNumberPattern()Ljava/lang/String;
 HSPLlibcore/icu/DecimalFormatData;->getPatternSeparator()C
@@ -8391,7 +8520,7 @@
 HSPLlibcore/icu/ICU;->transformIcuDateTimePattern(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/icu/ICU;->transformIcuDateTimePattern_forJavaText(Ljava/lang/String;)Ljava/lang/String;
 HSPLlibcore/icu/LocaleData;->get(Ljava/util/Locale;)Llibcore/icu/LocaleData;
-HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale;
+HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale;+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/Locale;Ljava/util/Locale;
 HSPLlibcore/icu/LocaleData;->initLocaleData(Ljava/util/Locale;)Llibcore/icu/LocaleData;
 HSPLlibcore/icu/LocaleData;->initializeCalendarData(Ljava/util/Locale;)V
 HSPLlibcore/icu/LocaleData;->initializeDateFormatData(Ljava/util/Locale;)V
@@ -8403,13 +8532,13 @@
 HSPLlibcore/internal/StringPool;->contentEquals(Ljava/lang/String;[CII)Z
 HSPLlibcore/internal/StringPool;->get([CII)Ljava/lang/String;
 HSPLlibcore/io/BlockGuardOs;->accept(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)Ljava/io/FileDescriptor;
-HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z+]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLlibcore/io/BlockGuardOs;->android_getaddrinfo(Ljava/lang/String;Landroid/system/StructAddrinfo;I)[Ljava/net/InetAddress;
 HSPLlibcore/io/BlockGuardOs;->chmod(Ljava/lang/String;I)V
 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 +8551,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,17 +8563,17 @@
 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+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 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
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->connect()V
 HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->getInputStream()Ljava/io/InputStream;
 HSPLlibcore/io/ClassPathURLStreamHandler;-><init>(Ljava/lang/String;)V
-HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL;
+HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL;+]Ljava/util/jar/JarFile;Ljava/util/jar/JarFile;
 HSPLlibcore/io/ClassPathURLStreamHandler;->isEntryStored(Ljava/lang/String;)Z
 HSPLlibcore/io/ClassPathURLStreamHandler;->openConnection(Ljava/net/URL;)Ljava/net/URLConnection;
 HSPLlibcore/io/ForwardingOs;-><init>(Llibcore/io/Os;)V
@@ -8474,13 +8603,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 +8617,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 +8633,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 +8641,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 +8649,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;
@@ -8532,7 +8661,7 @@
 HSPLlibcore/io/IoBridge;->write(Ljava/io/FileDescriptor;[BII)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
 HSPLlibcore/io/IoTracker;-><init>()V
 HSPLlibcore/io/IoTracker;->reset()V
-HSPLlibcore/io/IoTracker;->trackIo(I)V+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/IoTracker;->trackIo(I)V+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
 HSPLlibcore/io/IoTracker;->trackIo(ILlibcore/io/IoTracker$Mode;)V+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
 HSPLlibcore/io/IoUtils;->acquireRawFd(Ljava/io/FileDescriptor;)I
 HSPLlibcore/io/IoUtils;->canOpenReadOnly(Ljava/lang/String;)Z
@@ -8542,7 +8671,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
@@ -8579,37 +8708,37 @@
 HSPLlibcore/net/http/HttpURLConnectionFactory;->openConnection(Ljava/net/URL;Ljavax/net/SocketFactory;Ljava/net/Proxy;)Ljava/net/URLConnection;
 HSPLlibcore/net/http/HttpURLConnectionFactory;->setDns(Llibcore/net/http/Dns;)V
 HSPLlibcore/net/http/HttpURLConnectionFactory;->setNewConnectionPool(IJLjava/util/concurrent/TimeUnit;)V
-HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V
+HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V+]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/AnnotationFactory;->createAnnotation(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;
 HSPLlibcore/reflect/AnnotationFactory;->getElementsDescription(Ljava/lang/Class;)[Llibcore/reflect/AnnotationMember;
-HSPLlibcore/reflect/AnnotationFactory;->invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
+HSPLlibcore/reflect/AnnotationFactory;->invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/AnnotationMember;-><init>(Ljava/lang/String;Ljava/lang/Object;)V
 HSPLlibcore/reflect/AnnotationMember;-><init>(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V
 HSPLlibcore/reflect/AnnotationMember;->copyValue()Ljava/lang/Object;
 HSPLlibcore/reflect/AnnotationMember;->setDefinition(Llibcore/reflect/AnnotationMember;)Llibcore/reflect/AnnotationMember;
-HSPLlibcore/reflect/AnnotationMember;->validateValue()Ljava/lang/Object;
+HSPLlibcore/reflect/AnnotationMember;->validateValue()Ljava/lang/Object;+]Ljava/lang/Object;missing_types]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember;
 HSPLlibcore/reflect/GenericArrayTypeImpl;->getGenericComponentType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;-><init>(Ljava/lang/ClassLoader;)V
-HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V
+HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->isStopSymbol(C)Z
 HSPLlibcore/reflect/GenericSignatureParser;->parseClassSignature()V
-HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type;
+HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 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
 HSPLlibcore/reflect/GenericSignatureParser;->parseOptFormalTypeParameters()V
-HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes;
+HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes;+]Llibcore/reflect/ListOfTypes;Llibcore/reflect/ListOfTypes;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->parseReturnType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeArgument()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeSignature()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/GenericSignatureParser;->parseTypeVariableSignature()Llibcore/reflect/TypeVariableImpl;
-HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V
+HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/GenericSignatureParser;->scanSymbol()V
-HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
+HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser;
 HSPLlibcore/reflect/ListOfTypes;-><init>(I)V
 HSPLlibcore/reflect/ListOfTypes;-><init>([Ljava/lang/reflect/Type;)V
 HSPLlibcore/reflect/ListOfTypes;->add(Ljava/lang/reflect/Type;)V
@@ -8620,10 +8749,11 @@
 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;
-HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type;
+HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type;+]Llibcore/reflect/ParameterizedTypeImpl;Llibcore/reflect/ParameterizedTypeImpl;
 HSPLlibcore/reflect/ParameterizedTypeImpl;->getResolvedType()Ljava/lang/reflect/Type;
 HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V
 HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;Llibcore/reflect/ListOfTypes;)V
@@ -8680,11 +8810,11 @@
 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;->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;->getOffsetsByUtcTime(J[I)I
-HSPLlibcore/util/ZoneInfo;->getRawOffset()I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
+HSPLlibcore/util/ZoneInfo;->getOffset(J)I
+HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)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 +8838,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 +8874,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 +8885,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
@@ -8841,17 +8974,17 @@
 HSPLorg/json/JSONStringer;->newline()V
 HSPLorg/json/JSONStringer;->object()Lorg/json/JSONStringer;
 HSPLorg/json/JSONStringer;->open(Lorg/json/JSONStringer$Scope;Ljava/lang/String;)Lorg/json/JSONStringer;
-HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope;
-HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V
-HSPLorg/json/JSONStringer;->string(Ljava/lang/String;)V
+HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope;+]Ljava/util/List;Ljava/util/ArrayList;
+HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V+]Ljava/util/List;Ljava/util/ArrayList;
+HSPLorg/json/JSONStringer;->string(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
 HSPLorg/json/JSONStringer;->toString()Ljava/lang/String;
 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;->nextToInternal(Ljava/lang/String;)Ljava/lang/String;
-HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;
-HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;
+HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;
+HSPLorg/json/JSONTokener;->nextToInternal(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
+HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener;
+HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener;]Lorg/json/JSONArray;Lorg/json/JSONArray;
 HSPLorg/json/JSONTokener;->readEscapeCharacter()C
 HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object;
 HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject;
@@ -8891,14 +9024,14 @@
 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;Landroid/graphics/HardwareRenderer$DestroyContextRunnable;,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
 HSPLsun/misc/CompoundEnumeration;->hasMoreElements()Z
 HSPLsun/misc/CompoundEnumeration;->next()Z
 HSPLsun/misc/CompoundEnumeration;->nextElement()Ljava/lang/Object;
-HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B
+HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;,Ljava/io/ByteArrayInputStream;
 HSPLsun/misc/LRUCache;-><init>(I)V
 HSPLsun/misc/LRUCache;->forName(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/misc/LRUCache;->moveToFront([Ljava/lang/Object;I)V
@@ -8950,7 +9083,7 @@
 HSPLsun/nio/ch/FileChannelImpl$Unmapper;-><init>(JJILjava/io/FileDescriptor;Lsun/nio/ch/FileChannelImpl$Unmapper-IA;)V
 HSPLsun/nio/ch/FileChannelImpl$Unmapper;->run()V
 HSPLsun/nio/ch/FileChannelImpl;-><init>(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)V
-HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V+]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;
+HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V
 HSPLsun/nio/ch/FileChannelImpl;->fileLockTable()Lsun/nio/ch/FileLockTable;
 HSPLsun/nio/ch/FileChannelImpl;->finalize()V
 HSPLsun/nio/ch/FileChannelImpl;->force(Z)V
@@ -8961,7 +9094,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;
 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
@@ -9119,11 +9252,11 @@
 HSPLsun/nio/cs/StreamEncoder;->implClose()V
 HSPLsun/nio/cs/StreamEncoder;->implFlush()V
 HSPLsun/nio/cs/StreamEncoder;->implFlushBuffer()V
-HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V
+HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
 HSPLsun/nio/cs/StreamEncoder;->write(I)V
 HSPLsun/nio/cs/StreamEncoder;->write(Ljava/lang/String;II)V
 HSPLsun/nio/cs/StreamEncoder;->write([CII)V
-HSPLsun/nio/cs/StreamEncoder;->writeBytes()V
+HSPLsun/nio/cs/StreamEncoder;->writeBytes()V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/io/OutputStream;Ljava/util/logging/FileHandler$MeteredStream;,Ljava/io/FileOutputStream;
 HSPLsun/nio/cs/ThreadLocalCoders$1;->create(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLsun/nio/cs/ThreadLocalCoders$1;->hasName(Ljava/lang/Object;Ljava/lang/Object;)Z
 HSPLsun/nio/cs/ThreadLocalCoders$2;->create(Ljava/lang/Object;)Ljava/lang/Object;
@@ -9160,6 +9293,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 +9307,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 +9318,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 +9369,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;
@@ -9260,11 +9397,11 @@
 HSPLsun/security/jca/GetInstance$Instance;-><init>(Ljava/security/Provider;Ljava/lang/Object;Lsun/security/jca/GetInstance$Instance-IA;)V
 HSPLsun/security/jca/GetInstance$Instance;->toArray()[Ljava/lang/Object;
 HSPLsun/security/jca/GetInstance;->checkSuperClass(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Class;)V
-HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;
+HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/security/Provider;)Lsun/security/jca/GetInstance$Instance;
-HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance;
+HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance;+]Ljava/security/Provider$Service;Ljava/security/Provider$Service;
 HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance;
 HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
 HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;
@@ -9284,6 +9421,7 @@
 HSPLsun/security/jca/ProviderList$ServiceList$1;->hasNext()Z
 HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/lang/Object;
 HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/security/Provider$Service;
+HSPLsun/security/jca/ProviderList$ServiceList;->-$$Nest$mtryGet(Lsun/security/jca/ProviderList$ServiceList;I)Ljava/security/Provider$Service;
 HSPLsun/security/jca/ProviderList$ServiceList;-><init>(Lsun/security/jca/ProviderList;Ljava/lang/String;Ljava/lang/String;)V
 HSPLsun/security/jca/ProviderList$ServiceList;->addService(Ljava/security/Provider$Service;)V
 HSPLsun/security/jca/ProviderList$ServiceList;->iterator()Ljava/util/Iterator;
@@ -9295,7 +9433,7 @@
 HSPLsun/security/jca/ProviderList;->getProvider(I)Ljava/security/Provider;
 HSPLsun/security/jca/ProviderList;->getProvider(Ljava/lang/String;)Ljava/security/Provider;
 HSPLsun/security/jca/ProviderList;->getProviderConfig(Ljava/lang/String;)Lsun/security/jca/ProviderConfig;
-HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;
+HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;]Ljava/security/Provider;missing_types
 HSPLsun/security/jca/ProviderList;->getServices(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
 HSPLsun/security/jca/ProviderList;->insertAt(Lsun/security/jca/ProviderList;Ljava/security/Provider;I)Lsun/security/jca/ProviderList;
 HSPLsun/security/jca/ProviderList;->loadAll()I
@@ -9429,7 +9567,7 @@
 HSPLsun/security/provider/certpath/PolicyChecker;->processParents(IZZLsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;Z)Z
 HSPLsun/security/provider/certpath/PolicyChecker;->processPolicies(ILjava/util/Set;IIIZLsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/x509/X509CertImpl;Z)Lsun/security/provider/certpath/PolicyNodeImpl;
 HSPLsun/security/provider/certpath/PolicyChecker;->processPolicyMappings(Lsun/security/x509/X509CertImpl;IILsun/security/provider/certpath/PolicyNodeImpl;ZLjava/util/Set;)Lsun/security/provider/certpath/PolicyNodeImpl;
-HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V
+HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V+]Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl;
 HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl;)V
 HSPLsun/security/provider/certpath/PolicyNodeImpl;->addChild(Lsun/security/provider/certpath/PolicyNodeImpl;)V
 HSPLsun/security/provider/certpath/PolicyNodeImpl;->copyTree()Lsun/security/provider/certpath/PolicyNodeImpl;
@@ -9467,6 +9605,7 @@
 HSPLsun/security/util/AlgorithmDecomposer;->decomposeOneHash(Ljava/lang/String;)Ljava/util/Set;
 HSPLsun/security/util/AlgorithmDecomposer;->hasLoop(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)V
 HSPLsun/security/util/BitArray;-><init>(I[B)V
+HSPLsun/security/util/BitArray;-><init>(I[BI)V
 HSPLsun/security/util/BitArray;->get(I)Z
 HSPLsun/security/util/BitArray;->length()I
 HSPLsun/security/util/BitArray;->position(I)I
@@ -9482,7 +9621,7 @@
 HSPLsun/security/util/DerIndefLenConverter;->isLongForm(I)Z
 HSPLsun/security/util/DerInputBuffer;-><init>([B)V
 HSPLsun/security/util/DerInputBuffer;-><init>([BII)V
-HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer;
+HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/lang/Object;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputBuffer;->getBigInteger(IZ)Ljava/math/BigInteger;
 HSPLsun/security/util/DerInputBuffer;->getBitString()[B
 HSPLsun/security/util/DerInputBuffer;->getBitString(I)[B
@@ -9495,34 +9634,34 @@
 HSPLsun/security/util/DerInputBuffer;->getUnalignedBitString()Lsun/security/util/BitArray;
 HSPLsun/security/util/DerInputBuffer;->peek()I
 HSPLsun/security/util/DerInputBuffer;->toByteArray()[B
-HSPLsun/security/util/DerInputBuffer;->truncate(I)V
-HSPLsun/security/util/DerInputStream;-><init>(Lsun/security/util/DerInputBuffer;)V
+HSPLsun/security/util/DerInputBuffer;->truncate(I)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
+HSPLsun/security/util/DerInputStream;-><init>(Lsun/security/util/DerInputBuffer;)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;-><init>([B)V
-HSPLsun/security/util/DerInputStream;->available()I
+HSPLsun/security/util/DerInputStream;->available()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getBigInteger()Ljava/math/BigInteger;
-HSPLsun/security/util/DerInputStream;->getByte()I
+HSPLsun/security/util/DerInputStream;->getByte()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getBytes([B)V
 HSPLsun/security/util/DerInputStream;->getDerValue()Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getEnumerated()I
 HSPLsun/security/util/DerInputStream;->getGeneralizedTime()Ljava/util/Date;
 HSPLsun/security/util/DerInputStream;->getLength()I
 HSPLsun/security/util/DerInputStream;->getLength(ILjava/io/InputStream;)I
-HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I
+HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->getOID()Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/util/DerInputStream;->getOctetString()[B
 HSPLsun/security/util/DerInputStream;->getSequence(I)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue;
+HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
+HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerInputStream;->getSet(IZ)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getSet(IZZ)[Lsun/security/util/DerValue;
 HSPLsun/security/util/DerInputStream;->getUTCTime()Ljava/util/Date;
 HSPLsun/security/util/DerInputStream;->getUnalignedBitString()Lsun/security/util/BitArray;
-HSPLsun/security/util/DerInputStream;->init([BIIZ)V
+HSPLsun/security/util/DerInputStream;->init([BIIZ)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 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;->reset()V
+HSPLsun/security/util/DerInputStream;->reset()V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerInputStream;->subStream(IZ)Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerInputStream;->toByteArray()[B
 HSPLsun/security/util/DerOutputStream;-><init>()V
@@ -9541,12 +9680,12 @@
 HSPLsun/security/util/DerValue;-><init>(Ljava/lang/String;)V
 HSPLsun/security/util/DerValue;-><init>(Lsun/security/util/DerInputBuffer;Z)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;
 HSPLsun/security/util/DerValue;-><init>([B)V
-HSPLsun/security/util/DerValue;->encode(Lsun/security/util/DerOutputStream;)V
+HSPLsun/security/util/DerValue;->encode(Lsun/security/util/DerOutputStream;)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerOutputStream;Lsun/security/util/DerOutputStream;
 HSPLsun/security/util/DerValue;->getBigInteger()Ljava/math/BigInteger;
 HSPLsun/security/util/DerValue;->getBitString()[B
 HSPLsun/security/util/DerValue;->getBoolean()Z
 HSPLsun/security/util/DerValue;->getData()Lsun/security/util/DerInputStream;
-HSPLsun/security/util/DerValue;->getDataBytes()[B
+HSPLsun/security/util/DerValue;->getDataBytes()[B+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerValue;->getIA5String()Ljava/lang/String;
 HSPLsun/security/util/DerValue;->getInteger()I
 HSPLsun/security/util/DerValue;->getOID()Lsun/security/util/ObjectIdentifier;
@@ -9555,14 +9694,14 @@
 HSPLsun/security/util/DerValue;->getTag()B
 HSPLsun/security/util/DerValue;->getUnalignedBitString()Lsun/security/util/BitArray;
 HSPLsun/security/util/DerValue;->init(BLjava/lang/String;)Lsun/security/util/DerInputStream;
-HSPLsun/security/util/DerValue;->init(ZLjava/io/InputStream;)Lsun/security/util/DerInputStream;
+HSPLsun/security/util/DerValue;->init(ZLjava/io/InputStream;)Lsun/security/util/DerInputStream;+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;,Ljava/io/ByteArrayInputStream;
 HSPLsun/security/util/DerValue;->isConstructed()Z
 HSPLsun/security/util/DerValue;->isContextSpecific()Z
 HSPLsun/security/util/DerValue;->isContextSpecific(B)Z
 HSPLsun/security/util/DerValue;->isPrintableStringChar(C)Z
 HSPLsun/security/util/DerValue;->length()I
 HSPLsun/security/util/DerValue;->resetTag(B)V
-HSPLsun/security/util/DerValue;->toByteArray()[B
+HSPLsun/security/util/DerValue;->toByteArray()[B+]Lsun/security/util/DerValue;Lsun/security/util/DerValue;]Lsun/security/util/DerOutputStream;Lsun/security/util/DerOutputStream;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DerValue;->toDerInputStream()Lsun/security/util/DerInputStream;
 HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->getConstraints(Ljava/lang/String;)Ljava/util/Set;
 HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->permits(Ljava/security/Key;)Z
@@ -9597,12 +9736,12 @@
 HSPLsun/security/util/MemoryCache;->newEntry(Ljava/lang/Object;Ljava/lang/Object;JLjava/lang/ref/ReferenceQueue;)Lsun/security/util/MemoryCache$CacheEntry;
 HSPLsun/security/util/MemoryCache;->put(Ljava/lang/Object;Ljava/lang/Object;)V
 HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputBuffer;)V
-HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputStream;)V
+HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputStream;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/util/ObjectIdentifier;->check([B)V
 HSPLsun/security/util/ObjectIdentifier;->encode(Lsun/security/util/DerOutputStream;)V
 HSPLsun/security/util/ObjectIdentifier;->equals(Ljava/lang/Object;)Z
 HSPLsun/security/util/ObjectIdentifier;->hashCode()I
-HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String;
+HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
 HSPLsun/security/util/SignatureFileVerifier;-><init>(Ljava/util/ArrayList;Lsun/security/util/ManifestDigester;Ljava/lang/String;[B)V
 HSPLsun/security/util/SignatureFileVerifier;->getDigest(Ljava/lang/String;)Ljava/security/MessageDigest;
 HSPLsun/security/util/SignatureFileVerifier;->getSigners([Lsun/security/pkcs/SignerInfo;Lsun/security/pkcs/PKCS7;)[Ljava/security/CodeSigner;
@@ -9616,16 +9755,16 @@
 HSPLsun/security/util/SignatureFileVerifier;->verifyManifestHash(Ljava/util/jar/Manifest;Lsun/security/util/ManifestDigester;Ljava/util/List;)Z
 HSPLsun/security/x509/AVA;-><init>(Ljava/io/Reader;ILjava/util/Map;)V
 HSPLsun/security/x509/AVA;-><init>(Ljava/io/Reader;Ljava/util/Map;)V
-HSPLsun/security/x509/AVA;-><init>(Lsun/security/util/DerValue;)V
+HSPLsun/security/x509/AVA;-><init>(Lsun/security/util/DerValue;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/x509/AVA;->derEncode(Ljava/io/OutputStream;)V
 HSPLsun/security/x509/AVA;->isDerString(Lsun/security/util/DerValue;Z)Z
 HSPLsun/security/x509/AVA;->isTerminator(II)Z
 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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Lsun/security/util/DerValue;Lsun/security/util/DerValue;
 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;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;+]Lsun/security/util/ObjectIdentifier;Lsun/security/util/ObjectIdentifier;]Ljava/util/Map;Ljava/util/HashMap;,Ljava/util/Collections$EmptyMap;
 HSPLsun/security/x509/AVAKeyword;->getOID(Ljava/lang/String;ILjava/util/Map;)Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/x509/AVAKeyword;->isCompliant(I)Z
 HSPLsun/security/x509/AccessDescription;-><init>(Lsun/security/util/DerValue;)V
@@ -9708,7 +9847,7 @@
 HSPLsun/security/x509/PolicyInformation;->getPolicyIdentifier()Lsun/security/x509/CertificatePolicyId;
 HSPLsun/security/x509/PolicyInformation;->getPolicyQualifiers()Ljava/util/Set;
 HSPLsun/security/x509/RDN;-><init>(Ljava/lang/String;Ljava/util/Map;)V
-HSPLsun/security/x509/RDN;-><init>(Lsun/security/util/DerValue;)V
+HSPLsun/security/x509/RDN;-><init>(Lsun/security/util/DerValue;)V+]Lsun/security/util/DerValue;Lsun/security/util/DerValue;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/x509/RDN;->encode(Lsun/security/util/DerOutputStream;)V
 HSPLsun/security/x509/RDN;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/RDN;->toRFC2253String(Z)Ljava/lang/String;
@@ -9732,20 +9871,20 @@
 HSPLsun/security/x509/X500Name;->asX500Principal()Ljavax/security/auth/x500/X500Principal;
 HSPLsun/security/x509/X500Name;->checkNoNewLinesNorTabsAtBeginningOfDN(Ljava/lang/String;)V
 HSPLsun/security/x509/X500Name;->countQuotes(Ljava/lang/String;II)I
-HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z
+HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z+]Lsun/security/x509/X500Name;Lsun/security/x509/X500Name;
 HSPLsun/security/x509/X500Name;->escaped(IILjava/lang/String;)Z
 HSPLsun/security/x509/X500Name;->generateRFC2253DN(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->getEncoded()[B
 HSPLsun/security/x509/X500Name;->getEncodedInternal()[B
-HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String;
+HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/x509/RDN;Lsun/security/x509/RDN;
 HSPLsun/security/x509/X500Name;->getRFC2253Name()Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->getRFC2253Name(Ljava/util/Map;)Ljava/lang/String;
 HSPLsun/security/x509/X500Name;->hashCode()I
 HSPLsun/security/x509/X500Name;->intern(Lsun/security/util/ObjectIdentifier;)Lsun/security/util/ObjectIdentifier;
 HSPLsun/security/x509/X500Name;->isEmpty()Z
-HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V
+HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
 HSPLsun/security/x509/X500Name;->parseDN(Ljava/lang/String;Ljava/util/Map;)V
-HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V
+HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;
 HSPLsun/security/x509/X509AttributeName;->getPrefix()Ljava/lang/String;
 HSPLsun/security/x509/X509AttributeName;->getSuffix()Ljava/lang/String;
 HSPLsun/security/x509/X509CertImpl;-><init>([B)V
@@ -9805,7 +9944,7 @@
 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;->getDayOfWeekFromFixedDate(J)I
 HSPLsun/util/calendar/BaseCalendar;->getDayOfYear(III)J
-HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J
+HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;
 HSPLsun/util/calendar/BaseCalendar;->getFixedDate(Lsun/util/calendar/CalendarDate;)J
 HSPLsun/util/calendar/BaseCalendar;->getGregorianYearFromFixedDate(J)I
 HSPLsun/util/calendar/BaseCalendar;->isLeapYear(I)Z
@@ -9814,7 +9953,7 @@
 HSPLsun/util/calendar/CalendarDate;-><init>(Ljava/util/TimeZone;)V
 HSPLsun/util/calendar/CalendarDate;->clone()Ljava/lang/Object;
 HSPLsun/util/calendar/CalendarDate;->getDayOfMonth()I
-HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I
+HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I+]Lsun/util/calendar/CalendarDate;Lsun/util/calendar/Gregorian$Date;
 HSPLsun/util/calendar/CalendarDate;->getEra()Lsun/util/calendar/Era;
 HSPLsun/util/calendar/CalendarDate;->getHours()I
 HSPLsun/util/calendar/CalendarDate;->getMillis()I
@@ -9856,7 +9995,7 @@
 HSPLsun/util/calendar/CalendarUtils;->mod(JJ)J
 HSPLsun/util/calendar/CalendarUtils;->sprintf0d(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder;
 HSPLsun/util/calendar/Gregorian$Date;-><init>(Ljava/util/TimeZone;)V
-HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I
+HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I+]Lsun/util/calendar/Gregorian$Date;Lsun/util/calendar/Gregorian$Date;
 HSPLsun/util/calendar/Gregorian$Date;->setNormalizedYear(I)V
 HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/CalendarDate;
 HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/Gregorian$Date;
@@ -9878,20 +10017,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+]Ljava/lang/String;Ljava/lang/String;
+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 +10074,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;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry;
 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 +10105,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 +10183,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;
@@ -10366,7 +10506,10 @@
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DES;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede$Mappings;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2WithHmacSHA1;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$Mappings;
+Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$PBKDF2WithHmacSHA1UTF8;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12$Mappings;
 Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12;
@@ -10388,6 +10531,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;
@@ -10396,6 +10540,7 @@
 Lcom/android/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter;
 Lcom/android/org/bouncycastle/jcajce/provider/util/DigestFactory;
 Lcom/android/org/bouncycastle/jcajce/spec/AEADParameterSpec;
+Lcom/android/org/bouncycastle/jcajce/spec/PBKDF2KeySpec;
 Lcom/android/org/bouncycastle/jcajce/util/BCJcaJceHelper;
 Lcom/android/org/bouncycastle/jcajce/util/DefaultJcaJceHelper;
 Lcom/android/org/bouncycastle/jcajce/util/JcaJceHelper;
@@ -10404,6 +10549,7 @@
 Lcom/android/org/bouncycastle/jce/interfaces/BCKeyStore;
 Lcom/android/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$1;
+Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$PrivateProvider;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider;
 Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration;
 Lcom/android/org/bouncycastle/jce/provider/CertStoreCollectionSpi;
@@ -10469,13 +10615,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;
@@ -10586,6 +10735,7 @@
 Ljava/io/StringBufferInputStream;
 Ljava/io/StringReader;
 Ljava/io/StringWriter;
+Ljava/io/SyncFailedException;
 Ljava/io/UTFDataFormatException;
 Ljava/io/UncheckedIOException;
 Ljava/io/UnixFileSystem;
@@ -10593,6 +10743,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;
@@ -10623,6 +10775,8 @@
 Ljava/lang/ClassLoader$SystemClassLoader;
 Ljava/lang/ClassLoader;
 Ljava/lang/ClassNotFoundException;
+Ljava/lang/ClassValue$Entry;
+Ljava/lang/ClassValue;
 Ljava/lang/CloneNotSupportedException;
 Ljava/lang/Cloneable;
 Ljava/lang/Comparable;
@@ -10653,8 +10807,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 +10852,19 @@
 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$Option;
+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 +10872,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;
@@ -10725,6 +10892,7 @@
 Ljava/lang/ThreadGroup;
 Ljava/lang/ThreadLocal$SuppliedThreadLocal;
 Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
+Ljava/lang/ThreadLocal$ThreadLocalMap-IA;
 Ljava/lang/ThreadLocal$ThreadLocalMap;
 Ljava/lang/ThreadLocal;
 Ljava/lang/Throwable$PrintStreamOrWriter;
@@ -10742,6 +10910,7 @@
 Ljava/lang/UNIXProcess$ProcessReaperThreadFactory;
 Ljava/lang/UNIXProcess;
 Ljava/lang/UnsatisfiedLinkError;
+Ljava/lang/UnsupportedClassVersionError;
 Ljava/lang/UnsupportedOperationException;
 Ljava/lang/VMClassLoader;
 Ljava/lang/VerifyError;
@@ -10819,10 +10988,14 @@
 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;
 Ljava/lang/invoke/Transformers$ZeroValue;
+Ljava/lang/invoke/TypeDescriptor$OfField;
+Ljava/lang/invoke/TypeDescriptor$OfMethod;
+Ljava/lang/invoke/TypeDescriptor;
 Ljava/lang/invoke/VarHandle$1;
 Ljava/lang/invoke/VarHandle$AccessMode;
 Ljava/lang/invoke/VarHandle$AccessType;
@@ -10956,6 +11129,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 +11163,7 @@
 Ljava/net/UnknownServiceException;
 Ljava/nio/Bits;
 Ljava/nio/Buffer;
+Ljava/nio/BufferMismatch;
 Ljava/nio/BufferOverflowException;
 Ljava/nio/BufferUnderflowException;
 Ljava/nio/ByteBuffer;
@@ -11062,6 +11237,7 @@
 Ljava/nio/channels/spi/AbstractSelectionKey;
 Ljava/nio/channels/spi/AbstractSelector$1;
 Ljava/nio/channels/spi/AbstractSelector;
+Ljava/nio/channels/spi/AsynchronousChannelProvider;
 Ljava/nio/channels/spi/SelectorProvider$1;
 Ljava/nio/channels/spi/SelectorProvider;
 Ljava/nio/charset/CharacterCodingException;
@@ -11069,8 +11245,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 +11265,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 +11278,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 +11497,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,10 +11527,11 @@
 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;
+Ljava/time/format/DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0;
+Ljava/time/format/DateTimeFormatterBuilder$DayPeriod;
 Ljava/time/format/DateTimeFormatterBuilder$FractionPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$InstantPrinterParser;
 Ljava/time/format/DateTimeFormatterBuilder$NumberPrinterParser;
@@ -11437,14 +11616,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 +11639,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 +11719,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 +11794,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 +11829,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 +11869,16 @@
 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$1;
 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 +12060,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;
@@ -11897,6 +12079,7 @@
 Ljava/util/concurrent/LinkedBlockingQueue$Itr;
 Ljava/util/concurrent/LinkedBlockingQueue$Node;
 Ljava/util/concurrent/LinkedBlockingQueue;
+Ljava/util/concurrent/Phaser;
 Ljava/util/concurrent/PriorityBlockingQueue;
 Ljava/util/concurrent/RejectedExecutionException;
 Ljava/util/concurrent/RejectedExecutionHandler;
@@ -11946,8 +12129,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 +12180,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 +12263,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 +12453,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;
@@ -12372,6 +12564,7 @@
 Ljavax/net/ssl/SSLSocket;
 Ljavax/net/ssl/SSLSocketFactory$1;
 Ljavax/net/ssl/SSLSocketFactory;
+Ljavax/net/ssl/StandardConstants;
 Ljavax/net/ssl/TrustManager;
 Ljavax/net/ssl/TrustManagerFactory$1;
 Ljavax/net/ssl/TrustManagerFactory;
@@ -12391,6 +12584,7 @@
 Ljavax/security/cert/CertificateException;
 Ljavax/security/cert/X509Certificate$1;
 Ljavax/security/cert/X509Certificate;
+Ljavax/xml/datatype/DatatypeConfigurationException;
 Ljavax/xml/datatype/DatatypeConstants$Field;
 Ljavax/xml/datatype/DatatypeConstants;
 Ljavax/xml/datatype/Duration;
@@ -12407,6 +12601,7 @@
 Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
 Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
 Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer;
+Ljdk/internal/math/FloatingDecimal$HexFloatPattern;
 Ljdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;
 Ljdk/internal/math/FloatingDecimal;
 Ljdk/internal/math/FormattedFloatingDecimal$1;
@@ -12415,11 +12610,15 @@
 Ljdk/internal/math/FormattedFloatingDecimal;
 Ljdk/internal/misc/JavaObjectInputStreamAccess;
 Ljdk/internal/misc/SharedSecrets;
+Ljdk/internal/misc/TerminatingThreadLocal$1;
+Ljdk/internal/misc/TerminatingThreadLocal;
 Ljdk/internal/misc/Unsafe;
+Ljdk/internal/misc/UnsafeConstants;
 Ljdk/internal/misc/VM;
 Ljdk/internal/reflect/Reflection;
 Ljdk/internal/util/ArraysSupport;
 Ljdk/internal/util/Preconditions;
+Ljdk/internal/util/StaticProperty;
 Llibcore/content/type/MimeMap$$ExternalSyntheticLambda0;
 Llibcore/content/type/MimeMap$Builder$Element;
 Llibcore/content/type/MimeMap$Builder;
@@ -12427,7 +12626,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 +12788,6 @@
 Lsun/misc/JavaIOFileDescriptorAccess;
 Lsun/misc/LRUCache;
 Lsun/misc/SharedSecrets;
-Lsun/misc/Unsafe$$ExternalSyntheticBackportWithForwarding0;
 Lsun/misc/Unsafe;
 Lsun/misc/VM;
 Lsun/misc/Version;
@@ -12630,6 +12827,7 @@
 Lsun/nio/ch/IOStatus;
 Lsun/nio/ch/IOUtil;
 Lsun/nio/ch/Interruptible;
+Lsun/nio/ch/LinuxAsynchronousChannelProvider;
 Lsun/nio/ch/NativeDispatcher;
 Lsun/nio/ch/NativeObject;
 Lsun/nio/ch/NativeThread;
@@ -12721,6 +12919,7 @@
 Lsun/security/jca/Providers;
 Lsun/security/jca/ServiceId;
 Lsun/security/pkcs/ContentInfo;
+Lsun/security/pkcs/ESSCertId;
 Lsun/security/pkcs/PKCS7$VerbatimX509Certificate;
 Lsun/security/pkcs/PKCS7$WrappedX509Certificate;
 Lsun/security/pkcs/PKCS7;
@@ -12780,6 +12979,7 @@
 Lsun/security/util/DisabledAlgorithmConstraints$Constraints;
 Lsun/security/util/DisabledAlgorithmConstraints$KeySizeConstraint;
 Lsun/security/util/DisabledAlgorithmConstraints;
+Lsun/security/util/FilePaths;
 Lsun/security/util/KeyUtil;
 Lsun/security/util/Length;
 Lsun/security/util/ManifestDigester$Entry;
@@ -12792,6 +12992,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 +13070,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 +13096,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;
@@ -12947,6 +13150,7 @@
 [Ljava/lang/Character;
 [Ljava/lang/Class;
 [Ljava/lang/ClassLoader;
+[Ljava/lang/ClassValue$Entry;
 [Ljava/lang/Comparable;
 [Ljava/lang/Daemons$Daemon;
 [Ljava/lang/Double;
@@ -12954,11 +13158,13 @@
 [Ljava/lang/Float;
 [Ljava/lang/Integer;
 [Ljava/lang/Long;
+[Ljava/lang/Number;
 [Ljava/lang/Object;
 [Ljava/lang/Package;
 [Ljava/lang/Runnable;
 [Ljava/lang/Short;
 [Ljava/lang/StackTraceElement;
+[Ljava/lang/StackWalker$Option;
 [Ljava/lang/String;
 [Ljava/lang/StringBuilder;
 [Ljava/lang/Thread$State;
@@ -12970,6 +13176,7 @@
 [Ljava/lang/annotation/Annotation;
 [Ljava/lang/invoke/MethodHandle;
 [Ljava/lang/invoke/MethodType;
+[Ljava/lang/invoke/TypeDescriptor$OfField;
 [Ljava/lang/invoke/VarHandle$AccessMode;
 [Ljava/lang/invoke/VarHandle$AccessType;
 [Ljava/lang/ref/WeakReference;
@@ -12992,8 +13199,10 @@
 [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/FileVisitResult;
 [Ljava/nio/file/LinkOption;
 [Ljava/nio/file/OpenOption;
 [Ljava/nio/file/StandardCopyOption;
@@ -13001,6 +13210,7 @@
 [Ljava/nio/file/attribute/FileAttribute;
 [Ljava/security/CodeSigner;
 [Ljava/security/CryptoPrimitive;
+[Ljava/security/MessageDigest;
 [Ljava/security/Permission;
 [Ljava/security/Principal;
 [Ljava/security/ProtectionDomain;
@@ -13040,15 +13250,16 @@
 [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/Set;
 [Ljava/util/TimerTask;
 [Ljava/util/UUID;
 [Ljava/util/WeakHashMap$Entry;
@@ -13056,7 +13267,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;
@@ -13120,6 +13330,9 @@
 [[Ljava/lang/annotation/Annotation;
 [[Ljava/lang/invoke/MethodHandle;
 [[Ljava/math/BigInteger;
+[[Ljava/security/cert/Certificate;
+[[Ljava/security/cert/X509Certificate;
 [[S
+[[Z
 [[[B
 [[[I
diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes
index 4d22e22..2264b95 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
@@ -398,7 +411,10 @@
 com.android.org.bouncycastle.jcajce.provider.symmetric.DES
 com.android.org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings
 com.android.org.bouncycastle.jcajce.provider.symmetric.DESede
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2WithHmacSHA1
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings
+com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$PBKDF2WithHmacSHA1UTF8
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings
 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12
@@ -420,6 +436,7 @@
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.ClassUtil
+com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil$2
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE$Util
 com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE
@@ -428,6 +445,7 @@
 com.android.org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
 com.android.org.bouncycastle.jcajce.provider.util.DigestFactory
 com.android.org.bouncycastle.jcajce.spec.AEADParameterSpec
+com.android.org.bouncycastle.jcajce.spec.PBKDF2KeySpec
 com.android.org.bouncycastle.jcajce.util.BCJcaJceHelper
 com.android.org.bouncycastle.jcajce.util.DefaultJcaJceHelper
 com.android.org.bouncycastle.jcajce.util.JcaJceHelper
@@ -436,6 +454,7 @@
 com.android.org.bouncycastle.jce.interfaces.BCKeyStore
 com.android.org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier
 com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$1
+com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$PrivateProvider
 com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
 com.android.org.bouncycastle.jce.provider.BouncyCastleProviderConfiguration
 com.android.org.bouncycastle.jce.provider.CertStoreCollectionSpi
@@ -501,13 +520,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 +538,7 @@
 java.io.ByteArrayOutputStream
 java.io.CharArrayReader
 java.io.CharArrayWriter
+java.io.CharConversionException
 java.io.Closeable
 java.io.Console
 java.io.DataInput
@@ -601,6 +624,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 +647,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
@@ -652,6 +679,8 @@
 java.lang.ClassLoader$SystemClassLoader
 java.lang.ClassLoader
 java.lang.ClassNotFoundException
+java.lang.ClassValue$Entry
+java.lang.ClassValue
 java.lang.CloneNotSupportedException
 java.lang.Cloneable
 java.lang.Comparable
@@ -682,8 +711,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 +733,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 +745,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 +756,19 @@
 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$Option
+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 +776,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
@@ -760,6 +796,7 @@
 java.lang.ThreadGroup
 java.lang.ThreadLocal$SuppliedThreadLocal
 java.lang.ThreadLocal$ThreadLocalMap$Entry
+java.lang.ThreadLocal$ThreadLocalMap-IA
 java.lang.ThreadLocal$ThreadLocalMap
 java.lang.ThreadLocal
 java.lang.Throwable$PrintStreamOrWriter
@@ -777,6 +814,7 @@
 java.lang.UNIXProcess$ProcessReaperThreadFactory
 java.lang.UNIXProcess
 java.lang.UnsatisfiedLinkError
+java.lang.UnsupportedClassVersionError
 java.lang.UnsupportedOperationException
 java.lang.VMClassLoader
 java.lang.VerifyError
@@ -854,10 +892,14 @@
 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
 java.lang.invoke.Transformers$ZeroValue
+java.lang.invoke.TypeDescriptor$OfField
+java.lang.invoke.TypeDescriptor$OfMethod
+java.lang.invoke.TypeDescriptor
 java.lang.invoke.VarHandle$1
 java.lang.invoke.VarHandle$AccessMode
 java.lang.invoke.VarHandle$AccessType
@@ -914,6 +956,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
@@ -990,6 +1033,7 @@
 java.net.Proxy
 java.net.ProxySelector
 java.net.ResponseCache
+java.net.ServerSocket$1
 java.net.ServerSocket
 java.net.Socket$1
 java.net.Socket$2
@@ -1023,6 +1067,7 @@
 java.net.UnknownServiceException
 java.nio.Bits
 java.nio.Buffer
+java.nio.BufferMismatch
 java.nio.BufferOverflowException
 java.nio.BufferUnderflowException
 java.nio.ByteBuffer
@@ -1096,6 +1141,7 @@
 java.nio.channels.spi.AbstractSelectionKey
 java.nio.channels.spi.AbstractSelector$1
 java.nio.channels.spi.AbstractSelector
+java.nio.channels.spi.AsynchronousChannelProvider
 java.nio.channels.spi.SelectorProvider$1
 java.nio.channels.spi.SelectorProvider
 java.nio.charset.CharacterCodingException
@@ -1103,14 +1149,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 +1169,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 +1182,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 +1224,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
@@ -1351,6 +1401,7 @@
 java.time.Duration
 java.time.Instant$1
 java.time.Instant
+java.time.InstantSource
 java.time.LocalDate$1
 java.time.LocalDate
 java.time.LocalDateTime
@@ -1380,15 +1431,17 @@
 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
+java.time.format.DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0
+java.time.format.DateTimeFormatterBuilder$DayPeriod
 java.time.format.DateTimeFormatterBuilder$FractionPrinterParser
 java.time.format.DateTimeFormatterBuilder$InstantPrinterParser
 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
@@ -1467,14 +1520,15 @@
 java.util.AbstractQueue
 java.util.AbstractSequentialList
 java.util.AbstractSet
+java.util.ArrayDeque$$ExternalSyntheticLambda1
 java.util.ArrayDeque$DeqIterator
 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,18 +1543,12 @@
 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
 java.util.BitSet
+java.util.Calendar$$ExternalSyntheticLambda0
 java.util.Calendar$Builder
 java.util.Calendar
 java.util.Collection
@@ -1575,6 +1623,7 @@
 java.util.Date
 java.util.Deque
 java.util.Dictionary
+java.util.DualPivotQuicksort$Sorter
 java.util.DualPivotQuicksort
 java.util.DuplicateFormatFlagsException
 java.util.EmptyStackException
@@ -1647,15 +1696,15 @@
 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.ImmutableCollections$SubList
 java.util.InputMismatchException
 java.util.Iterator
 java.util.JumboEnumSet$EnumSetIterator
@@ -1671,6 +1720,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 +1732,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
@@ -1718,13 +1772,16 @@
 java.util.ResourceBundle$BundleReference
 java.util.ResourceBundle$CacheKey
 java.util.ResourceBundle$CacheKeyReference
+java.util.ResourceBundle$Control$$ExternalSyntheticLambda0
 java.util.ResourceBundle$Control$1
 java.util.ResourceBundle$Control$CandidateListCache
 java.util.ResourceBundle$Control
-java.util.ResourceBundle$LoaderReference
+java.util.ResourceBundle$KeyElementReference
+java.util.ResourceBundle$RBClassLoader$1
+java.util.ResourceBundle$RBClassLoader
 java.util.ResourceBundle$SingleFormatControl
 java.util.ResourceBundle
-java.util.Scanner$1
+java.util.Scanner$PatternLRUCache
 java.util.Scanner
 java.util.ServiceConfigurationError
 java.util.ServiceLoader$1
@@ -1779,6 +1836,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 +1865,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 +1929,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
@@ -1902,12 +1963,13 @@
 java.util.concurrent.Executors$RunnableAdapter
 java.util.concurrent.Executors
 java.util.concurrent.ForkJoinPool$1
+java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory
 java.util.concurrent.ForkJoinPool$ManagedBlocker
 java.util.concurrent.ForkJoinPool$WorkQueue
 java.util.concurrent.ForkJoinPool
-java.util.concurrent.ForkJoinTask$ExceptionNode
+java.util.concurrent.ForkJoinTask$Aux
 java.util.concurrent.ForkJoinTask
 java.util.concurrent.ForkJoinWorkerThread
 java.util.concurrent.Future
@@ -1920,6 +1982,7 @@
 java.util.concurrent.LinkedBlockingQueue$Itr
 java.util.concurrent.LinkedBlockingQueue$Node
 java.util.concurrent.LinkedBlockingQueue
+java.util.concurrent.Phaser
 java.util.concurrent.PriorityBlockingQueue
 java.util.concurrent.RejectedExecutionException
 java.util.concurrent.RejectedExecutionHandler
@@ -1935,6 +1998,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
@@ -1967,8 +2031,11 @@
 java.util.concurrent.atomic.Striped64$Cell
 java.util.concurrent.atomic.Striped64
 java.util.concurrent.locks.AbstractOwnableSynchronizer
+java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject
+java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
+java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode
 java.util.concurrent.locks.AbstractQueuedSynchronizer
 java.util.concurrent.locks.Condition
 java.util.concurrent.locks.Lock
@@ -2015,8 +2082,13 @@
 java.util.function.IntUnaryOperator
 java.util.function.LongBinaryOperator
 java.util.function.LongConsumer
+java.util.function.LongFunction
+java.util.function.LongPredicate
 java.util.function.LongSupplier
 java.util.function.LongUnaryOperator
+java.util.function.ObjDoubleConsumer
+java.util.function.ObjIntConsumer
+java.util.function.ObjLongConsumer
 java.util.function.Predicate
 java.util.function.Supplier
 java.util.function.ToDoubleBiFunction
@@ -2038,6 +2110,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
@@ -2092,6 +2165,7 @@
 java.util.stream.Collector$Characteristics
 java.util.stream.Collector
 java.util.stream.Collectors$$ExternalSyntheticLambda0
+java.util.stream.Collectors$$ExternalSyntheticLambda13
 java.util.stream.Collectors$$ExternalSyntheticLambda15
 java.util.stream.Collectors$$ExternalSyntheticLambda1
 java.util.stream.Collectors$$ExternalSyntheticLambda20
@@ -2157,6 +2231,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 +2305,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 +2350,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 +2388,7 @@
 javax.crypto.Cipher$SpiAndProviderUpdater
 javax.crypto.Cipher$Transform
 javax.crypto.Cipher
+javax.crypto.CipherInputStream
 javax.crypto.CipherOutputStream
 javax.crypto.CipherSpi
 javax.crypto.CryptoPermissions
@@ -2385,6 +2465,7 @@
 javax.net.ssl.SSLSocket
 javax.net.ssl.SSLSocketFactory$1
 javax.net.ssl.SSLSocketFactory
+javax.net.ssl.StandardConstants
 javax.net.ssl.TrustManager
 javax.net.ssl.TrustManagerFactory$1
 javax.net.ssl.TrustManagerFactory
@@ -2394,6 +2475,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
@@ -2401,9 +2485,11 @@
 javax.security.cert.CertificateException
 javax.security.cert.X509Certificate$1
 javax.security.cert.X509Certificate
+javax.xml.datatype.DatatypeConfigurationException
 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
@@ -2416,18 +2502,24 @@
 jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer
 jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter
 jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer
+jdk.internal.math.FloatingDecimal$HexFloatPattern
 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
 jdk.internal.misc.SharedSecrets
+jdk.internal.misc.TerminatingThreadLocal$1
 jdk.internal.misc.TerminatingThreadLocal
 jdk.internal.misc.Unsafe
+jdk.internal.misc.UnsafeConstants
 jdk.internal.misc.VM
 jdk.internal.reflect.Reflection
 jdk.internal.util.ArraysSupport
 jdk.internal.util.Preconditions
+jdk.internal.util.StaticProperty
 libcore.content.type.MimeMap$$ExternalSyntheticLambda0
 libcore.content.type.MimeMap$Builder$Element
 libcore.content.type.MimeMap$Builder
@@ -2435,7 +2527,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 +2599,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 +2630,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 +2659,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 +2668,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 +2689,6 @@
 sun.misc.JavaIOFileDescriptorAccess
 sun.misc.LRUCache
 sun.misc.SharedSecrets
-sun.misc.Unsafe$$ExternalSyntheticBackportWithForwarding0
 sun.misc.Unsafe
 sun.misc.VM
 sun.misc.Version
@@ -2624,6 +2728,7 @@
 sun.nio.ch.IOStatus
 sun.nio.ch.IOUtil
 sun.nio.ch.Interruptible
+sun.nio.ch.LinuxAsynchronousChannelProvider
 sun.nio.ch.NativeDispatcher
 sun.nio.ch.NativeObject
 sun.nio.ch.NativeThread
@@ -2714,6 +2819,7 @@
 sun.security.jca.Providers
 sun.security.jca.ServiceId
 sun.security.pkcs.ContentInfo
+sun.security.pkcs.ESSCertId
 sun.security.pkcs.PKCS7$VerbatimX509Certificate
 sun.security.pkcs.PKCS7$WrappedX509Certificate
 sun.security.pkcs.PKCS7
@@ -2752,6 +2858,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
@@ -2772,6 +2879,7 @@
 sun.security.util.DisabledAlgorithmConstraints$Constraints
 sun.security.util.DisabledAlgorithmConstraints$KeySizeConstraint
 sun.security.util.DisabledAlgorithmConstraints
+sun.security.util.FilePaths
 sun.security.util.KeyUtil
 sun.security.util.Length
 sun.security.util.ManifestDigester$Entry
@@ -2784,6 +2892,9 @@
 sun.security.util.MemoryCache$SoftCacheEntry
 sun.security.util.MemoryCache
 sun.security.util.ObjectIdentifier
+sun.security.util.PropertyExpander
+sun.security.util.Resources
+sun.security.util.ResourcesMgr$1
 sun.security.util.ResourcesMgr
 sun.security.util.SecurityConstants
 sun.security.util.SignatureFileVerifier
@@ -2859,6 +2970,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 +2996,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 +3014,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,23 +3043,28 @@
 [Ljava.io.ObjectStreamClass$MemberSignature;
 [Ljava.io.ObjectStreamField;
 [Ljava.io.Serializable;
+[Ljava.lang.Boolean;
 [Ljava.lang.Byte;
 [Ljava.lang.CharSequence;
 [Ljava.lang.Character$UnicodeBlock;
 [Ljava.lang.Character;
 [Ljava.lang.Class;
 [Ljava.lang.ClassLoader;
+[Ljava.lang.ClassValue$Entry;
 [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;
 [Ljava.lang.Short;
 [Ljava.lang.StackTraceElement;
+[Ljava.lang.StackWalker$Option;
 [Ljava.lang.String;
 [Ljava.lang.StringBuilder;
 [Ljava.lang.Thread$State;
@@ -2953,6 +3076,7 @@
 [Ljava.lang.annotation.Annotation;
 [Ljava.lang.invoke.MethodHandle;
 [Ljava.lang.invoke.MethodType;
+[Ljava.lang.invoke.TypeDescriptor$OfField;
 [Ljava.lang.invoke.VarHandle$AccessMode;
 [Ljava.lang.invoke.VarHandle$AccessType;
 [Ljava.lang.ref.WeakReference;
@@ -2975,8 +3099,10 @@
 [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.FileVisitResult;
 [Ljava.nio.file.LinkOption;
 [Ljava.nio.file.OpenOption;
 [Ljava.nio.file.StandardCopyOption;
@@ -2984,12 +3110,15 @@
 [Ljava.nio.file.attribute.FileAttribute;
 [Ljava.security.CodeSigner;
 [Ljava.security.CryptoPrimitive;
+[Ljava.security.MessageDigest;
 [Ljava.security.Permission;
 [Ljava.security.Principal;
 [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,15 +3150,16 @@
 [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.Set;
 [Ljava.util.TimerTask;
 [Ljava.util.UUID;
 [Ljava.util.WeakHashMap$Entry;
@@ -3037,7 +3167,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 +3185,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 +3218,7 @@
 [Z
 [[B
 [[C
+[[D
 [[F
 [[I
 [[J
@@ -3098,6 +3230,9 @@
 [[Ljava.lang.annotation.Annotation;
 [[Ljava.lang.invoke.MethodHandle;
 [[Ljava.math.BigInteger;
+[[Ljava.security.cert.Certificate;
+[[Ljava.security.cert.X509Certificate;
 [[S
+[[Z
 [[[B
 [[[I
diff --git a/libartbase/base/compiler_filter.h b/libartbase/base/compiler_filter.h
index 7e6aae8..0a7b1bc 100644
--- a/libartbase/base/compiler_filter.h
+++ b/libartbase/base/compiler_filter.h
@@ -29,6 +29,8 @@
  public:
   // Note: Order here matters. Later filter choices are considered "as good
   // as" earlier filter choices.
+  // Keep supported filters in sync with `ArtShellCommand.printHelp` in
+  // art/libartservice/service/java/com/android/server/art/ArtShellCommand.java.
   enum Filter {
     kAssumeVerified,      // Skip verification but mark all classes as verified anyway.
     kVerify,              // Only verify classes.
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index b3f42ee..b4d8440 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -79,6 +79,8 @@
 static constexpr const char* kAndroidSystemExtRootDefaultPath = "/system_ext";
 static constexpr const char* kAndroidDataEnvVar = "ANDROID_DATA";
 static constexpr const char* kAndroidDataDefaultPath = "/data";
+static constexpr const char* kAndroidExpandEnvVar = "ANDROID_EXPAND";
+static constexpr const char* kAndroidExpandDefaultPath = "/mnt/expand";
 static constexpr const char* kAndroidArtRootEnvVar = "ANDROID_ART_ROOT";
 static constexpr const char* kAndroidConscryptRootEnvVar = "ANDROID_CONSCRYPT_ROOT";
 static constexpr const char* kAndroidI18nRootEnvVar = "ANDROID_I18N_ROOT";
@@ -289,6 +291,18 @@
 
 std::string GetAndroidData() { return GetAndroidDir(kAndroidDataEnvVar, kAndroidDataDefaultPath); }
 
+std::string GetAndroidExpandSafe(std::string* error_msg) {
+  const char* android_dir = GetAndroidDirSafe(kAndroidExpandEnvVar,
+                                              kAndroidExpandDefaultPath,
+                                              /*must_exist=*/true,
+                                              error_msg);
+  return (android_dir != nullptr) ? android_dir : "";
+}
+
+std::string GetAndroidExpand() {
+  return GetAndroidDir(kAndroidExpandEnvVar, kAndroidExpandDefaultPath);
+}
+
 std::string GetArtApexData() {
   return GetAndroidDir(kArtApexDataEnvVar, kArtApexDataDefaultPath, /*must_exist=*/false);
 }
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index faec95e..cff6a92 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -71,6 +71,11 @@
 // Find $ANDROID_DATA, /data, or return an empty string.
 std::string GetAndroidDataSafe(/*out*/ std::string* error_msg);
 
+// Find $ANDROID_EXPAND, /mnt/expand, or abort.
+std::string GetAndroidExpand();
+// Find $ANDROID_EXPAND, /mnt/expand, or return an empty string.
+std::string GetAndroidExpandSafe(/*out*/ std::string* error_msg);
+
 // Find $ART_APEX_DATA, /data/misc/apexdata/com.android.art, or abort.
 std::string GetArtApexData();
 
diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp
index 9ac9091..e6aae64 100644
--- a/libartpalette/Android.bp
+++ b/libartpalette/Android.bp
@@ -58,6 +58,9 @@
         "libbase_headers",
         "jni_headers",
     ],
+    export_header_lib_headers: [
+        "jni_headers",
+    ],
     target: {
         // Targets supporting dlopen build the client library which loads
         // and binds the methods in the libartpalette-system library.
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 97f928e..ad2033a 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -60,13 +60,61 @@
     ],
 }
 
+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",
+        "sdk_module-lib_current_framework-permission-s",
+        // 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 +124,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 +209,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..a9cd65a 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -2,7 +2,183 @@
 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 {
+  }
+
+  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..4445ecd
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -0,0 +1,232 @@
+/*
+ * 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("PrimaryRefProfilePath[packageName = %s, profileName = %s]",
+                profile.packageName, profile.profileName);
+    }
+
+    @NonNull
+    public static String toString(@NonNull SecondaryRefProfilePath profile) {
+        return String.format("SecondaryRefProfilePath[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull PrebuiltProfilePath profile) {
+        return String.format("PrebuiltProfilePath[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull DexMetadataPath profile) {
+        return String.format("DexMetadataPath[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());
+            case ProfilePath.prebuiltProfilePath:
+                return toString(profile.getPrebuiltProfilePath());
+            case ProfilePath.dexMetadataPath:
+                return toString(profile.getDexMetadataPath());
+            default:
+                throw new UnsupportedOperationException(
+                        "Only a subset of 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..378bfa4 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,16 +16,1338 @@
 
 package com.android.server.art;
 
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+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.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 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.Build;
+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 androidx.annotation.RequiresApi;
+
+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.DetailedDexInfo;
+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 dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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.CompletableFuture;
+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";
+    /** @hide */
+    public static final String TAG = "ArtService";
 
-    public ArtManagerLocal() {}
+    private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
+            "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
+
+    /** @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
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        return new ArtShellCommand(this, mInjector.getPackageManagerLocal())
+                .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
+                        err.getFileDescriptor(), args);
+    }
+
+    /** Prints ART Service shell command help. */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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, mInjector.getArtd());
+            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) {
+            Utils.logArtdException(e);
+            return DeleteResult.create(0 /* freedBytes */);
+        }
+    }
+
+    /**
+     * 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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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);
+
+        List<Pair<DetailedDexInfo, Abi>> dexAndAbis = 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)) {
+                    dexAndAbis.add(Pair.create(dexInfo, abi));
+                }
+            }
+        }
+
+        if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+            for (SecondaryDexInfo dexInfo :
+                    mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) {
+                for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                    dexAndAbis.add(Pair.create(dexInfo, abi));
+                }
+            }
+        }
+
+        try {
+            List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
+
+            for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
+                DetailedDexInfo dexInfo = pair.first;
+                Abi abi = pair.second;
+                try {
+                    GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                            dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+                    statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                            dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
+                            abi.name(), result.compilerFilter, result.compilationReason,
+                            result.locationDebugString));
+                } catch (ServiceSpecificException e) {
+                    statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                            dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(),
+                            abi.name(), "error", "error", e.getMessage()));
+                }
+            }
+
+            return DexoptStatus.create(statuses);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            List<DexContainerFileDexoptStatus> statuses = new ArrayList<>();
+            for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) {
+                DetailedDexInfo dexInfo = pair.first;
+                Abi abi = pair.second;
+                statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(),
+                        dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), abi.name(),
+                        "error", "error", e.getMessage()));
+            }
+            return DexoptStatus.create(statuses);
+        }
+    }
+
+    /**
+     * Clear the profiles that are collected locally for the given package, including the profiles
+     * for primary and 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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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) {
+            Utils.logArtdException(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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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)
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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}.
+     *
+     * @throws RuntimeException if called during boot before the job scheduler service has started.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void unscheduleBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().unschedule();
+    }
+
+    /**
+     * Overrides the configuration of the background dexopt job. This method is thread-safe.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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}.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void startBackgroundDexoptJob() {
+        mInjector.getBackgroundDexoptJob().start();
+    }
+
+    /**
+     * Same as above, but also returns a {@link CompletableFuture}.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public CompletableFuture<BackgroundDexoptJob.Result> startBackgroundDexoptJobAndReturnFuture() {
+        return mInjector.getBackgroundDexoptJob().start();
+    }
+
+    /**
+     * Returns the running background dexopt job, or null of no background dexopt job is running.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Nullable
+    public CompletableFuture<BackgroundDexoptJob.Result> getRunningBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob().get();
+    }
+
+    /**
+     * 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}.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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);
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private ParcelFileDescriptor snapshotOrDumpAppProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName, @NonNull MergeProfileOptions options)
+            throws SnapshotProfileException {
+        try {
+            PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+            AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+            PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
+
+            List<ProfilePath> profiles = new ArrayList<>();
+
+            Pair<ProfilePath, Boolean> pair = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
+                    dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo),
+                    PrimaryDexUtils.getExternalProfiles(dexInfo),
+                    PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID,
+                            Process.SYSTEM_UID, false /* isPublic */));
+            ProfilePath refProfile = pair != null ? pair.first : null;
+
+            if (refProfile != null) {
+                profiles.add(refProfile);
+            }
+
+            profiles.addAll(
+                    PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
+
+            OutputProfile output = PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo,
+                    Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
+
+            try {
+                return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options);
+            } finally {
+                if (refProfile != null && refProfile.getTag() == ProfilePath.tmpProfilePath) {
+                    mInjector.getArtd().deleteProfile(refProfile);
+                }
+            }
+        } catch (RemoteException e) {
+            throw new SnapshotProfileException(e);
+        }
+    }
+
+    /**
+     * 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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public ParcelFileDescriptor snapshotBootImageProfile(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        if (!Constants.isBootImageProfilingEnabled()) {
+            throw new SnapshotProfileException("Boot image profiling not enabled");
+        }
+
+        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.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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).
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void dumpPackage(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        new DumpHelper(this).dumpPackage(
+                pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
+    }
+
+    /**
+     * Cleans up obsolete profiles and artifacts.
+     *
+     * This is done in a mark-and-sweep approach.
+     *
+     * @return The amount of the disk space freed by the cleanup, in bytes.
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public long cleanup(@NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        mInjector.getDexUseManager().cleanup();
+
+        try {
+            // For every primary dex container file or secondary dex container file of every app, if
+            // it has code, we keep the following types of files:
+            // - The reference profile and the current profiles, regardless of the hibernation state
+            //   of the app.
+            // - The dexopt artifacts, if they are up-to-date and the app is not hibernating.
+            // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated
+            //   but the VDEX part is still usable and the app is not hibernating.
+            List<ProfilePath> profilesToKeep = new ArrayList<>();
+            List<ArtifactsPath> artifactsToKeep = new ArrayList<>();
+            List<VdexPath> vdexFilesToKeep = new ArrayList<>();
+
+            for (PackageState pkgState : snapshot.getPackageStates().values()) {
+                if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) {
+                    continue;
+                }
+                AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+                boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd());
+                boolean keepArtifacts = !Utils.shouldSkipDexoptDueToHibernation(
+                        pkgState, mInjector.getAppHibernationManager());
+                for (DetailedPrimaryDexInfo dexInfo :
+                        PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+                    if (!dexInfo.hasCode()) {
+                        continue;
+                    }
+                    profilesToKeep.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+                    profilesToKeep.addAll(PrimaryDexUtils.getCurProfiles(
+                            mInjector.getUserManager(), pkgState, dexInfo));
+                    if (keepArtifacts) {
+                        for (Abi abi : Utils.getAllAbis(pkgState)) {
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
+                                    abi, isInDalvikCache);
+                        }
+                    }
+                }
+                for (DetailedSecondaryDexInfo dexInfo :
+                        mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
+                                pkgState.getPackageName())) {
+                    profilesToKeep.add(
+                            AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()));
+                    profilesToKeep.add(
+                            AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+                    if (keepArtifacts) {
+                        for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) {
+                            maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo,
+                                    abi, false /* isInDalvikCache */);
+                        }
+                    }
+                }
+            }
+            return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * Checks if the artifacts are up-to-date, and maybe adds them to {@code artifactsToKeep} or
+     * {@code vdexFilesToKeep} based on the result.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep,
+            @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState,
+            @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache)
+            throws RemoteException {
+        try {
+            GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus(
+                    dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext());
+            if (DexFile.isValidCompilerFilter(result.compilerFilter)) {
+                // TODO(b/263579377): This is a bit inaccurate. We may be keeping the artifacts in
+                // dalvik-cache while OatFileAssistant actually picks the ones not in dalvik-cache.
+                // However, this isn't a big problem because it is an edge case and it only causes
+                // us to delete less rather than deleting more.
+                ArtifactsPath artifacts =
+                        AidlUtils.buildArtifactsPath(dexInfo.dexPath(), abi.isa(), isInDalvikCache);
+                if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) {
+                    // Only the VDEX file is usable.
+                    vdexFilesToKeep.add(VdexPath.artifactsPath(artifacts));
+                } else {
+                    artifactsToKeep.add(artifacts);
+                }
+            }
+        } catch (ServiceSpecificException e) {
+            // Don't add the artifacts to the lists. They should be cleaned up.
+            Log.e(TAG,
+                    String.format("Failed to get dexopt status [packageName = %s, dexPath = %s, "
+                                    + "isa = %s, classLoaderContext = %s]",
+                            pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
+                            dexInfo.classLoaderContext()),
+                    e);
+        }
+    }
+
+    /**
+     * Should be used by {@link BackgroundDexoptJobService} ONLY.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    BackgroundDexoptJob getBackgroundDexoptJob() {
+        return mInjector.getBackgroundDexoptJob();
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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");
+                DexoptParams params =
+                        new DexoptParams.Builder(ReasonMapping.REASON_INACTIVE).build();
+                mInjector.getDexoptHelper().dexopt(snapshot, packages, params, cancellationSignal,
+                        executor, null /* processCallbackExecutor */, null /* progressCallback */);
+            } else {
+                Log.i(TAG,
+                        "Storage is low, but downgrading is disabled or there's nothing to "
+                                + "downgrade");
+            }
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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. */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull /* @BatchDexoptReason|REASON_INACTIVE */ String reason) {
+        var appHibernationManager = mInjector.getAppHibernationManager();
+
+        // 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())
+                                || mInjector.isLauncherPackage(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());
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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);
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @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;
+            Path emptyFile = null;
+            if (hasContent) {
+                path = output.profilePath.tmpPath;
+            } else {
+                // We cannot use /dev/null because `ParcelFileDescriptor` have an API `getStatSize`,
+                // which expects the file to be a regular file or a link, and apps may call that
+                // API.
+                emptyFile =
+                        Files.createTempFile(Paths.get(mInjector.getTempDir()), "empty", ".tmp");
+                path = emptyFile.toString();
+            }
+            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);
+            }
+
+            // The deletion is done on the open file so that only the FD keeps a reference to the
+            // file.
+            if (hasContent) {
+                mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
+            } else {
+                Files.delete(emptyFile);
+            }
+
+            return fd;
+        } catch (IOException | RemoteException e) {
+            throw new SnapshotProfileException(e);
+        }
+    }
+
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    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)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public interface DexoptDoneCallback {
+        void onDexoptDone(@NonNull DexoptResult result);
+    }
+
+    /**
+     * Represents an error that happens when snapshotting profiles.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static class SnapshotProfileException extends Exception {
+        /** @hide */
+        public SnapshotProfileException(@NonNull Throwable cause) {
+            super(cause);
+        }
+
+        /** @hide */
+        public SnapshotProfileException(@NonNull String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class Injector {
+        @Nullable private final ArtManagerLocal mArtManagerLocal;
+        @Nullable private final Context mContext;
+        @Nullable private final PackageManagerLocal mPackageManagerLocal;
+        @Nullable private final Config mConfig;
+        @Nullable private BackgroundDexoptJob mBgDexoptJob = null;
+
+        // TODO(jiakaiz): Remove @SuppressLint and check `Build.VERSION.SDK_INT >=
+        // Build.VERSION_CODES.UPSIDE_DOWN_CAKE` once the SDK is finalized.
+        @SuppressLint("NewApi")
+        Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
+            mArtManagerLocal = artManagerLocal;
+            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();
+
+                // 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;
+            }
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public Context getContext() {
+            return Objects.requireNonNull(mContext);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(mPackageManagerLocal);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        /** Returns a new {@link DexoptHelper} instance. */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public DexoptHelper getDexoptHelper() {
+            return new DexoptHelper(getContext(), getConfig());
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        /** Returns the registered {@link AppHibernationManager} instance. */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public AppHibernationManager getAppHibernationManager() {
+            return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
+        }
+
+        /**
+         * Returns the {@link BackgroundDexoptJob} instance.
+         *
+         * @throws RuntimeException if called during boot before the job scheduler service has
+         *         started.
+         */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public synchronized BackgroundDexoptJob getBackgroundDexoptJob() {
+            if (mBgDexoptJob == null) {
+                mBgDexoptJob = new BackgroundDexoptJob(mContext, mArtManagerLocal, mConfig);
+            }
+            return mBgDexoptJob;
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return Utils.isSystemUiPackage(mContext, packageName);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public boolean isLauncherPackage(@NonNull String packageName) {
+            return Utils.isLauncherPackage(mContext, packageName);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public long getCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @NonNull
+        public String getTempDir() {
+            // This is a path that system_server is known to have full access to.
+            return "/data/system";
+        }
+    }
 }
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..c9295c1
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+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)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+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..b83f4ab
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -0,0 +1,938 @@
+/*
+ * 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.ArtFlags.PriorityClassApi;
+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.annotation.Nullable;
+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.File;
+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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * This class handles ART shell commands.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public final class ArtShellCommand extends BasicShellCommandHandler {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /** The default location for profile dumps. */
+    private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman";
+
+    private final ArtManagerLocal mArtManagerLocal;
+    private final PackageManagerLocal mPackageManagerLocal;
+
+    @GuardedBy("sCancellationSignalMap")
+    @NonNull
+    private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
+
+    public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
+            @NonNull PackageManagerLocal packageManagerLocal) {
+        mArtManagerLocal = artManagerLocal;
+        mPackageManagerLocal = packageManagerLocal;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        // Apps shouldn't call ART Service shell commands, not even for dexopting themselves.
+        enforceRootOrShell();
+        PrintWriter pw = getOutPrintWriter();
+        try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) {
+            switch (cmd) {
+                case "compile":
+                    return handleCompile(pw, snapshot);
+                case "reconcile-secondary-dex-files":
+                    pw.println("Warning: 'pm reconcile-secondary-dex-files' is deprecated. It is "
+                            + "now doing nothing");
+                    return 0;
+                case "force-dex-opt":
+                    return handleForceDexopt(pw, snapshot);
+                case "bg-dexopt-job":
+                    return handleBgDexoptJob(pw, snapshot);
+                case "cancel-bg-dexopt-job":
+                    pw.println("Warning: 'pm cancel-bg-dexopt-job' is deprecated. It is now an "
+                            + "alias of 'pm bg-dexopt-job --cancel'");
+                    return handleCancelBgDexoptJob(pw);
+                case "delete-dexopt":
+                    return handleDeleteDexopt(pw, snapshot);
+                case "dump-profiles":
+                    return handleDumpProfile(pw, snapshot);
+                case "snapshot-profile":
+                    return handleSnapshotProfile(pw, snapshot);
+                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));
+            }
+        } catch (IllegalArgumentException | SnapshotProfileException e) {
+            pw.println("Error: " + e.getMessage());
+            return 1;
+        }
+    }
+
+    private int handleArtCommand(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String subcmd = getNextArgRequired();
+        switch (subcmd) {
+            case "dexopt-packages": {
+                return handleBatchDexopt(pw, snapshot);
+            }
+            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 "dump": {
+                String packageName = getNextArg();
+                if (packageName != null) {
+                    mArtManagerLocal.dumpPackage(pw, snapshot, packageName);
+                } else {
+                    mArtManagerLocal.dump(pw, snapshot);
+                }
+                return 0;
+            }
+            case "cleanup": {
+                return handleCleanup(pw, snapshot);
+            }
+            case "clear-app-profiles": {
+                mArtManagerLocal.clearAppProfiles(snapshot, getNextArgRequired());
+                pw.println("Profiles cleared");
+                return 0;
+            }
+            default:
+                pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
+                pw.println("See 'pm help' for help");
+                return 1;
+        }
+    }
+
+    private int handleCompile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        @DexoptFlags int scopeFlags = 0;
+        String reason = null;
+        String compilerFilter = null;
+        @PriorityClassApi int priorityClass = ArtFlags.PRIORITY_NONE;
+        String splitArg = null;
+        boolean force = false;
+        boolean reset = false;
+        boolean forAllPackages = false;
+        boolean legacyClearProfile = false;
+        boolean verbose = false;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    forAllPackages = true;
+                    break;
+                case "-r":
+                    reason = getNextArgRequired();
+                    break;
+                case "-m":
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-p":
+                    priorityClass = parsePriorityClass(getNextArgRequired());
+                    break;
+                case "-f":
+                    force = true;
+                    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 "--full":
+                    scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+                    break;
+                case "--split":
+                    splitArg = getNextArgRequired();
+                    break;
+                case "--reset":
+                    reset = true;
+                    break;
+                case "-c":
+                    pw.println("Warning: Flag '-c' is deprecated and usually produces undesired "
+                            + "results. Please use one of the following commands instead.");
+                    pw.println("- To clear the local profiles only, use "
+                            + "'pm art clear-app-profiles PACKAGE_NAME'. (The existing dexopt "
+                            + "artifacts will be kept, even if they are derived from the "
+                            + "profiles.)");
+                    pw.println("- To clear the local profiles and also clear the dexopt artifacts "
+                            + "that are derived from them, use 'pm compile --reset PACKAGE_NAME'. "
+                            + "(The package will be reset to the initial state as if it's newly "
+                            + "installed, which means the package will be re-dexopted if "
+                            + "necessary, and cloud profiles will be used if exist.)");
+                    pw.println("- To re-dexopt the package with no profile, use "
+                            + "'pm compile -m verify -f PACKAGE_NAME'. (The local profiles "
+                            + "will be kept but not used during the dexopt. The dexopt artifacts "
+                            + "are guaranteed to have no compiled code.)");
+                    legacyClearProfile = true;
+                    break;
+                case "--check-prof":
+                    getNextArgRequired();
+                    pw.println("Warning: Ignoring obsolete flag '--check-prof'. It is "
+                            + "unconditionally enabled now");
+                    break;
+                case "-v":
+                    verbose = true;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        List<String> packageNames = forAllPackages
+                ? List.copyOf(snapshot.getPackageStates().keySet())
+                : List.of(getNextArgRequired());
+
+        var paramsBuilder = new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE);
+        if (reason != null) {
+            if (reason.equals(ReasonMapping.REASON_INACTIVE)) {
+                pw.println("Warning: '-r inactive' produces undesired results.");
+            }
+            if (compilerFilter == null) {
+                paramsBuilder.setCompilerFilter(ReasonMapping.getCompilerFilterForReason(reason));
+            }
+            if (priorityClass == ArtFlags.PRIORITY_NONE) {
+                paramsBuilder.setPriorityClass(ReasonMapping.getPriorityClassForReason(reason));
+            }
+        }
+        if (compilerFilter != null) {
+            paramsBuilder.setCompilerFilter(compilerFilter);
+        }
+        if (priorityClass != ArtFlags.PRIORITY_NONE) {
+            paramsBuilder.setPriorityClass(priorityClass);
+        }
+        if (force) {
+            paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
+        }
+        if (splitArg != null) {
+            if (scopeFlags != 0) {
+                pw.println("Error: '--primary-dex', '--secondary-dex', "
+                        + "'--include-dependencies', or '--full' must not be set when '--split' "
+                        + "is set.");
+                return 1;
+            }
+            if (forAllPackages) {
+                pw.println("Error:  '-a' cannot be specified together with '--split'");
+                return 1;
+            }
+            scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX;
+            paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT, ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+                    .setSplitName(getSplitName(pw, snapshot, packageNames.get(0), splitArg));
+        }
+        if (scopeFlags != 0) {
+            paramsBuilder.setFlags(scopeFlags,
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        } else {
+            paramsBuilder.setFlags(
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                    ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                            | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        }
+        if (forAllPackages) {
+            // We'll iterate over all packages anyway.
+            paramsBuilder.setFlags(0, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+        }
+
+        if (reset) {
+            return resetPackages(pw, snapshot, packageNames, verbose);
+        } else {
+            if (legacyClearProfile) {
+                // For compat only. Combining this with dexopt usually produces in undesired
+                // results.
+                for (String packageName : packageNames) {
+                    mArtManagerLocal.clearAppProfiles(snapshot, packageName);
+                }
+            }
+            return dexoptPackages(pw, snapshot, packageNames, paramsBuilder.build(), verbose);
+        }
+    }
+
+    private int handleForceDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        pw.println("Warning: 'pm force-dex-opt' is deprecated. Please use 'pm compile "
+                + "-f PACKAGE_NAME' instead");
+        return dexoptPackages(pw, snapshot, List.of(getNextArgRequired()),
+                new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE)
+                        .setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE)
+                        .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX
+                                        | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+                                ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX
+                                        | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+                        .build(),
+                false /* verbose */);
+    }
+
+    private int handleBgDexoptJob(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String opt = getNextOption();
+        if (opt == null) {
+            List<String> packageNames = new ArrayList<>();
+            String arg;
+            while ((arg = getNextArg()) != null) {
+                packageNames.add(arg);
+            }
+            if (!packageNames.isEmpty()) {
+                pw.println("Warning: Running 'pm bg-dexopt-job' with package names is deprecated. "
+                        + "Please use 'pm compile -r bg-dexopt PACKAGE_NAME' instead");
+                return dexoptPackages(pw, snapshot, packageNames,
+                        new DexoptParams.Builder(ReasonMapping.REASON_BG_DEXOPT).build(),
+                        false /* verbose */);
+            }
+
+            CompletableFuture<BackgroundDexoptJob.Result> runningJob =
+                    mArtManagerLocal.getRunningBackgroundDexoptJob();
+            if (runningJob != null) {
+                pw.println("Another job already running. Waiting for it to finish... To cancel it, "
+                        + "run 'pm bg-dexopt-job --cancel'. in a separate shell.");
+                pw.flush();
+                Utils.getFuture(runningJob);
+            }
+            CompletableFuture<BackgroundDexoptJob.Result> future =
+                    mArtManagerLocal.startBackgroundDexoptJobAndReturnFuture();
+            pw.println("Job running...  To cancel it, run 'pm bg-dexopt-job --cancel'. in a "
+                    + "separate shell.");
+            pw.flush();
+            BackgroundDexoptJob.Result result = Utils.getFuture(future);
+            if (result instanceof BackgroundDexoptJob.CompletedResult) {
+                var completedResult = (BackgroundDexoptJob.CompletedResult) result;
+                if (completedResult.dexoptResult().getFinalStatus()
+                        == DexoptResult.DEXOPT_CANCELLED) {
+                    pw.println("Job cancelled. See logs for details");
+                } else {
+                    pw.println("Job finished. See logs for details");
+                }
+            } else if (result instanceof BackgroundDexoptJob.FatalErrorResult) {
+                // Never expected.
+                pw.println("Job encountered a fatal error");
+            }
+            return 0;
+        }
+        switch (opt) {
+            case "--cancel": {
+                return handleCancelBgDexoptJob(pw);
+            }
+            case "--enable": {
+                // This operation requires the uid to be "system" (1000).
+                long identityToken = Binder.clearCallingIdentity();
+                try {
+                    mArtManagerLocal.scheduleBackgroundDexoptJob();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                pw.println("Background dexopt job enabled");
+                return 0;
+            }
+            case "--disable": {
+                // This operation requires the uid to be "system" (1000).
+                long identityToken = Binder.clearCallingIdentity();
+                try {
+                    mArtManagerLocal.unscheduleBackgroundDexoptJob();
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+                pw.println("Background dexopt job disabled");
+                return 0;
+            }
+            default:
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+        }
+    }
+
+    private int handleCancelBgDexoptJob(@NonNull PrintWriter pw) {
+        mArtManagerLocal.cancelBackgroundDexoptJob();
+        pw.println("Background dexopt job cancelled");
+        return 0;
+    }
+
+    private int handleCleanup(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        long freedBytes = mArtManagerLocal.cleanup(snapshot);
+        pw.printf("Freed %d bytes\n", freedBytes);
+        return 0;
+    }
+
+    private int handleDeleteDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        DeleteResult result =
+                mArtManagerLocal.deleteDexoptArtifacts(snapshot, getNextArgRequired());
+        pw.printf("Freed %d bytes\n", result.getFreedBytes());
+        return 0;
+    }
+
+    private int handleSnapshotProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        String splitName = null;
+        String codePath = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--split":
+                    splitName = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArgRequired();
+
+        if ("--code-path".equals(getNextOption())) {
+            pw.println("Warning: Specifying a split using '--code-path' is deprecated. Please use "
+                    + "'--split SPLIT_NAME' instead");
+            pw.println("Tip: '--split SPLIT_NAME' must be passed before the package name");
+            codePath = getNextArgRequired();
+        }
+
+        if (splitName != null && codePath != null) {
+            pw.println("Error: '--split' and '--code-path' cannot be both specified");
+            return 1;
+        }
+
+        if (packageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
+            if (splitName != null) {
+                pw.println("Error: '--split' must not be specified for boot image profile");
+                return 1;
+            }
+            if (codePath != null) {
+                pw.println("Error: '--code-path' must not be specified for boot image profile");
+                return 1;
+            }
+            return handleSnapshotBootProfile(pw, snapshot);
+        }
+
+        if (splitName != null && splitName.isEmpty()) {
+            splitName = null;
+        }
+        if (codePath != null) {
+            splitName = getSplitNameByFullPath(snapshot, packageName, codePath);
+        }
+
+        return handleSnapshotAppProfile(pw, snapshot, packageName, splitName);
+    }
+
+    private int handleSnapshotBootProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        String outputRelativePath = "android.prof";
+        ParcelFileDescriptor fd = mArtManagerLocal.snapshotBootImageProfile(snapshot);
+        writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+        return 0;
+    }
+
+    private int handleSnapshotAppProfile(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @Nullable String splitName) throws SnapshotProfileException {
+        String outputRelativePath = String.format("%s%s.prof", packageName,
+                splitName != null ? String.format("-split_%s.apk", splitName) : "");
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName);
+        writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+        return 0;
+    }
+
+    private int handleDumpProfile(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+            throws SnapshotProfileException {
+        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);
+        try (var tracing = new Utils.Tracing("dump profiles")) {
+            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-profile". This
+                // is to match the behavior of the legacy PM shell command.
+                String outputRelativePath =
+                        String.format("%s-%s.prof.txt", packageName, profileName);
+                ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile(
+                        snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods);
+                writeProfileFdContentsToFile(pw, fd, outputRelativePath);
+            }
+        }
+        return 0;
+    }
+
+    private int handleBatchDexopt(
+            @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+        String reason = null;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-r":
+                    reason = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+        if (reason == null) {
+            pw.println("Error: '-r REASON' is required");
+            return 1;
+        }
+        if (!ReasonMapping.BATCH_DEXOPT_REASONS.contains(reason)) {
+            pw.printf("Error: Invalid batch dexopt reason '%s'. Valid values are: %s\n", reason,
+                    ReasonMapping.BATCH_DEXOPT_REASONS);
+            return 1;
+        }
+        DexoptResult result;
+        ExecutorService progressCallbackExecutor = Executors.newSingleThreadExecutor();
+        try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
+            result = mArtManagerLocal.dexoptPackages(
+                    snapshot, reason, signal.get(), progressCallbackExecutor, progress -> {
+                        pw.println(String.format("Dexopting apps: %d%%", progress.getPercentage()));
+                        pw.flush();
+                    });
+            Utils.executeAndWait(progressCallbackExecutor, () -> {
+                printDexoptResult(pw, result, true /* verbose */, true /* multiPackage */);
+            });
+        } finally {
+            progressCallbackExecutor.shutdown();
+        }
+        return 0;
+    }
+
+    @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) {
+        pw.println("compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f]");
+        pw.println("    [--primary-dex] [--secondary-dex] [--include-dependencies] [--full]");
+        pw.println("    [--split SPLIT_NAME] [--reset] [-a | PACKAGE_NAME]");
+        pw.println("  Dexopt a package or all packages.");
+        pw.println("  Options:");
+        pw.println("    -a Dexopt all packages");
+        pw.println("    -r Set the compiler filter and the priority based on the given");
+        pw.println("       compilation reason.");
+        pw.println("       Available options: 'first-boot', 'boot-after-ota',");
+        pw.println("       'boot-after-mainline-update', 'install', 'bg-dexopt', 'cmdline'.");
+        pw.println("    -m Set the target compiler filter. The filter actually used may be");
+        pw.println("       different, e.g. 'speed-profile' without profiles present may result in");
+        pw.println("       'verify' being used instead. If not specified, this defaults to the");
+        pw.println("       value given by -r, or the system property 'pm.dexopt.cmdline'.");
+        pw.println("       Available options (in descending order): 'speed', 'speed-profile',");
+        pw.println("       'verify'.");
+        pw.println("    -p Set the priority of the operation, which determines the resource usage");
+        pw.println("       and the process priority. If not specified, this defaults to");
+        pw.println("       the value given by -r, or 'PRIORITY_INTERACTIVE'.");
+        pw.println("       Available options (in descending order): 'PRIORITY_BOOT',");
+        pw.println("       'PRIORITY_INTERACTIVE_FAST', 'PRIORITY_INTERACTIVE',");
+        pw.println("       'PRIORITY_BACKGROUND'.");
+        pw.println("    -f Force dexopt, also when the compiler filter being applied is not");
+        pw.println("       better than that of the current dexopt artifacts for a package.");
+        pw.println("    --reset Reset the dexopt state of the package as if the package is newly");
+        pw.println("       installed.");
+        pw.println("       More specifically, it clears reference profiles, current profiles, and");
+        pw.println("       any code compiled from those local profiles. If there is an external");
+        pw.println("       profile (e.g., a cloud profile), the code compiled from that profile");
+        pw.println("       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("    -v Verbose mode. This mode prints detailed results.");
+        pw.println("  Scope options:");
+        pw.println("    --primary-dex Dexopt primary dex files only (all APKs that are installed");
+        pw.println("      as part of the package, including the base APK and all other split");
+        pw.println("      APKs).");
+        pw.println("    --secondary-dex Dexopt secondary dex files only (APKs/JARs that the app");
+        pw.println("      puts in its own data directory at runtime and loads with custom");
+        pw.println("      classloaders).");
+        pw.println("    --include-dependencies Include dependency packages (dependencies that are");
+        pw.println("      declared by the app with <uses-library> tags and transitive");
+        pw.println("      dependencies). This option can only be used together with");
+        pw.println("      '--primary-dex' or '--secondary-dex'.");
+        pw.println("    --full Dexopt all above. (Recommended)");
+        pw.println("    --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an empty");
+        pw.println("      string, only dexopt the base APK.");
+        pw.println("      Tip: To pass an empty string, use a pair of quotes (\"\").");
+        pw.println("      When this option is set, '--primary-dex', '--secondary-dex',");
+        pw.println("      '--include-dependencies', '--full', and '-a' must not be set.");
+        pw.println("    Note: If none of the scope options above are set, the scope defaults to");
+        pw.println("    '--primary-dex --include-dependencies'.");
+        pw.println();
+        pw.println("delete-dexopt PACKAGE_NAME");
+        pw.println("  Delete the dexopt artifacts of both primary dex files and secondary dex");
+        pw.println("  files of a package.");
+        pw.println();
+        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 and waits for");
+        pw.println("    it to finish. If a job is already started either automatically by the");
+        pw.println("    system or through this command, it will wait for the running job to");
+        pw.println("    finish and then start a new one.");
+        pw.println("  Different from 'pm compile -r bg-dexopt -a', the behavior of this command");
+        pw.println("  is the same as a real background dexopt job. Specifically,");
+        pw.println("    - It only dexopts a subset of apps determined by either the system's");
+        pw.println("      default logic based on app usage data or the custom logic specified by");
+        pw.println("      the 'ArtManagerLocal.setBatchDexoptStartCallback' Java API.");
+        pw.println("    - It runs dexopt in parallel, where the concurrency setting is specified");
+        pw.println("      by the system property 'pm.dexopt.bg-dexopt.concurrency'.");
+        pw.println("    - If the storage is low, it also downgrades unused apps.");
+        pw.println("    - It also cleans up obsolete files.");
+        pw.println("  Options:");
+        pw.println("    --cancel Cancel any currently running background dexopt job immediately.");
+        pw.println("      This cancels jobs started either automatically by the system or through");
+        pw.println("      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 is");
+        pw.println("      running, it will be cancelled immediately. Does not affect jobs started");
+        pw.println("      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("  When a list of package names is passed, this command does NOT start a real");
+        pw.println("  background dexopt job. Instead, it dexopts the given packages sequentially.");
+        pw.println("  This usage is deprecated. Please use 'pm compile -r bg-dexopt PACKAGE_NAME'");
+        pw.println("  instead.");
+        pw.println();
+        pw.println("snapshot-profile [android | [--split SPLIT_NAME] PACKAGE_NAME]");
+        pw.println("  Snapshot the boot image profile or the app profile and save it to");
+        pw.println("  '" + PROFILE_DEBUG_LOCATION + "'.");
+        pw.println("  If 'android' is passed, the command snapshots the boot image profile, and");
+        pw.println("  the output filename is 'android.prof'.");
+        pw.println("  If a package name is passed, the command snapshots the app profile.");
+        pw.println("  Options:");
+        pw.println("    --split SPLIT_NAME If specified, the command snapshots the profile of the");
+        pw.println("      given split, and the output filename is");
+        pw.println("      'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'.");
+        pw.println("      If not specified, the command snapshots the profile of the base APK,");
+        pw.println("      and the output filename is 'PACKAGE_NAME.prof'");
+        pw.println();
+        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'");
+        pw.println();
+        pw.println("art SUB_COMMAND [ARGS]...");
+        pw.println("  Run ART Service commands");
+        pw.println();
+        pw.println("  Supported sub-commands:");
+        pw.println();
+        pw.println("  cancel JOB_ID");
+        pw.println("    Cancel a job started by a shell command. This doesn't apply to background");
+        pw.println("    jobs.");
+        pw.println();
+        pw.println("  clear-app-profiles PACKAGE_NAME");
+        pw.println("    Clear the profiles that are collected locally for the given package,");
+        pw.println("    including the profiles for primary and secondary dex files. More");
+        pw.println("    specifically, this command clears reference profiles and current");
+        pw.println("    profiles. External profiles (e.g., cloud profiles) will be kept.");
+        pw.println();
+        pw.println("  cleanup");
+        pw.println("    Cleanup obsolete files, such as dexopt artifacts that are outdated or");
+        pw.println("    correspond to dex container files that no longer exist.");
+        pw.println();
+        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();
+        pw.println("  dexopt-packages -r REASON");
+        pw.println("    Run batch dexopt for the given reason.");
+        pw.println("    Valid values for REASON: 'first-boot', 'boot-after-ota',");
+        pw.println("    'boot-after-mainline-update', 'bg-dexopt'");
+        pw.println("    This command is different from 'pm compile -r REASON -a'. For example, it");
+        pw.println("    only dexopts a subset of apps, and it runs dexopt in parallel. See the");
+        pw.println("    API documentation for 'ArtManagerLocal.dexoptPackages' for details.");
+    }
+
+    private void enforceRootOrShell() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+            throw new SecurityException("ART service shell commands need root or shell access");
+        }
+    }
+
+    @PriorityClassApi
+    int parsePriorityClass(@NonNull String priorityClass) {
+        switch (priorityClass) {
+            case "PRIORITY_BOOT":
+                return ArtFlags.PRIORITY_BOOT;
+            case "PRIORITY_INTERACTIVE_FAST":
+                return ArtFlags.PRIORITY_INTERACTIVE_FAST;
+            case "PRIORITY_INTERACTIVE":
+                return ArtFlags.PRIORITY_INTERACTIVE;
+            case "PRIORITY_BACKGROUND":
+                return ArtFlags.PRIORITY_BACKGROUND;
+            default:
+                throw new IllegalArgumentException("Unknown priority " + priorityClass);
+        }
+    }
+
+    @Nullable
+    private String getSplitName(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+            @NonNull String splitArg) {
+        if (splitArg.isEmpty()) {
+            return null; // Base APK.
+        }
+
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (splitArg.equals(dexInfo.splitName())) {
+                return splitArg;
+            }
+        }
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (splitArg.equals(new File(dexInfo.dexPath()).getName())) {
+                pw.println("Warning: Specifying a split using a filename is deprecated. Please "
+                        + "use a split name (or an empty string for the base APK) instead");
+                return dexInfo.splitName();
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg));
+    }
+
+    @Nullable
+    private String getSplitNameByFullPath(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull String packageName, @NonNull String fullPath) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg);
+
+        for (PrimaryDexInfo dexInfo : dexInfoList) {
+            if (fullPath.equals(dexInfo.dexPath())) {
+                return dexInfo.splitName();
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath));
+    }
+
+    private int resetPackages(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, boolean verbose) {
+        try (var signal = new WithCancellationSignal(pw, verbose)) {
+            for (String packageName : packageNames) {
+                DexoptResult result =
+                        mArtManagerLocal.resetDexoptStatus(snapshot, packageName, signal.get());
+                printDexoptResult(pw, result, verbose, packageNames.size() > 1);
+            }
+        }
+        return 0;
+    }
+
+    private int dexoptPackages(@NonNull PrintWriter pw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose) {
+        try (var signal = new WithCancellationSignal(pw, verbose)) {
+            for (String packageName : packageNames) {
+                DexoptResult result =
+                        mArtManagerLocal.dexoptPackage(snapshot, packageName, params, signal.get());
+                printDexoptResult(pw, result, verbose, packageNames.size() > 1);
+            }
+        }
+        return 0;
+    }
+
+    @NonNull
+    private String dexoptResultStatusToSimpleString(@DexoptResultStatus int status) {
+        return (status == DexoptResult.DEXOPT_SKIPPED || status == DexoptResult.DEXOPT_PERFORMED)
+                ? "Success"
+                : "Failure";
+    }
+
+    private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result,
+            boolean verbose, boolean multiPackage) {
+        for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) {
+            if (verbose) {
+                pw.printf("[%s]\n", packageResult.getPackageName());
+                for (DexContainerFileDexoptResult fileResult :
+                        packageResult.getDexContainerFileDexoptResults()) {
+                    pw.println(fileResult);
+                }
+            } else if (multiPackage) {
+                pw.printf("[%s] %s\n", packageResult.getPackageName(),
+                        dexoptResultStatusToSimpleString(packageResult.getStatus()));
+            }
+        }
+
+        if (verbose) {
+            pw.println("Final Status: "
+                    + DexoptResult.dexoptResultStatusToString(result.getFinalStatus()));
+        } else if (!multiPackage) {
+            // Multi-package result is printed by the loop above.
+            pw.println(dexoptResultStatusToSimpleString(result.getFinalStatus()));
+        }
+
+        pw.flush();
+    }
+
+    private void writeProfileFdContentsToFile(@NonNull PrintWriter pw,
+            @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);
+            pw.printf("Profile saved to '%s'\n", outputPath);
+        } 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, boolean verbose) {
+            mJobId = UUID.randomUUID().toString();
+            if (verbose) {
+                pw.printf(
+                        "Job running. To cancel it, run 'pm art cancel %s' in a separate shell.\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..b297326
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
@@ -0,0 +1,325 @@
+/*
+ * 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.Build;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+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.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class BackgroundDexoptJob {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /**
+     * "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(() -> {
+            try (var tracing = new Utils.TracingWithTimingLogging(TAG, "jobExecution")) {
+                return run(mCancellationSignal);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Fatal error", e);
+                return new FatalErrorResult();
+            } finally {
+                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");
+    }
+
+    @Nullable
+    public synchronized CompletableFuture<Result> get() {
+        return mRunningJob;
+    }
+
+    @NonNull
+    private CompletedResult run(@NonNull CancellationSignal cancellationSignal) {
+        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 */);
+
+            // For simplicity, we don't support cancelling the following operation in the middle.
+            // This is fine because it typically takes only a few seconds.
+            if (!cancellationSignal.isCanceled()) {
+                // We do the cleanup after dexopt so that it doesn't affect the `getSizeBeforeBytes`
+                // field in the result that we send to callbacks. Admittedly, this will cause us to
+                // lose some chance to dexopt when the storage is very low, but it's fine because we
+                // can still dexopt in the next run.
+                long freedBytes = mInjector.getArtManagerLocal().cleanup(snapshot);
+                Log.i(TAG, String.format("Freed %d bytes", freedBytes));
+            }
+        }
+        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 Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+
+        @NonNull
+        public Config getConfig() {
+            return mConfig;
+        }
+
+        @NonNull
+        public JobScheduler getJobScheduler() {
+            return Objects.requireNonNull(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..41425ee
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * Entry point for the callback from the job scheduler. This class is instantiated by the system
+ * automatically.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+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..f4c6e07
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Constants.java
@@ -0,0 +1,66 @@
+/*
+ * 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.os.SystemProperties;
+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);
+    }
+
+    public static boolean isBootImageProfilingEnabled() {
+        boolean profileBootClassPath = SystemProperties.getBoolean(
+                "persist.device_config.runtime_native_boot.profilebootclasspath",
+                SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false /* def */));
+        return Build.isDebuggable() && profileBootClassPath;
+    }
+}
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..153e83b
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -0,0 +1,1162 @@
+/*
+ * 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.AndroidPackageSplit;
+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.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Function;
+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)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DexUseManagerLocal {
+    private static final String TAG = ArtManagerLocal.TAG;
+    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;
+    @GuardedBy("mLock")
+    @NonNull
+    private SecondaryDexLocationManager mSecondaryDexLocationManager =
+            new SecondaryDexLocationManager();
+
+    /**
+     * 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
+     */
+    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,
+                    (pkgState) -> isOwningPackageForPrimaryDex(pkgState, dexPath));
+            if (owningPackageName != null) {
+                addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+                        lastUsedAtMs);
+                continue;
+            }
+            Path path = Paths.get(dexPath);
+            synchronized (mLock) {
+                owningPackageName = findOwningPackage(snapshot, loadingPackageName,
+                        (pkgState) -> isOwningPackageForSecondaryDexLocked(pkgState, path));
+            }
+            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 Function<PackageState, 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)) {
+            return loadingPkgState.getPackageName();
+        }
+
+        for (PackageState pkgState : snapshot.getPackageStates().values()) {
+            if (predicate.apply(pkgState)) {
+                return pkgState.getPackageName();
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isOwningPackageForPrimaryDex(
+            @NonNull PackageState pkgState, @NonNull String dexPath) {
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+        List<AndroidPackageSplit> splits = pkg.getSplits();
+        for (int i = 0; i < splits.size(); i++) {
+            if (splits.get(i).getPath().equals(dexPath)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isOwningPackageForSecondaryDexLocked(
+            @NonNull PackageState pkgState, @NonNull Path dexPath) {
+        UserHandle userHandle = Binder.getCallingUserHandle();
+        List<Path> locations = mSecondaryDexLocationManager.getLocations(pkgState, userHandle);
+        for (int i = 0; i < locations.size(); i++) {
+            if (dexPath.startsWith(locations.get(i))) {
+                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;
+        }
+    }
+
+    /** @hide */
+    @Nullable
+    public String getSecondaryClassLoaderContext(
+            @NonNull String owningPackageName, @NonNull String dexFile, @NonNull DexLoader loader) {
+        synchronized (mLock) {
+            return Optional
+                    .ofNullable(mDexUse.mPackageDexUseByOwningPackageName.get(owningPackageName))
+                    .map(packageDexUse -> packageDexUse.mSecondaryDexUseByDexFile.get(dexFile))
+                    .map(secondaryDexUse -> secondaryDexUse.mRecordByLoader.get(loader))
+                    .map(record -> record.mClassLoaderContext)
+                    .orElse(null);
+        }
+    }
+
+    /**
+     * Cleans up obsolete information about dex files and packages that no longer exist.
+     *
+     * @hide
+     */
+    public void cleanup() {
+        Set<String> packageNames = mInjector.getAllPackageNames();
+        Map<String, Integer> dexFileVisibilityByName = new HashMap<>();
+
+        // Scan the data in two passes to avoid holding the lock during I/O.
+        synchronized (mLock) {
+            for (PackageDexUse packageDexUse : mDexUse.mPackageDexUseByOwningPackageName.values()) {
+                for (String dexFile : packageDexUse.mPrimaryDexUseByDexFile.keySet()) {
+                    dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
+                }
+                for (String dexFile : packageDexUse.mSecondaryDexUseByDexFile.keySet()) {
+                    dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND);
+                }
+            }
+        }
+
+        for (var entry : dexFileVisibilityByName.entrySet()) {
+            entry.setValue(getDexFileVisibility(entry.getKey()));
+        }
+
+        synchronized (mLock) {
+            for (var it = mDexUse.mPackageDexUseByOwningPackageName.entrySet().iterator();
+                    it.hasNext();) {
+                Map.Entry<String, PackageDexUse> entry = it.next();
+                String owningPackageName = entry.getKey();
+                PackageDexUse packageDexUse = entry.getValue();
+
+                if (!packageNames.contains(owningPackageName)) {
+                    // Remove information about the non-existing owning package.
+                    it.remove();
+                    mRevision++;
+                    continue;
+                }
+
+                cleanupPrimaryDexUsesLocked(packageDexUse.mPrimaryDexUseByDexFile, packageNames,
+                        dexFileVisibilityByName, owningPackageName);
+
+                cleanupSecondaryDexUsesLocked(packageDexUse.mSecondaryDexUseByDexFile, packageNames,
+                        dexFileVisibilityByName, owningPackageName);
+
+                if (packageDexUse.mPrimaryDexUseByDexFile.isEmpty()
+                        && packageDexUse.mSecondaryDexUseByDexFile.isEmpty()) {
+                    it.remove();
+                    mRevision++;
+                }
+            }
+        }
+
+        maybeSaveAsync();
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupPrimaryDexUsesLocked(@NonNull Map<String, PrimaryDexUse> primaryDexUses,
+            @NonNull Set<String> packageNames,
+            @NonNull Map<String, Integer> dexFileVisibilityByName,
+            @NonNull String owningPackageName) {
+        for (var it = primaryDexUses.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<String, PrimaryDexUse> entry = it.next();
+            String dexFile = entry.getKey();
+            PrimaryDexUse primaryDexUse = entry.getValue();
+
+            if (!dexFileVisibilityByName.containsKey(dexFile)) {
+                // This can only happen when the file is added after the first pass. We can just
+                // keep it as-is and check it in the next `cleanup` run.
+                continue;
+            }
+
+            @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
+
+            if (visibility == FileVisibility.NOT_FOUND) {
+                // Remove information about the non-existing dex files.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            cleanupRecordsLocked(
+                    primaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
+
+            if (primaryDexUse.mRecordByLoader.isEmpty()) {
+                it.remove();
+                mRevision++;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupSecondaryDexUsesLocked(
+            @NonNull Map<String, SecondaryDexUse> secondaryDexUses,
+            @NonNull Set<String> packageNames,
+            @NonNull Map<String, Integer> dexFileVisibilityByName,
+            @NonNull String owningPackageName) {
+        for (var it = secondaryDexUses.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<String, SecondaryDexUse> entry = it.next();
+            String dexFile = entry.getKey();
+            SecondaryDexUse secondaryDexUse = entry.getValue();
+
+            if (!dexFileVisibilityByName.containsKey(dexFile)) {
+                // This can only happen when the file is added after the first pass. We can just
+                // keep it as-is and check it in the next `cleanup` run.
+                continue;
+            }
+
+            @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile);
+
+            // Remove information about non-existing dex files.
+            if (visibility == FileVisibility.NOT_FOUND) {
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            cleanupRecordsLocked(
+                    secondaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName);
+
+            if (secondaryDexUse.mRecordByLoader.isEmpty()) {
+                it.remove();
+                mRevision++;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupRecordsLocked(@NonNull Map<DexLoader, ?> records,
+            @NonNull Set<String> packageNames, @FileVisibility int visibility,
+            @NonNull String owningPackageName) {
+        for (var it = records.entrySet().iterator(); it.hasNext();) {
+            Map.Entry<DexLoader, ?> entry = it.next();
+            DexLoader loader = entry.getKey();
+
+            if (!packageNames.contains(loader.loadingPackageName())) {
+                // Remove information about the non-existing loading package.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+
+            if (visibility == FileVisibility.NOT_OTHER_READABLE
+                    && isLoaderOtherApp(loader, owningPackageName)) {
+                // The visibility must have changed since the last load. The loader cannot load this
+                // dex file anymore.
+                it.remove();
+                mRevision++;
+                continue;
+            }
+        }
+    }
+
+    /**
+     * 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 implements DetailedDexInfo {
+        // 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 implements Comparable<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();
+
+        @Override
+        @NonNull
+        public String toString() {
+            return loadingPackageName() + (isolatedProcess() ? " (isolated)" : "");
+        }
+
+        @Override
+        public int compareTo(DexLoader o) {
+            return Comparator.comparing(DexLoader::loadingPackageName)
+                    .thenComparing(DexLoader::isolatedProcess)
+                    .compare(this, o);
+        }
+    }
+
+    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);
+        }
+    }
+
+    // TODO(b/278697552): Consider removing the cache or moving it to `Environment`.
+    static class SecondaryDexLocationManager {
+        private @NonNull Map<CacheKey, CacheValue> mCache = new HashMap<>();
+
+        public @NonNull List<Path> getLocations(
+                @NonNull PackageState pkgState, @NonNull UserHandle userHandle) {
+            AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+            UUID storageUuid = pkg.getStorageUuid();
+            String packageName = pkgState.getPackageName();
+
+            CacheKey cacheKey = CacheKey.create(packageName, userHandle);
+            CacheValue cacheValue = mCache.get(cacheKey);
+            if (cacheValue != null && cacheValue.storageUuid().equals(storageUuid)) {
+                return cacheValue.locations();
+            }
+
+            File ceDir = Environment.getDataCePackageDirectoryForUser(
+                    storageUuid, userHandle, packageName);
+            File deDir = Environment.getDataDePackageDirectoryForUser(
+                    storageUuid, userHandle, packageName);
+            List<Path> locations = List.of(ceDir.toPath(), deDir.toPath());
+            mCache.put(cacheKey, CacheValue.create(locations, storageUuid));
+            return locations;
+        }
+
+        @Immutable
+        @AutoValue
+        abstract static class CacheKey {
+            static CacheKey create(@NonNull String packageName, @NonNull UserHandle userHandle) {
+                return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheKey(
+                        packageName, userHandle);
+            }
+
+            abstract @NonNull String packageName();
+
+            abstract @NonNull UserHandle userHandle();
+        }
+
+        @Immutable
+        @AutoValue
+        abstract static class CacheValue {
+            static CacheValue create(@NonNull List<Path> locations, @NonNull UUID storageUuid) {
+                return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheValue(
+                        locations, storageUuid);
+            }
+
+            abstract @NonNull List<Path> locations();
+
+            abstract @NonNull UUID storageUuid();
+        }
+    }
+
+    /**
+     * 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();
+            getPackageManagerLocal();
+        }
+
+        @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;
+        }
+
+        @NonNull
+        public Set<String> getAllPackageNames() {
+            try (PackageManagerLocal.UnfilteredSnapshot snapshot =
+                            getPackageManagerLocal().withUnfilteredSnapshot()) {
+                return new HashSet<>(snapshot.getPackageStates().keySet());
+            }
+        }
+
+        @NonNull
+        private PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+    }
+}
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..c04a981
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java
@@ -0,0 +1,375 @@
+/*
+ * 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.Build;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+import androidx.annotation.RequiresApi;
+
+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.Function;
+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
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DexoptHelper {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    /**
+     * 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<>();
+
+            // Child threads will set their own listeners on the cancellation signal, so we must
+            // create a separate cancellation signal for each of them so that the listeners don't
+            // overwrite each other.
+            List<CancellationSignal> childCancellationSignals =
+                    pkgStates.stream()
+                            .map(pkgState -> new CancellationSignal())
+                            .collect(Collectors.toList());
+            cancellationSignal.setOnCancelListener(() -> {
+                for (CancellationSignal childCancellationSignal : childCancellationSignals) {
+                    childCancellationSignal.cancel();
+                }
+            });
+
+            for (int i = 0; i < pkgStates.size(); i++) {
+                PackageState pkgState = pkgStates.get(i);
+                CancellationSignal childCancellationSignal = childCancellationSignals.get(i);
+                futures.add(CompletableFuture.supplyAsync(() -> {
+                    return dexoptPackage(pkgState, params, childCancellationSignal);
+                }, 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);
+            // Make sure nothing leaks even if the caller holds `cancellationSignal` forever.
+            cancellationSignal.setOnCancelListener(null);
+        }
+    }
+
+    /**
+     * 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<>();
+        Function<Integer, PackageDexoptResult> createResult = (packageLevelStatus)
+                -> PackageDexoptResult.create(
+                        pkgState.getPackageName(), results, packageLevelStatus);
+
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        if (!canDexoptPackage(pkgState)) {
+            return createResult.apply(null /* packageLevelStatus */);
+        }
+
+        if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+            // Throws if the split is not found.
+            PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
+        }
+
+        try (var tracing = new Utils.Tracing("dexopt")) {
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.apply(DexoptResult.DEXOPT_CANCELLED);
+                }
+
+                results.addAll(
+                        mInjector.getPrimaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+
+            if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+                if (cancellationSignal.isCanceled()) {
+                    return createResult.apply(DexoptResult.DEXOPT_CANCELLED);
+                }
+
+                results.addAll(
+                        mInjector.getSecondaryDexopter(pkgState, pkg, params, cancellationSignal)
+                                .dexopt());
+            }
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return createResult.apply(DexoptResult.DEXOPT_FAILED);
+        }
+
+        return createResult.apply(null /* packageLevelStatus */);
+    }
+
+    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 && !library.isNative()
+                    && !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.
+            getAppHibernationManager();
+            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);
+        }
+
+        @NonNull
+        public AppHibernationManager getAppHibernationManager() {
+            return Objects.requireNonNull(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..446d948
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -0,0 +1,713 @@
+/*
+ * 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.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+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 androidx.annotation.RequiresApi;
+
+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.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.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** @hide */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+    private static final List<String> ART_PACKAGE_NAMES =
+            List.of("com.google.android.art", "com.android.art", "com.google.android.go.art");
+
+    @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<>();
+
+        boolean isInDalvikCache = isInDalvikCache();
+
+        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 {
+                        var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(),
+                                abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow);
+                        Log.i(TAG,
+                                String.format("Dexopt result: [packageName = %s] %s",
+                                        mPkgState.getPackageName(), result));
+                        results.add(result);
+                        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;
+            }
+        }
+
+        if (mInjector.isLauncherPackage(mPkgState.getPackageName())) {
+            return "speed-profile";
+        }
+
+        // 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;
+    }
+
+    /** @see Utils#getOrInitReferenceProfile */
+    @Nullable
+    private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException {
+        return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
+                buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo),
+                buildOutputProfile(dexInfo, true /* isPublic */));
+    }
+
+    @Nullable
+    private ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo) throws RemoteException {
+        return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
+                getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */));
+    }
+
+    @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();
+        dexoptOptions.comments = String.format(
+                "app-version-name:%s,app-version-code:%d,art-version:%d", mPkg.getVersionName(),
+                mPkg.getLongVersionCode(), mInjector.getArtVersion());
+        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
+                    | DexoptTrigger.NEED_EXTRACTION;
+        }
+
+        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 | DexoptTrigger.NEED_EXTRACTION;
+        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() throws RemoteException;
+
+    /** 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 list of external profiles (e.g., a DM profile) that the reference profile can be
+     * initialized from, in the order of preference.
+     */
+    @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo);
+
+    /** 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 Utils.isSystemUiPackage(mContext, packageName);
+        }
+
+        public boolean isLauncherPackage(@NonNull String packageName) {
+            return Utils.isLauncherPackage(mContext, packageName);
+        }
+
+        @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));
+        }
+
+        @NonNull
+        private PackageManagerLocal getPackageManagerLocal() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(PackageManagerLocal.class));
+        }
+
+        public long getArtVersion() {
+            try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) {
+                Map<String, PackageState> packageStates = snapshot.getPackageStates();
+                for (String artPackageName : ART_PACKAGE_NAMES) {
+                    PackageState pkgState = packageStates.get(artPackageName);
+                    if (pkgState != null) {
+                        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+                        return pkg.getLongVersionCode();
+                    }
+                }
+            }
+            return -1;
+        }
+    }
+}
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..2a640ec
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DumpHelper.java
@@ -0,0 +1,273 @@
+/*
+ * 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 android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+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.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dump.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DumpHelper {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    @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) {
+        snapshot.getPackageStates()
+                .values()
+                .stream()
+                .sorted(Comparator.comparing(PackageState::getPackageName))
+                .forEach(pkgState -> 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. They are ordered by their split indexes.
+        var primaryStatusesByDexPath =
+                new LinkedHashMap<String, List<DexContainerFileDexoptStatus>>();
+        // Use TreeMap to force lexicographical order.
+        var secondaryStatusesByDexPath = new TreeMap<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, snapshot, fileStatuses, packageName);
+        }
+        if (!secondaryStatusesByDexPath.isEmpty()) {
+            ipw.println("known secondary dex files:");
+            ipw.increaseIndent();
+            for (Map.Entry<String, List<DexContainerFileDexoptStatus>> entry :
+                    secondaryStatusesByDexPath.entrySet()) {
+                dumpSecondaryDex(ipw, snapshot, entry.getValue(), packageName,
+                        secondaryDexInfoByDexPath.get(entry.getKey()));
+            }
+            ipw.decreaseIndent();
+        }
+        ipw.decreaseIndent();
+    }
+
+    private void dumpPrimaryDex(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            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, snapshot,
+                mInjector.getDexUseManager().getPrimaryDexLoaders(packageName, dexPath),
+                packageName);
+        ipw.decreaseIndent();
+    }
+
+    private void dumpSecondaryDex(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName,
+            @NonNull SecondaryDexInfo info) {
+        String dexPath = fileStatuses.get(0).getDexContainerFile();
+        @FileVisibility int visibility = getDexFileVisibility(dexPath);
+        ipw.println(dexPath
+                + (visibility == FileVisibility.NOT_FOUND
+                                ? " (removed)"
+                                : (visibility == FileVisibility.OTHER_READABLE ? " (public)"
+                                                                               : "")));
+        ipw.increaseIndent();
+        dumpFileStatuses(ipw, fileStatuses);
+        ipw.printf("class loader context: %s\n", info.displayClassLoaderContext());
+        TreeMap<DexLoader, String> classLoaderContexts =
+                info.loaders().stream().collect(Collectors.toMap(loader
+                        -> loader,
+                        loader
+                        -> mInjector.getDexUseManager().getSecondaryClassLoaderContext(
+                                packageName, dexPath, loader),
+                        (a, b) -> a, TreeMap::new));
+        // We should print all class loader contexts even if `info.displayClassLoaderContext()` is
+        // not `VARYING_CLASS_LOADER_CONTEXTS`. This is because `info.displayClassLoaderContext()`
+        // may show the only supported class loader context while other apps have unsupported ones.
+        if (classLoaderContexts.values().stream().distinct().count() >= 2) {
+            ipw.increaseIndent();
+            for (var entry : classLoaderContexts.entrySet()) {
+                // entry.getValue() may be null due to a race, but it's an edge case.
+                ipw.printf("%s: %s\n", entry.getKey(), entry.getValue());
+            }
+            ipw.decreaseIndent();
+        }
+        dumpUsedByOtherApps(ipw, snapshot, 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]%s\n",
+                    VMRuntime.getInstructionSet(fileStatus.getAbi()),
+                    fileStatus.getCompilerFilter(), fileStatus.getCompilationReason(),
+                    fileStatus.isPrimaryAbi() ? " [primary-abi]" : "");
+            ipw.increaseIndent();
+            ipw.printf("[location is %s]\n", fileStatus.getLocationDebugString());
+            ipw.decreaseIndent();
+        }
+    }
+
+    private void dumpUsedByOtherApps(@NonNull IndentingPrintWriter ipw,
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+            @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()
+                            .sorted()
+                            .map(loader -> getLoaderState(snapshot, loader))
+                            .collect(Collectors.joining(", ")));
+        }
+    }
+
+    @NonNull
+    private String getLoaderState(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull DexLoader loader) {
+        var result = new StringBuilder(loader.toString());
+        PackageState loadingPkgState = snapshot.getPackageState(loader.loadingPackageName());
+        if (loadingPkgState == null) {
+            // This can happen because the information held by DexUseManagerLocal can be outdated.
+            // We don't want to clean up the entry at this point because we don't want the dump
+            // operation to have an side effect.
+            result.append(" (removed)");
+            return result.toString();
+        }
+        Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
+        result.append(String.format(" (isa=%s)", abi.isa()));
+        return result.toString();
+    }
+
+    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;
+        }
+    }
+
+    /** 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));
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+    }
+}
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..64f9bc5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -0,0 +1,415 @@
+/*
+ * 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.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+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 */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+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()
+                .filter(library -> !library.isNative())
+                .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";
+    }
+
+    @NonNull
+    public static List<ProfilePath> getExternalProfiles(@NonNull PrimaryDexInfo dexInfo) {
+        return List.of(AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath()),
+                AidlUtils.buildProfilePathForDm(dexInfo.dexPath()));
+    }
+
+    /** 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..a5481d9
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
@@ -0,0 +1,187 @@
+/*
+ * 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.content.Context;
+import android.os.Build;
+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 androidx.annotation.RequiresApi;
+
+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 */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    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() throws RemoteException {
+        return Utils.isInDalvikCache(mPkgState, mInjector.getArtd());
+    }
+
+    @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
+    @NonNull
+    protected List<ProfilePath> getExternalProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return PrimaryDexUtils.getExternalProfiles(dexInfo);
+    }
+
+    @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());
+    }
+
+    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..ac08856
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.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.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.pm.PackageManagerLocal;
+
+import dalvik.system.DexFile;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Maps a compilation reason to a compiler filter and a priority class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class ReasonMapping {
+    private ReasonMapping() {}
+
+    // Keep this in sync with `ArtShellCommand.printHelp` except for 'inactive'.
+
+    /** 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);
+
+    // Keep this in sync with `ArtShellCommand.printHelp`.
+    /** @hide */
+    public static final Set<String> BATCH_DEXOPT_REASONS = Set.of(REASON_FIRST_BOOT,
+            REASON_BOOT_AFTER_OTA, REASON_BOOT_AFTER_MAINLINE_UPDATE, REASON_BG_DEXOPT);
+
+    /**
+     * 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..c8c63db
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
@@ -0,0 +1,152 @@
+/*
+ * 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.Build;
+import android.os.CancellationSignal;
+
+import androidx.annotation.RequiresApi;
+
+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 */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class SecondaryDexopter extends Dexopter<DetailedSecondaryDexInfo> {
+    private static final String TAG = ArtManagerLocal.TAG;
+
+    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
+    @NonNull
+    protected List<ProfilePath> getExternalProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        // A secondary dex file doesn't have any external profile to use.
+        return List.of();
+    }
+
+    @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..fc94d64
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -0,0 +1,502 @@
+/*
+ * 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.ProfilePath.TmpProfilePath;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.role.RoleManager;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import androidx.annotation.RequiresApi;
+
+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.Comparator;
+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 */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public final class Utils {
+    public static final String TAG = ArtManagerLocal.TAG;
+    public static final String PLATFORM_PACKAGE_NAME = "android";
+
+    /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */
+    private static final long TRACE_TAG_DALVIK = 1L << 14;
+
+    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. The primary ABI comes first. */
+    @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. The primary ABI comes first,
+     * if given.
+     */
+    @NonNull
+    public static List<Abi> getAllAbisForNames(
+            @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
+        Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi));
+        Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
+        return abiNames.stream()
+                .map(name
+                        -> Abi.create(name, VMRuntime.getInstructionSet(name),
+                                name.equals(pkgPrimaryAbi.name())))
+                .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed())
+                .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();
+        Utils.check(isNativeAbi(preferredAbi));
+        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));
+    }
+
+    private static boolean isNativeAbi(@NonNull String abiName) {
+        return abiName.equals(Constants.getNative64BitAbi())
+                || abiName.equals(Constants.getNative32BitAbi());
+    }
+
+    /**
+     * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache
+     * directory.
+     *
+     * This method is not needed for secondary dex files because they are always in writable
+     * locations.
+     */
+    @NonNull
+    public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd)
+            throws RemoteException {
+        // The artifacts should be in the global dalvik-cache directory if:
+        // (1). the package is on a system partition, even if the partition is remounted read-write,
+        //      or
+        // (2). the package is in any other readonly location. (At the time of writing, this only
+        //      include Incremental FS.)
+        //
+        // Right now, we are using some heuristics to determine this. For (1), we can potentially
+        // use "libfstab" instead as a general solution, but for (2), unfortunately, we have to
+        // stick with heuristics.
+        //
+        // We cannot rely on access(2) because:
+        // - It doesn't take effective capabilities into account, from which artd gets root access
+        //   to the filesystem.
+        // - The `faccessat` variant with the `AT_EACCESS` flag, which takes effective capabilities
+        //   into account, is not supported by bionic.
+        //
+        // We cannot rely on `f_flags` returned by statfs(2) because:
+        // - Incremental FS is tagged as read-write while it's actually not.
+        return (pkgState.isSystem() && !pkgState.isUpdatedSystemApp())
+                || artd.isIncrementalFsPath(
+                        pkgState.getAndroidPackage().getSplits().get(0).getPath());
+    }
+
+    /** 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
+     */
+    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
+                && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static boolean shouldSkipDexoptDueToHibernation(
+            @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) {
+        return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
+                && appHibernationManager.isOatArtifactDeletionEnabled();
+    }
+
+    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);
+        }
+    }
+
+    public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) {
+        return packageName.equals(context.getString(R.string.config_systemUi));
+    }
+
+    public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName);
+    }
+
+    /**
+     * Gets the existing reference profile if one exists, or initializes a reference profile from an
+     * external profile.
+     *
+     * If the reference profile is initialized from an external profile, the returned profile path
+     * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the
+     * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link
+     * IArtd#deleteProfile}.
+     *
+     * @param dexPath the path to the dex file that the profile is checked against
+     * @param refProfile the path where an existing reference profile would be found, if present
+     * @param externalProfiles a list of external profiles to initialize the reference profile from,
+     *         in the order of preference
+     * @param initOutput the final location to initialize the reference profile to
+     *
+     * @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. Returns null if there is no
+     *         reference profile or external profile to use
+     */
+    @Nullable
+    public static Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull IArtd artd,
+            @NonNull String dexPath, @NonNull ProfilePath refProfile,
+            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)
+            throws RemoteException {
+        try {
+            if (artd.isProfileUsable(refProfile, dexPath)) {
+                boolean isOtherReadable =
+                        artd.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(artd, dexPath, externalProfiles, initOutput);
+        return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+    }
+
+    /**
+     * Similar to above, but never uses an existing profile.
+     *
+     * Unlike the one above, this method doesn't return a boolean flag to indicate if the profile is
+     * readable by others. The profile returned by this method is initialized form an external
+     * profile, meaning it has no user data, so it's always readable by others.
+     */
+    @Nullable
+    public static ProfilePath initReferenceProfile(@NonNull IArtd artd, @NonNull String dexPath,
+            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output)
+            throws RemoteException {
+        for (ProfilePath profile : externalProfiles) {
+            try {
+                // If the profile path is a PrebuiltProfilePath, and 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 (artd.copyAndRewriteProfile(profile, output, dexPath)) {
+                    return ProfilePath.tmpProfilePath(output.profilePath);
+                }
+            } catch (ServiceSpecificException e) {
+                Log.e(TAG, "Failed to initialize profile from " + AidlUtils.toString(profile), e);
+            }
+        }
+
+        return null;
+    }
+
+    public static void logArtdException(@NonNull RemoteException e) {
+        String message = "An error occurred when calling artd";
+        if (e instanceof DeadObjectException) {
+            // We assume that `DeadObjectException` only happens in two cases:
+            // 1. artd crashed, in which case a native stack trace was logged.
+            // 2. artd was killed before system server during device shutdown, in which case the
+            //    exception is expected.
+            // In either case, we don't need to surface the exception from here.
+            // The Java stack trace is intentionally omitted because it's not helpful.
+            Log.e(TAG, message);
+        } else {
+            // Not expected. Log wtf to surface it.
+            Slog.wtf(TAG, message, 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();
+    }
+
+    public static class Tracing implements AutoCloseable {
+        public Tracing(@NonNull String methodName) {
+            Trace.traceBegin(TRACE_TAG_DALVIK, methodName);
+        }
+
+        @Override
+        public void close() {
+            Trace.traceEnd(TRACE_TAG_DALVIK);
+        }
+    }
+
+    public static class TracingWithTimingLogging extends Tracing {
+        @NonNull private final String mTag;
+        @NonNull private final String mMethodName;
+        @NonNull private final long mStartTimeMs;
+
+        public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) {
+            super(methodName);
+            mTag = tag;
+            mMethodName = methodName;
+            mStartTimeMs = SystemClock.elapsedRealtime();
+            Log.d(tag, methodName);
+        }
+
+        @Override
+        public void close() {
+            Log.d(mTag,
+                    mMethodName + " took to complete: "
+                            + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms");
+            super.close();
+        }
+    }
+}
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..cc4f826
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
@@ -0,0 +1,222 @@
+/*
+ * 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 (all APKs that are installed as part of
+     * the package, including the base APK and all other split APKs).
+     */
+    public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0;
+    /**
+     * Whether the operation is applied for secondary dex'es (APKs/JARs that the app puts in its
+     * own data directory at runtime and loads with custom classloaders).
+     */
+    public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1;
+
+    // Flags specific to `dexoptPackage`.
+
+    /**
+     * Whether to dexopt dependency libraries as well (dependencies that are declared by the app
+     * with <uses-library> tags and transitive dependencies).
+     */
+    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`.
+    // Keep this in sync with `ArtShellCommand.printHelp` 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..cf86ad6
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptParams.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.ArtConstants;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@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.mReason.equals(ArtConstants.REASON_VDEX)) {
+                throw new IllegalArgumentException(
+                        "Reason must not be '" + ArtConstants.REASON_VDEX + "'");
+            }
+
+            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..79f9b5f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
@@ -0,0 +1,274 @@
+/*
+ * 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.Nullable;
+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);
+    }
+
+    /** @hide */
+    @NonNull
+    public static 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);
+    }
+
+    /**
+     * 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,
+                @Nullable @DexoptResultStatus Integer packageLevelStatus) {
+            return new AutoValue_DexoptResult_PackageDexoptResult(
+                    packageName, dexContainerFileDexoptResults, packageLevelStatus);
+        }
+
+        /** 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 */
+        @Nullable @DexoptResultStatus public abstract Integer getPackageLevelStatus();
+
+        /** The overall status of the package. */
+        public @DexoptResultStatus int getStatus() {
+            return getPackageLevelStatus() != null ? getPackageLevelStatus()
+                                                   : 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();
+
+        @Override
+        @NonNull
+        public String toString() {
+            return String.format("DexContainerFileDexoptResult{"
+                            + "dexContainerFile=%s, "
+                            + "primaryAbi=%b, "
+                            + "abi=%s, "
+                            + "actualCompilerFilter=%s, "
+                            + "status=%s, "
+                            + "dex2oatWallTimeMillis=%d, "
+                            + "dex2oatCpuTimeMillis=%d, "
+                            + "sizeBytes=%d, "
+                            + "sizeBeforeBytes=%d}",
+                    getDexContainerFile(), isPrimaryAbi(), getAbi(), getActualCompilerFilter(),
+                    DexoptResult.dexoptResultStatusToString(getStatus()),
+                    getDex2oatWallTimeMillis(), getDex2oatCpuTimeMillis(), getSizeBytes(),
+                    getSizeBeforeBytes());
+        }
+    }
+}
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..2e3bd5f 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,29 +16,1050 @@
 
 package com.android.server.art;
 
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+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.times;
+import static org.mockito.Mockito.verify;
+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.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+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_1 = "com.example.foo";
+    private static final String PKG_NAME_2 = "com.android.bar";
+    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 mPkgState1;
+    private AndroidPackage mPkg1;
+    private List<DetailedSecondaryDexInfo> mSecondaryDexInfo1;
+    private Config mConfig;
+
+    // True if the primary dex'es are in a system partition.
+    @Parameter(0) public boolean mIsInSystemPartition;
+    // True if the primary dex'es are in Incremental FS.
+    @Parameter(1) public boolean mIsInIncrementalFs;
+    // True if the artifacts should be in dalvik-cache.
+    @Parameter(2) public boolean mExpectedIsInDalvikCache;
+
     private ArtManagerLocal mArtManagerLocal;
 
+    @Parameters(name = "isInReadonlyPartition={0},isInIncrementalFs={1},"
+                    + "expectedIsInDalvikCache={2}")
+    public static Iterable<Object[]>
+    data() {
+        return List.of(new Object[] {true, false, true}, new Object[] {false, true, true},
+                new Object[] {false, false, false});
+    }
+
     @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.isLauncherPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+
+        Path tempDir = Files.createTempDirectory("temp");
+        tempDir.toFile().deleteOnExit();
+        lenient().when(mInjector.getTempDir()).thenReturn(tempDir.toString());
+
+        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(Constants.isBootImageProfilingEnabled()).thenReturn(true);
+
+        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);
+        mSecondaryDexInfo1 = createSecondaryDexInfo();
+        lenient()
+                .doReturn(mSecondaryDexInfo1)
+                .when(mDexUseManager)
+                .getSecondaryDexInfo(eq(PKG_NAME_1));
+        lenient()
+                .doReturn(mSecondaryDexInfo1)
+                .when(mDexUseManager)
+                .getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME_1));
+
+        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);
+        mPkgState1 = mSnapshot.getPackageState(PKG_NAME_1);
+        mPkg1 = mPkgState1.getAndroidPackage();
+
+        lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mIsInIncrementalFs);
+
+        // 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);
+
+        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_1);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(5)).deleteArtifacts(any());
+    }
+
+    @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(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64"));
+
+        when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+        DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+        assertThat(result.getFreedBytes()).isEqualTo(5);
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86_64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "x86", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86_64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "x86", mExpectedIsInDalvikCache)));
+        // 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", "x86_64", false /* isInDalvikCache */)));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(5)).deleteArtifacts(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsPackageNotFound() throws Exception {
+        when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testdeleteDexoptArtifactsNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
+    }
+
+    @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_1);
+
+        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_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDexoptStatusNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1);
+    }
+
+    @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_1);
+
+        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_1);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "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_1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClearAppProfilesNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1);
+    }
+
+    @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_1)), same(params),
+                     same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(
+                mArtManagerLocal.dexoptPackage(mSnapshot, PKG_NAME_1, 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_1)), any(), same(cancellationSignal), any()))
+                .thenReturn(result);
+
+        assertThat(mArtManagerLocal.resetDexoptStatus(mSnapshot, PKG_NAME_1, cancellationSignal))
+                .isSameInstanceAs(result);
+
+        verify(mArtd).deleteProfile(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
+        verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
+
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)));
+        verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache)));
+
+        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_2)).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_2, PKG_NAME_1)),
+                        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 = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(0l);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME_1 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_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        // PKG_NAME_1 should be downgraded.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_1)),
+                        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_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS);
+
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+
+        // PKG_NAME_1 should not be dexopted.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_2)),
+                        argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(),
+                        any(), any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // PKG_NAME_1 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();
+
+        lenient().when(mInjector.isSystemUiPackage(PKG_NAME_1)).thenReturn(true);
+        lenient().when(mInjector.isLauncherPackage(PKG_NAME_2)).thenReturn(true);
+
+        // It should dexopt the system UI and the launcher.
+        when(mDexoptHelper.dexopt(
+                     any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), any(), any(), any(), any(), any()))
+                .thenReturn(result);
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+    }
+
+    @Test
+    public void testDexoptPackagesBootAfterMainlineUpdatePackagesNotFound() throws Exception {
+        var result = mock(DexoptResult.class);
+        var cancellationSignal = new CancellationSignal();
+        // PKG_NAME_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        lenient().when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        lenient()
+                .when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1))
+                .thenReturn(NOT_RECENT_TIME_MS);
+        simulateStorageLow();
+
+        // It should dexopt the system UI and the launcher, but they are not found.
+        when(mDexoptHelper.dexopt(any(), deepEq(List.of()), any(), any(), any(), any(), any()))
+                .thenReturn(result);
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // 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_1 is neither recently installed nor recently used.
+        PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1));
+        when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS);
+        when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).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_2);
+                    assertThat(passedSignal).isSameInstanceAs(cancellationSignal);
+                    builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params);
+                });
+
+        // It should use the overridden package list and params.
+        doReturn(result)
+                .when(mDexoptHelper)
+                .dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params), any(), any(), any(),
+                        any());
+
+        mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal,
+                null /* processCallbackExecutor */, null /* processCallback */);
+
+        // It should not downgrade PKG_NAME_1 because it's in the overridden package list. It should
+        // not downgrade PKG_NAME_2 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_1)).setDexoptParams(params);
+                });
+        mArtManagerLocal.clearBatchDexoptStartCallback();
+
+        // It should use the default package list and params.
+        when(mDexoptHelper.dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), 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();
+
+        ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+        String dexPath = "/data/app/foo/base.apk";
+
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
+
+        when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 0 /* userId */, PKG_NAME_1, "primary"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 1 /* userId */, PKG_NAME_1, "primary"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "primary",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of(dexPath)), 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_1, null /* splitName */);
+
+        verify(mArtd).deleteProfile(
+                argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
+
+        assertThat(fd.getStatSize()).isGreaterThan(0);
+        try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+            String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+            assertThat(contents).isEqualTo("snapshot");
+        }
+    }
+
+    @Test
+    public void testSnapshotAppProfileFromDm() throws Exception {
+        String tempPathForRef = "/temp/path/for/ref";
+        File tempFileForSnapshot = File.createTempFile("primary", ".prof");
+        tempFileForSnapshot.deleteOnExit();
+
+        ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+        String dexPath = "/data/app/foo/base.apk";
+
+        // Simulate that the reference profile doesn't exist.
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
+
+        // The DM file is usable.
+        when(mArtd.copyAndRewriteProfile(
+                     deepEq(AidlUtils.buildProfilePathForDm(dexPath)), any(), eq(dexPath)))
+                .thenAnswer(invocation -> {
+                    var output = invocation.<OutputProfile>getArgument(1);
+                    output.profilePath.tmpPath = tempPathForRef;
+                    return true;
+                });
+
+        // Verify that the reference file initialized from the DM file is used.
+        when(mArtd.mergeProfiles(
+                     argThat(profiles
+                             -> profiles.stream().anyMatch(profile
+                                     -> profile.getTag() == ProfilePath.tmpProfilePath
+                                             && profile.getTmpProfilePath().tmpPath.equals(
+                                                     tempPathForRef))),
+                     isNull(), any(), deepEq(List.of(dexPath)), any()))
+                .thenAnswer(invocation -> {
+                    var output = invocation.<OutputProfile>getArgument(2);
+                    output.profilePath.tmpPath = tempFileForSnapshot.getPath();
+                    return true;
+                });
+
+        ParcelFileDescriptor fd =
+                mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+
+        verify(mArtd).deleteProfile(argThat(profile
+                -> profile.getTmpProfilePath().tmpPath.equals(tempFileForSnapshot.getPath())));
+        verify(mArtd).deleteProfile(
+                argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempPathForRef)));
+    }
+
+    @Test
+    public void testSnapshotAppProfileSplit() throws Exception {
+        ProfilePath refProfile =
+                AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split");
+        String dexPath = "/data/app/foo/split_0.apk";
+
+        when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true);
+
+        when(mArtd.mergeProfiles(deepEq(List.of(refProfile,
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 0 /* userId */, PKG_NAME_1, "split_0.split"),
+                                         AidlUtils.buildProfilePathForPrimaryCur(
+                                                 1 /* userId */, PKG_NAME_1, "split_0.split"))),
+                     isNull(),
+                     deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "split_0.split",
+                             Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+                     deepEq(List.of(dexPath)), any()))
+                .thenReturn(false);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "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_1, null /* splitName */);
+
+        verify(mArtd, never()).deleteProfile(any());
+
+        assertThat(fd.getStatSize()).isEqualTo(0);
+        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_1, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileNoPackage() throws Exception {
+        when(mPkgState1.getAndroidPackage()).thenReturn(null);
+
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSnapshotAppProfileSplitNotFound() throws Exception {
+        mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "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_1, 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_1, 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_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_1, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_1, "split_0.split"),
+                             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_2, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     0 /* userId */, PKG_NAME_2, "primary"),
+                             AidlUtils.buildProfilePathForPrimaryCur(
+                                     1 /* userId */, PKG_NAME_2, "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);
+    }
+
+    @Test
+    public void testCleanup() throws Exception {
+        // It should keep all artifacts.
+        doReturn(createGetDexoptStatusResult("speed-profile", "bg-dexopt", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm64"), any());
+        doReturn(createGetDexoptStatusResult("verify", "cmdline", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
+
+        // It should only keep VDEX files.
+        doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm64"), any());
+        doReturn(createGetDexoptStatusResult("verify", "vdex", "location"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm"), any());
+
+        // It should not keep any artifacts.
+        doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown"))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm"), any());
+
+        when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1));
+        mArtManagerLocal.cleanup(mSnapshot);
+
+        verify(mArtd).cleanup(
+                inAnyOrderDeepEquals(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                0 /* userId */, PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                1 /* userId */, PKG_NAME_1, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                0 /* userId */, PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                1 /* userId */, PKG_NAME_1, "split_0.split"),
+                        AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"),
+                        AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")),
+                inAnyOrderDeepEquals(AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64",
+                                             mExpectedIsInDalvikCache),
+                        AidlUtils.buildArtifactsPath(
+                                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)),
+                inAnyOrderDeepEquals(
+                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)),
+                        VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache))));
+    }
+
+    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(mIsInSystemPartition);
+        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 pkgState1 =
+                createPackageState(PKG_NAME_1, true /* isDexoptable */, true /* multiSplit */);
+
+        PackageState pkgState2 =
+                createPackageState(PKG_NAME_2, 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(pkgState1, pkgState2, 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<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+        var dexInfo = mock(DetailedSecondaryDexInfo.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..3528caf
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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 = 10;
+
+    @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);
+
+        verify(mArtManagerLocal).cleanup(same(mSnapshot));
+    }
+
+    @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..5850e61
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -0,0 +1,768 @@
+/*
+ * 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.proto.DexUseProto;
+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.io.FileInputStream;
+import java.io.InputStream;
+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;
+    private File mTempFile;
+    private Map<String, PackageState> mPackageStates;
+
+    @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);
+
+        mPackageStates = new HashMap<>();
+
+        PackageState loadingPkgState = createPackageState(LOADING_PKG_NAME, "armeabi-v7a");
+        addPackage(LOADING_PKG_NAME, loadingPkgState);
+        PackageState owningPkgState = createPackageState(OWNING_PKG_NAME, "arm64-v8a");
+        addPackage(OWNING_PKG_NAME, owningPkgState);
+        PackageState platformPkgState =
+                createPackageState(Utils.PLATFORM_PACKAGE_NAME, "arm64-v8a");
+        addPackage(Utils.PLATFORM_PACKAGE_NAME, platformPkgState);
+
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(mPackageStates);
+
+        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();
+
+        mTempFile = File.createTempFile("package-dex-usage", ".pb");
+        mTempFile.deleteOnExit();
+
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+        lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l);
+        lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath());
+        lenient()
+                .when(mInjector.createScheduledExecutor())
+                .thenAnswer(invocation -> mMockClock.createScheduledExecutor());
+        lenient().when(mInjector.getContext()).thenReturn(mContext);
+        lenient().when(mInjector.getAllPackageNames()).thenReturn(mPackageStates.keySet());
+
+        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 */, false /* cleanup */);
+    }
+
+    /** 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 */, false /* cleanup */);
+    }
+
+    /** 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 */, false /* cleanup */);
+    }
+
+    /** Checks that it doesn't accidentally cleanup any entry that is needed. */
+    @Test
+    public void testPrimaryDexMultipleEntriesSurviveCleanup() throws Exception {
+        verifyPrimaryDexMultipleEntries(
+                false /*saveAndLoad */, false /* shutdown */, true /* cleanup */);
+    }
+
+    private void verifyPrimaryDexMultipleEntries(
+            boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        lenient()
+                .when(mArtd.getDexFileVisibility(BASE_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(SPLIT_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        // 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);
+        }
+
+        if (cleanup) {
+            // Nothing should be cleaned up.
+            mDexUseManager.cleanup();
+        }
+
+        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 */, false /* cleanup */);
+    }
+
+    /** 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 */, false /* cleanup */);
+    }
+
+    /** 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 */, false /* cleanup */);
+    }
+
+    /** Checks that it doesn't accidentally cleanup any entry that is needed. */
+    @Test
+    public void testSecondaryDexMultipleEntriesSurviveCleanup() throws Exception {
+        verifySecondaryDexMultipleEntries(
+                false /*saveAndLoad */, false /* shutdown */, true /* cleanup */);
+    }
+
+    private void verifySecondaryDexMultipleEntries(
+            boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception {
+        when(mInjector.getCurrentTimeMillis()).thenReturn(1000l);
+
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // 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);
+        }
+
+        if (cleanup) {
+            // Nothing should be cleaned up.
+            mDexUseManager.cleanup();
+        }
+
+        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
+    public void testCleanup() throws Exception {
+        PackageState pkgState = createPackageState("com.example.deletedpackage", "arm64-v8a");
+        addPackage("com.example.deletedpackage", pkgState);
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/com.example.deletedpackage/base.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        lenient()
+                .when(mArtd.getDexFileVisibility(BASE_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        // Simulate that a package loads its own dex file and another package's dex file.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, "com.example.deletedpackage",
+                Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC", BASE_APK, "CLC"));
+        // Simulate that another package loads this package's dex file.
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC"));
+        // Simulate that the package is then deleted.
+        removePackage("com.example.deletedpackage");
+
+        // Simulate that a primary dex file is loaded and then deleted.
+        lenient()
+                .when(mArtd.getDexFileVisibility(SPLIT_APK))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(SPLIT_APK, "CLC"));
+        lenient().when(mArtd.getDexFileVisibility(SPLIT_APK)).thenReturn(FileVisibility.NOT_FOUND);
+
+        // Simulate that a secondary dex file is loaded and then deleted.
+        lenient()
+                .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"));
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        // Create an entry that should be kept.
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "CLC"));
+
+        // Simulate that a secondary dex file is loaded by another package and then made private.
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(
+                mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/baz.apk", "CLC"));
+        lenient()
+                .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk"))
+                .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+        // Simulate that all the files of a package are deleted. The whole container entry of the
+        // package should be cleaned up, though the package still exists.
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+        mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+                Map.of("/data/app/" + LOADING_PKG_NAME + "/base.apk", "CLC"));
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        // Run cleanup.
+        mDexUseManager.cleanup();
+
+        // Save.
+        mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS);
+
+        // Check that the entries are removed from the proto. Normally, we should check the return
+        // values of the public get methods instead of checking the raw proto. However, here we want
+        // to make sure that the container entries are cleaned up when they are empty so that they
+        // don't cost extra memory or storage.
+        // Note that every repeated field must not contain more than one entry, to keep the
+        // textproto deterministic.
+        DexUseProto proto;
+        try (InputStream in = new FileInputStream(mTempFile.getPath())) {
+            proto = DexUseProto.parseFrom(in);
+        }
+        String textproto = proto.toString();
+        // Remove the first line, which is an auto-generated comment.
+        textproto = textproto.substring(textproto.indexOf('\n') + 1).trim();
+        assertThat(textproto).isEqualTo("package_dex_use {\n"
+                + "  owning_package_name: \"com.example.owningpackage\"\n"
+                + "  secondary_dex_use {\n"
+                + "    dex_file: \"/data/user/0/com.example.owningpackage/bar.apk\"\n"
+                + "    record {\n"
+                + "      abi_name: \"arm64-v8a\"\n"
+                + "      class_loader_context: \"CLC\"\n"
+                + "      last_used_at_ms: 0\n"
+                + "      loading_package_name: \"com.example.owningpackage\"\n"
+                + "    }\n"
+                + "    user_id {\n"
+                + "    }\n"
+                + "  }\n"
+                + "}");
+    }
+
+    @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;
+    }
+
+    private void addPackage(String packageName, PackageState pkgState) {
+        lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(pkgState);
+        mPackageStates.put(packageName, pkgState);
+    }
+
+    private void removePackage(String packageName) {
+        lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(null);
+        mPackageStates.remove(packageName);
+    }
+}
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..f08035d
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
@@ -0,0 +1,859 @@
+/*
+ * 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.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+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), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateBar), same(mPkgBar), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
+        inOrder.verify(mInjector).getPrimaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
+        inOrder.verify(mInjector).getSecondaryDexopter(
+                same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
+        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), any());
+
+        verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+    }
+
+    // This test verifies that every child thread can register its own listener on the cancellation
+    // signal through `setOnCancelListener` (i.e., the listeners don't overwrite each other).
+    @Test
+    public void testDexoptCancelledDuringDex2oatInvocationsMultiThreaded() throws Exception {
+        final int NUM_PACKAGES = 6;
+        final long TIMEOUT_SEC = 10;
+        var dexoptStarted = new Semaphore(0);
+        var dexoptCancelled = new Semaphore(0);
+
+        when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
+            var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
+            var dexopter = mock(PrimaryDexopter.class);
+            when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
+                // Simulate that the child thread registers its own listener.
+                var isListenerCalled = new AtomicBoolean(false);
+                cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
+
+                dexoptStarted.release();
+                assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+                // Verify that the listener is called.
+                assertThat(isListenerCalled.get()).isTrue();
+
+                return mPrimaryResults;
+            });
+            return dexopter;
+        });
+
+        ExecutorService dexoptExecutor = Executors.newFixedThreadPool(NUM_PACKAGES);
+        Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
+            return mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
+        });
+
+        try {
+            // Wait for all dexopt operations to start.
+            for (int i = 0; i < NUM_PACKAGES; i++) {
+                assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+            }
+
+            mCancellationSignal.cancel();
+
+            for (int i = 0; i < NUM_PACKAGES; i++) {
+                dexoptCancelled.release();
+            }
+        } finally {
+            dexoptExecutor.shutdown();
+            Utils.getFuture(future);
+        }
+    }
+
+    // This test verifies that dexopt operation on the current thread can be cancelled.
+    @Test
+    public void testDexoptCancelledDuringDex2oatInvocationsOnCurrentThread() throws Exception {
+        final long TIMEOUT_SEC = 10;
+        var dexoptStarted = new Semaphore(0);
+        var dexoptCancelled = new Semaphore(0);
+
+        when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
+            var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
+            var dexopter = mock(PrimaryDexopter.class);
+            when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
+                if (cancellationSignal.isCanceled()) {
+                    return mPrimaryResults;
+                }
+
+                var isListenerCalled = new AtomicBoolean(false);
+                cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
+
+                dexoptStarted.release();
+                assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+                // Verify that the listener is called.
+                assertThat(isListenerCalled.get()).isTrue();
+
+                return mPrimaryResults;
+            });
+            return dexopter;
+        });
+
+        // Use the current thread (the one in ForkJoinPool).
+        Executor dexoptExecutor = Runnable::run;
+        Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
+            return mDexoptHelper.dexopt(
+                    mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
+        });
+
+        try {
+            // Only one dexopt operation should start.
+            assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+            mCancellationSignal.cancel();
+
+            dexoptCancelled.release();
+        } finally {
+            Utils.getFuture(future);
+        }
+    }
+
+    @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);
+        lenient().when(library.isNative()).thenReturn(false);
+        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);
+
+        // The native library is not dexoptable.
+        SharedLibrary libNative = createLibrary("libnative", "com.example.libnative", List.of());
+        lenient().when(libNative.isNative()).thenReturn(true);
+
+        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, libNative, lib4));
+        SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
+
+        mPkgStateFoo =
+                createPackageState(PKG_NAME_FOO, List.of(lib1a, libNative), 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..3833249
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+import android.annotation.NonNull;
+import android.os.SystemProperties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.DexoptStatus;
+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.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DumpHelperTest {
+    private static final String PKG_NAME_FOO = "com.example1.foo";
+    private static final String PKG_NAME_BAR = "com.example2.bar";
+
+    @Rule
+    public StaticMockitoRule mockitoRule =
+            new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+    @Mock private DumpHelper.Injector mInjector;
+    @Mock private ArtManagerLocal mArtManagerLocal;
+    @Mock private DexUseManagerLocal mDexUseManagerLocal;
+    @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+    @Mock private IArtd mArtd;
+
+    private DumpHelper mDumpHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+        lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+        // No ISA translation.
+        lenient()
+                .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+                .thenReturn("");
+
+        lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManagerLocal);
+        lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+
+        Map<String, PackageState> pkgStates = createPackageStates();
+        lenient().when(mSnapshot.getPackageStates()).thenReturn(pkgStates);
+        for (var entry : pkgStates.entrySet()) {
+            lenient().when(mSnapshot.getPackageState(entry.getKey())).thenReturn(entry.getValue());
+        }
+
+        setUpForFoo();
+        setUpForBar();
+
+        mDumpHelper = new DumpHelper(mInjector);
+    }
+
+    @Test
+    public void testDump() throws Exception {
+        String expected = "[com.example1.foo]\n"
+                + "  path: /data/app/foo/base.apk\n"
+                + "    arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+                + "      [location is /data/app/foo/oat/arm64/base.odex]\n"
+                + "    arm: [status=verify] [reason=install]\n"
+                + "      [location is /data/app/foo/oat/arm/base.odex]\n"
+                + "  path: /data/app/foo/split_0.apk\n"
+                + "    arm64: [status=verify] [reason=vdex] [primary-abi]\n"
+                + "      [location is primary.vdex in /data/app/foo/split_0.dm]\n"
+                + "    arm: [status=verify] [reason=vdex]\n"
+                + "      [location is primary.vdex in /data/app/foo/split_0.dm]\n"
+                + "    used by other apps: [com.example2.bar (isa=arm)]\n"
+                + "  known secondary dex files:\n"
+                + "    /data/user_de/0/foo/1.apk (removed)\n"
+                + "      arm: [status=run-from-apk] [reason=unknown]\n"
+                + "        [location is unknown]\n"
+                + "      class loader context: =VaryingClassLoaderContexts=\n"
+                + "        com.example1.foo (isolated): CLC1\n"
+                + "        com.example3.baz: CLC2\n"
+                + "      used by other apps: [com.example1.foo (isolated) (isa=arm64), com.example3.baz (removed)]\n"
+                + "    /data/user_de/0/foo/2.apk (public)\n"
+                + "      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+                + "        [location is /data/user_de/0/foo/oat/arm64/2.odex]\n"
+                + "      arm: [status=verify] [reason=vdex]\n"
+                + "        [location is /data/user_de/0/foo/oat/arm/2.vdex]\n"
+                + "      class loader context: PCL[]\n"
+                + "[com.example2.bar]\n"
+                + "  path: /data/app/bar/base.apk\n"
+                + "    arm: [status=verify] [reason=install] [primary-abi]\n"
+                + "      [location is /data/app/bar/oat/arm/base.odex]\n"
+                + "    arm64: [status=verify] [reason=install]\n"
+                + "      [location is /data/app/bar/oat/arm64/base.odex]\n";
+
+        var stringWriter = new StringWriter();
+        mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
+        assertThat(stringWriter.toString()).isEqualTo(expected);
+    }
+
+    private PackageState createPackageState(@NonNull String packageName, int appId,
+            boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) {
+        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);
+        lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi);
+        lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn(secondaryAbi);
+        return pkgState;
+    }
+
+    private Map<String, PackageState> createPackageStates() {
+        var pkgStates = new HashMap<String, PackageState>();
+        pkgStates.put(PKG_NAME_FOO,
+                createPackageState(PKG_NAME_FOO, 10001 /* appId */, true /* hasPackage */,
+                        "arm64-v8a", "armeabi-v7a"));
+        pkgStates.put(PKG_NAME_BAR,
+                createPackageState(PKG_NAME_BAR, 10003 /* appId */, true /* hasPackage */,
+                        "armeabi-v7a", "arm64-v8a"));
+        // 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 */,
+                        "arm64-v8a", "armeabi-v7a"));
+        // 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 */,
+                        "arm64-v8a", "armeabi-v7a"));
+        return pkgStates;
+    }
+
+    private void setUpForFoo() throws Exception {
+        // The order of the primary dex files and the ABIs should be kept in the output. Secondary
+        // dex files should be reordered in lexicographical order.
+        var status = DexoptStatus.create(List.of(
+                DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                        true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                        "speed-profile", "bg-dexopt", "/data/app/foo/oat/arm64/base.odex"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/base.apk",
+                        true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "install", "/data/app/foo/oat/arm/base.odex"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                        true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", "verify",
+                        "vdex", "primary.vdex in /data/app/foo/split_0.dm"),
+                DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk",
+                        true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "vdex", "primary.vdex in /data/app/foo/split_0.dm"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                        false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+                        "speed-profile", "bg-dexopt", "/data/user_de/0/foo/oat/arm64/2.odex"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
+                        false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
+                        "vdex", "/data/user_de/0/foo/oat/arm/2.vdex"),
+                DexContainerFileDexoptStatus.create("/data/user_de/0/foo/1.apk",
+                        false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
+                        "run-from-apk", "unknown", "unknown")));
+
+        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);
+
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/1.apk",
+                        DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */)))
+                .thenReturn("CLC1");
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/1.apk",
+                        DexLoader.create("com.example3.baz", false /* isolatedProcess */)))
+                .thenReturn("CLC2");
+
+        var loaders = new HashSet<DexLoader>();
+        // The output should show "foo" with "(isolated)" in "used by other apps:".
+        loaders.add(DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */));
+        // The output should show "baz" with "(removed)" in "used by other apps:".
+        loaders.add(DexLoader.create("com.example3.baz", false /* isolatedProcess */));
+        lenient().when(info1.loaders()).thenReturn(loaders);
+
+        // The output should show the dex path with "(removed)".
+        lenient()
+                .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/1.apk"))
+                .thenReturn(FileVisibility.NOT_FOUND);
+
+        var info2 = mock(SecondaryDexInfo.class);
+        lenient().when(info2.dexPath()).thenReturn("/data/user_de/0/foo/2.apk");
+        lenient().when(info2.displayClassLoaderContext()).thenReturn("PCL[]");
+        lenient()
+                .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
+                        "/data/user_de/0/foo/2.apk",
+                        DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)))
+                .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()
+                .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/2.apk"))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        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", "/data/app/bar/oat/arm/base.odex"),
+                        DexContainerFileDexoptStatus.create("/data/app/bar/base.apk",
+                                true /* isPrimaryDex */, false /* isPrimaryAbi */, "arm64-v8a",
+                                "verify", "install", "/data/app/bar/oat/arm64/base.odex")));
+
+        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..a54b371
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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<>();
+
+        // The native library should not be added to the CLC.
+        SharedLibrary libraryNative = mock(SharedLibrary.class);
+        lenient().when(libraryNative.getAllCodePaths()).thenReturn(List.of("library_native.so"));
+        lenient().when(libraryNative.getDependencies()).thenReturn(null);
+        lenient().when(libraryNative.isNative()).thenReturn(true);
+        usesLibraryInfos.add(libraryNative);
+
+        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);
+        lenient().when(library1.isNative()).thenReturn(false);
+
+        SharedLibrary library2 = mock(SharedLibrary.class);
+        lenient().when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
+        lenient().when(library2.getDependencies()).thenReturn(List.of(library1, libraryNative));
+        lenient().when(library2.isNative()).thenReturn(false);
+        usesLibraryInfos.add(library2);
+
+        SharedLibrary library3 = mock(SharedLibrary.class);
+        lenient().when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
+        lenient().when(library3.getDependencies()).thenReturn(null);
+        lenient().when(library3.isNative()).thenReturn(false);
+        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));
+        lenient().when(library4.isNative()).thenReturn(false);
+        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..31ea915
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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.anyInt;
+import static org.mockito.Mockito.argThat;
+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.times;
+import static org.mockito.Mockito.verify;
+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.TestingUtils;
+
+import dalvik.system.DexFile;
+
+import org.junit.Before;
+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 org.mockito.ArgumentMatcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
+    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.mIsIncrementalFsPath = true;
+        params.mExpectedIsInDalvikCache = 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.mIsLauncher = true;
+        params.mExpectedCompilerFilter = "speed-profile";
+        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 | DexoptTrigger.NEED_EXTRACTION;
+        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 | DexoptTrigger.NEED_EXTRACTION;
+        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(mInjector.isLauncherPackage(any())).thenReturn(mParams.mIsLauncher);
+
+        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);
+
+        // Make all profile-related operations succeed so that "speed-profile" doesn't fall back to
+        // "verify".
+        lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(true);
+        lenient().when(mArtd.getProfileVisibility(any())).thenReturn(FileVisibility.OTHER_READABLE);
+        lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+        lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mParams.mIsIncrementalFsPath);
+
+        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 */);
+
+        // No need to check `generateAppImage`. It is checked in `PrimaryDexopterTest`.
+        ArgumentMatcher<DexoptOptions> dexoptOptionsMatcher = options
+                -> options.compilationReason.equals("install") && options.targetSdkVersion == 123
+                && options.debuggable == mParams.mExpectedIsDebuggable
+                && options.hiddenApiPolicyEnabled == mParams.mExpectedIsHiddenApiPolicyEnabled
+                && options.comments.equals(
+                        String.format("app-version-name:%s,app-version-code:%d,art-version:%d",
+                                APP_VERSION_NAME, APP_VERSION_CODE, ART_VERSION));
+
+        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), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), 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), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), 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), any() /* profile */,
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), 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 */));
+
+        // Verify that there are no more calls than the ones above.
+        verify(mArtd, times(3))
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
+    }
+
+    private static class Params {
+        // Package information.
+        public boolean mIsSystem = false;
+        public boolean mIsUpdatedSystemApp = false;
+        public boolean mIsIncrementalFsPath = false;
+        public int mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED;
+        public boolean mIsVmSafeMode = false;
+        public boolean mIsDebuggable = false;
+        public boolean mIsSystemUi = false;
+        public boolean mIsLauncher = 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 | DexoptTrigger.NEED_EXTRACTION;
+        public boolean mExpectedIsInDalvikCache = false;
+        public boolean mExpectedIsDebuggable = false;
+        public boolean mExpectedIsHiddenApiPolicyEnabled = true;
+
+        public String toString() {
+            return String.format("isSystem=%b,"
+                            + "isUpdatedSystemApp=%b,"
+                            + "isIncrementalFsPath=%b,"
+                            + "mHiddenApiEnforcementPolicy=%d,"
+                            + "isVmSafeMode=%b,"
+                            + "isDebuggable=%b,"
+                            + "isSystemUi=%b,"
+                            + "isLauncher=%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, mIsIncrementalFsPath,
+                    mHiddenApiEnforcementPolicy, mIsVmSafeMode, mIsDebuggable, mIsSystemUi,
+                    mIsLauncher, 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..f02ebf0
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -0,0 +1,670 @@
+/*
+ * 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 | DexoptTrigger.NEED_EXTRACTION;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+    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
+            | DexoptTrigger.NEED_EXTRACTION;
+
+    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 = 10;
+
+        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..94e0b48
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
@@ -0,0 +1,208 @@
+/*
+ * 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);
+    protected static final long ART_VERSION = 331413030l;
+    protected static final String APP_VERSION_NAME = "12.34.56";
+    protected static final long APP_VERSION_CODE = 1536036288l;
+
+    @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.isLauncherPackage(any())).thenReturn(false);
+        lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+        lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+        lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
+        lenient().when(mInjector.getArtVersion()).thenReturn(ART_VERSION);
+
+        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.isNonSdkApiRequested()).thenReturn(false);
+        lenient().when(pkg.getVersionName()).thenReturn(APP_VERSION_NAME);
+        lenient().when(pkg.getLongVersionCode()).thenReturn(APP_VERSION_CODE);
+        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..5d8661f
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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 | DexoptTrigger.NEED_EXTRACTION;
+    private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+            | DexoptTrigger.COMPILER_FILTER_IS_SAME
+            | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
+
+    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.isLauncherPackage(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.isNonSdkApiRequested()).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..bc6ed16
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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");
+
+        lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+        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("arm64-v8a", "arm64", true /* isPrimaryAbi */));
+
+        // Make sure the result does come from `Constants.getPreferredAbi()` rather than somewhere
+        // else.
+        when(Constants.getPreferredAbi()).thenReturn("armeabi-v7a");
+        assertThat(Utils.getAllAbis(pkgState))
+                .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", 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..6098641
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 testBuildReasonVdex() {
+        new DexoptParams.Builder("vdex").setCompilerFilter("speed").setPriorityClass(90).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..5ee0a57
--- /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 = "ArtServiceTesting";
+
+    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 087d928..5c0f1f2 100644
--- a/libarttools/Android.bp
+++ b/libarttools/Android.bp
@@ -34,6 +34,7 @@
         "tools/tools.cc",
     ],
     export_include_dirs: ["."],
+    header_libs: ["art_libartbase_headers"],
     shared_libs: [
         "libbase",
     ],
@@ -57,11 +58,22 @@
     name: "art_libarttools_tests_defaults",
     defaults: ["libarttools_defaults"],
     srcs: [
+        "tools/art_exec_test.cc",
+        "tools/cmdline_builder_test.cc",
+        "tools/system_properties_test.cc",
         "tools/tools_test.cc",
     ],
     shared_libs: [
         "libbase",
     ],
+    static_libs: [
+        "libgmock",
+    ],
+    target: {
+        android: {
+            static_libs: ["libmodules-utils-build"],
+        },
+    },
 }
 
 // Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target.
@@ -83,3 +95,26 @@
         "art_libarttools_tests_defaults",
     ],
 }
+
+cc_binary {
+    name: "art_exec",
+    defaults: [
+        "art_defaults",
+    ],
+    srcs: [
+        "tools/art_exec.cc",
+    ],
+    shared_libs: [
+        "libartbase",
+        "libartpalette",
+        "libarttools", // Contains "libc++fs".
+        "libbase",
+    ],
+    static_libs: [
+        "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..8f33658
--- /dev/null
+++ b/libarttools/tools/art_exec.cc
@@ -0,0 +1,222 @@
+/*
+ * 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 "palette/palette.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;
+
+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(ART_FORMAT("/proc/self/fd/{}", 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 (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
+        ret != PALETTE_STATUS_OK) {
+      LOG(ERROR) << "Failed to set task profile: " << ret;
+      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..a5a0b01
--- /dev/null
+++ b/libarttools/tools/art_exec_test.cc
@@ -0,0 +1,265 @@
+/*
+ * 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "system/thread_defs.h"
+
+#ifdef ART_TARGET_ANDROID
+#include "android-modules-utils/sdk_level.h"
+#endif
+
+namespace art {
+namespace {
+
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+using ::android::base::Split;
+using ::testing::Contains;
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+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 ART_FORMAT("{}/bin/{}", GetArtRoot(), name);
+}
+
+std::string GetBin(const std::string& name) {
+  return ART_FORMAT("{}/bin/{}", 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) {
+// The condition is always true because ArtExecTest is run on device only.
+#ifdef ART_TARGET_ANDROID
+  if (!android::modules::sdklevel::IsAtLeastU()) {
+    GTEST_SKIP() << "This test depends on a libartpalette API that is only available on U+";
+  }
+#endif
+
+  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_,
+                                ART_FORMAT("--keep-fds={}:{}", file3->Fd(), file2->Fd()),
+                                kEmptyLdLibraryPath,
+                                "--",
+                                GetBin("sh"),
+                                "-c",
+                                ART_FORMAT("("
+                                           "readlink /proc/self/fd/{} || echo;"
+                                           "readlink /proc/self/fd/{} || echo;"
+                                           "readlink /proc/self/fd/{} || echo;"
+                                           ") > {}",
+                                           file1->Fd(),
+                                           file2->Fd(),
+                                           file3->Fd(),
+                                           filename)};
+
+  ScopedExecAndWait(args);
+
+  std::string open_fds;
+  ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds));
+
+  // `file1` should be closed, while the other two should be open. There's a blank line at the end.
+  EXPECT_THAT(Split(open_fds, "\n"), ElementsAre(Not("/dev/zero"), "/dev/zero", "/dev/zero", ""));
+}
+
+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/libarttools/tools/tools.cc b/libarttools/tools/tools.cc
index a3a91e8..4ec9d9a 100644
--- a/libarttools/tools/tools.cc
+++ b/libarttools/tools/tools.cc
@@ -16,12 +16,127 @@
 
 #include "tools.h"
 
+#include <errno.h>
+#include <fnmatch.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <functional>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "base/macros.h"
+
 namespace art {
 namespace tools {
 
-std::string getMsg() {
-    return "hello world!";
+namespace {
+
+using ::std::placeholders::_1;
+
+// Returns true if `path_prefix` matches `pattern` or can be a prefix of a path that matches
+// `pattern` (i.e., `path_prefix` represents a directory that may contain a file whose path matches
+// `pattern`).
+bool PartialMatch(const std::filesystem::path& pattern, const std::filesystem::path& path_prefix) {
+  for (std::filesystem::path::const_iterator pattern_it = pattern.begin(),
+                                             path_prefix_it = path_prefix.begin();
+       ;  // NOLINT
+       pattern_it++, path_prefix_it++) {
+    if (path_prefix_it == path_prefix.end()) {
+      return true;
+    }
+    if (pattern_it == pattern.end()) {
+      return false;
+    }
+    if (*pattern_it == "**") {
+      return true;
+    }
+    if (fnmatch(pattern_it->c_str(), path_prefix_it->c_str(), /*flags=*/0) != 0) {
+      return false;
+    }
+  }
 }
 
+bool FullMatchRecursive(const std::filesystem::path& pattern,
+                        std::filesystem::path::const_iterator pattern_it,
+                        const std::filesystem::path& path,
+                        std::filesystem::path::const_iterator path_it,
+                        bool double_asterisk_visited = false) {
+  if (pattern_it == pattern.end() && path_it == path.end()) {
+    return true;
+  }
+  if (pattern_it == pattern.end()) {
+    return false;
+  }
+  if (*pattern_it == "**") {
+    DCHECK(!double_asterisk_visited);
+    std::filesystem::path::const_iterator next_pattern_it = pattern_it;
+    return FullMatchRecursive(
+               pattern, ++next_pattern_it, path, path_it, /*double_asterisk_visited=*/true) ||
+           (path_it != path.end() && FullMatchRecursive(pattern, pattern_it, path, ++path_it));
+  }
+  if (path_it == path.end()) {
+    return false;
+  }
+  if (fnmatch(pattern_it->c_str(), path_it->c_str(), /*flags=*/0) != 0) {
+    return false;
+  }
+  return FullMatchRecursive(pattern, ++pattern_it, path, ++path_it);
 }
+
+// Returns true if `path` fully matches `pattern`.
+bool FullMatch(const std::filesystem::path& pattern, const std::filesystem::path& path) {
+  return FullMatchRecursive(pattern, pattern.begin(), path, path.begin());
 }
+
+void MatchGlobRecursive(const std::vector<std::filesystem::path>& patterns,
+                        const std::filesystem::path& root_dir,
+                        /*out*/ std::vector<std::string>* results) {
+  std::error_code ec;
+  for (auto it = std::filesystem::recursive_directory_iterator(
+           root_dir, std::filesystem::directory_options::skip_permission_denied, ec);
+       !ec && it != std::filesystem::end(it);
+       it.increment(ec)) {
+    const std::filesystem::directory_entry& entry = *it;
+    if (std::none_of(patterns.begin(), patterns.end(), std::bind(PartialMatch, _1, entry.path()))) {
+      // Avoid unnecessary I/O and SELinux denials.
+      it.disable_recursion_pending();
+      continue;
+    }
+    std::error_code ec2;
+    if (entry.is_regular_file(ec2) &&
+        std::any_of(patterns.begin(), patterns.end(), std::bind(FullMatch, _1, entry.path()))) {
+      results->push_back(entry.path());
+    }
+    if (ec2) {
+      // It's expected that we don't have permission to stat some dirs/files, and we don't care
+      // about them.
+      if (ec2.value() != EACCES) {
+        LOG(ERROR) << ART_FORMAT("Unable to lstat '{}': {}", entry.path().string(), ec2.message());
+      }
+      continue;
+    }
+  }
+  if (ec) {
+    LOG(ERROR) << ART_FORMAT("Unable to walk through '{}': {}", root_dir.string(), ec.message());
+  }
+}
+
+}  // namespace
+
+std::vector<std::string> Glob(const std::vector<std::string>& patterns, std::string_view root_dir) {
+  std::vector<std::filesystem::path> parsed_patterns;
+  parsed_patterns.reserve(patterns.size());
+  for (std::string_view pattern : patterns) {
+    parsed_patterns.emplace_back(pattern);
+  }
+  std::vector<std::string> results;
+  MatchGlobRecursive(parsed_patterns, root_dir, &results);
+  return results;
+}
+
+}  // namespace tools
+}  // namespace art
diff --git a/libarttools/tools/tools.h b/libarttools/tools/tools.h
index 8231f5f..c2bcee7 100644
--- a/libarttools/tools/tools.h
+++ b/libarttools/tools/tools.h
@@ -18,11 +18,24 @@
 #define ART_LIBARTTOOLS_TOOLS_TOOLS_H_
 
 #include <string>
+#include <string_view>
+#include <vector>
 
 namespace art {
 namespace tools {
 
-std::string getMsg();
+// Searches in a filesystem, starting from `root_dir`. Returns all regular files (i.e., excluding
+// directories, symlinks, etc.) that match at least one pattern in `patterns`. Each pattern is an
+// absolute path that contains zero or more wildcards. The scan does not follow symlinks to
+// directories.
+//
+// Supported wildcards are:
+// - Those documented in glob(7)
+// - '**': Matches zero or more path elements. This is only recognised by itself as a path segment.
+//
+// For simplicity and efficiency, at most one '**' is allowed.
+std::vector<std::string> Glob(const std::vector<std::string>& patterns,
+                              std::string_view root_dir = "/");
 
 }  // namespace tools
 }  // namespace art
diff --git a/libarttools/tools/tools_test.cc b/libarttools/tools/tools_test.cc
index 6eaa8f6..2f61181 100644
--- a/libarttools/tools/tools_test.cc
+++ b/libarttools/tools/tools_test.cc
@@ -15,14 +15,101 @@
  */
 
 #include "tools.h"
+
+#include <algorithm>
+#include <filesystem>
+#include <iterator>
+
+#include "android-base/file.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace art {
+namespace tools {
+namespace {
 
-class ArtToolsTest : public testing::Test {};
+using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAre;
 
-TEST_F(ArtToolsTest, Hello) {
-  EXPECT_EQ("hello world!", art::tools::getMsg());
+void CreateFile(const std::string& filename) {
+  std::filesystem::path path(filename);
+  std::filesystem::create_directories(path.parent_path());
+  ASSERT_TRUE(WriteStringToFile(/*content=*/"", filename));
 }
 
+class ArtToolsTest : public CommonArtTest {
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+    scratch_dir_ = std::make_unique<ScratchDir>();
+    scratch_path_ = scratch_dir_->GetPath();
+    // Remove the trailing '/';
+    scratch_path_.resize(scratch_path_.length() - 1);
+  }
+
+  void TearDown() override {
+    scratch_dir_.reset();
+    CommonArtTest::TearDown();
+  }
+
+  std::unique_ptr<ScratchDir> scratch_dir_;
+  std::string scratch_path_;
+};
+
+TEST_F(ArtToolsTest, Glob) {
+  CreateFile(scratch_path_ + "/abc/def/000.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/123.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/456.txt");
+  CreateFile(scratch_path_ + "/abc/def/ghi/456.pdf");
+  CreateFile(scratch_path_ + "/abc/def/ghi/jkl/456.txt");
+  CreateFile(scratch_path_ + "/789.txt");
+  CreateFile(scratch_path_ + "/abc/789.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/789.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/789.txt");
+  CreateFile(scratch_path_ + "/abc/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/ccc/123.txt");
+  CreateFile(scratch_path_ + "/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt");
+  CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt");
+
+  // This symlink will cause infinite recursion. It should not be followed.
+  std::filesystem::create_directory_symlink(scratch_path_ + "/abc/aaa/bbb/pqr",
+                                            scratch_path_ + "/abc/aaa/bbb/pqr/lnk");
+
+  // This is a directory. It should not be included in the results.
+  std::filesystem::create_directory(scratch_path_ + "/abc/def/ghi/000.txt");
+
+  std::vector<std::string> patterns = {
+      scratch_path_ + "/abc/def/000.txt",
+      scratch_path_ + "/abc/def/ghi/*.txt",
+      scratch_path_ + "/abc/**/789.txt",
+      scratch_path_ + "/abc/**/mno/*.txt",
+      scratch_path_ + "/abc/**/pqr/**",
+  };
+
+  EXPECT_THAT(Glob(patterns, scratch_path_),
+              UnorderedElementsAre(scratch_path_ + "/abc/def/000.txt",
+                                   scratch_path_ + "/abc/def/ghi/123.txt",
+                                   scratch_path_ + "/abc/def/ghi/456.txt",
+                                   scratch_path_ + "/abc/789.txt",
+                                   scratch_path_ + "/abc/aaa/789.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/789.txt",
+                                   scratch_path_ + "/abc/mno/123.txt",
+                                   scratch_path_ + "/abc/aaa/mno/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/mno/123.txt",
+                                   scratch_path_ + "/abc/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt",
+                                   scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt"));
+}
+
+}  // namespace
+}  // namespace tools
 }  // namespace art
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index af007d1..d3bf475 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2477,8 +2477,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() &&
@@ -2498,8 +2498,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 57e80a5..6817762 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -655,9 +655,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 dc78418..8168004 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/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index b1d3023..8dc5912 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -33,6 +33,7 @@
 #include "arch/instruction_set.h"
 #include "base/common_art_test.h"
 #include "base/file_utils.h"
+#include "base/macros.h"
 #include "base/stl_util.h"
 #include "exec_utils.h"
 #include "gmock/gmock.h"
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 21efd45..375a489 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -1899,8 +1899,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
@@ -1909,7 +1909,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/oat_file.cc b/runtime/oat_file.cc
index c75a9ec..43ab991 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1833,7 +1833,7 @@
         InstructionSetFeatures::FromCppDefines();
     SafeMap<std::string, std::string> store;
     store.Put(OatHeader::kCompilerFilter, CompilerFilter::NameOfFilter(CompilerFilter::kVerify));
-    store.Put(OatHeader::kCompilationReasonKey, "vdex");
+    store.Put(OatHeader::kCompilationReasonKey, kReasonVdex);
     store.Put(OatHeader::kConcurrentCopying,
               gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
     if (context != nullptr) {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 68adb98..b7fa867 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -60,6 +60,10 @@
 }  // namespace collector
 }  // namespace gc
 
+// A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with
+// `ArtConstants::REASON_VDEX` in artd/binder/com/android/server/art/ArtConstants.aidl.
+static constexpr const char* kReasonVdex = "vdex";
+
 // OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can
 // save even one OatMethodOffsets struct, the more complicated encoding
 // using a bitmap pays for itself since few classes will have 160
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index c7c0b1e..d047c23 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -229,12 +229,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",