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",