Update tracing config on git-master-art-host to use streaming am: 4c25ffc893 am: 4f188e20be am: 3d00795382
Original change: https://android-review.googlesource.com/c/platform/art/+/2320558
Change-Id: Id4997208f0b5fb88e43dcffdcdf832f629cad357
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 15377fb..37846eb 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1267,6 +1267,9 @@
]
},
{
+ "name": "art_standalone_dex2oat_tests[com.google.android.art.apex]"
+ },
+ {
"name": "art_standalone_dexdump_tests[com.google.android.art.apex]"
},
{
@@ -1276,6 +1279,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]"
},
{
@@ -2592,6 +2598,9 @@
"name": "art_standalone_compiler_tests"
},
{
+ "name": "art_standalone_dex2oat_tests"
+ },
+ {
"name": "art_standalone_dexdump_tests"
},
{
@@ -2601,6 +2610,9 @@
"name": "art_standalone_dexoptanalyzer_tests"
},
{
+ "name": "art_standalone_libartbase_tests"
+ },
+ {
"name": "art_standalone_libartpalette_tests"
},
{
diff --git a/artd/Android.bp b/artd/Android.bp
index c0ffd0a..e0674f0 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -27,14 +27,21 @@
defaults: ["art_defaults"],
srcs: [
"artd.cc",
+ "file_utils.cc",
+ "path_utils.cc",
+ ],
+ header_libs: [
+ "profman_headers",
],
shared_libs: [
"libarttools",
"libbase",
"libbinder_ndk",
+ "libselinux",
],
static_libs: [
"artd-aidl-ndk",
+ "libc++fs",
],
}
@@ -45,6 +52,7 @@
"artd_main.cc",
],
shared_libs: [
+ "libart",
"libartbase",
],
apex_available: [
@@ -63,8 +71,17 @@
art_cc_defaults {
name: "art_artd_tests_defaults",
defaults: ["artd_defaults"],
+ static_libs: [
+ "libgmock",
+ ],
srcs: [
"artd_test.cc",
+ "file_utils_test.cc",
+ "path_utils_test.cc",
+ ],
+ data: [
+ ":art-gtest-jars-Main",
+ ":art-gtest-jars-Nested",
],
}
@@ -90,4 +107,5 @@
"art_standalone_gtest_defaults",
"art_artd_tests_defaults",
],
+ test_config_template: "art_standalone_artd_tests.xml",
}
diff --git a/artd/art_standalone_artd_tests.xml b/artd/art_standalone_artd_tests.xml
new file mode 100644
index 0000000..9125046
--- /dev/null
+++ b/artd/art_standalone_artd_tests.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="art-gtest-jars-Main.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Main.jar" />
+ <option name="push" value="art-gtest-jars-Nested.jar->/data/local/tmp/{MODULE}/art-gtest-jars-Nested.jar" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+ <option name="module-name" value="{MODULE}" />
+ <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+ <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+ </test>
+
+ <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+ one of the Mainline modules below is present on the device used for testing. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <!-- ART Mainline Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.art" />
+ <!-- ART Mainline Module (external (AOSP) version). -->
+ <option name="mainline-module-package-name" value="com.android.art" />
+ </object>
+
+ <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <!-- TODO(jiakaiz): Change this to U once `ro.build.version.sdk` is bumped. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/artd/artd.cc b/artd/artd.cc
index 27a609d..459f8d4 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -16,16 +16,56 @@
#include "artd.h"
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+#include <climits>
+#include <csignal>
+#include <cstdint>
+#include <cstring>
+#include <filesystem>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <ostream>
#include <string>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+#include <vector>
#include "aidl/com/android/server/art/BnArtd.h"
+#include "aidl/com/android/server/art/DexoptTrigger.h"
+#include "aidl/com/android/server/art/IArtdCancellationSignal.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
#include "android/binder_manager.h"
#include "android/binder_process.h"
+#include "base/compiler_filter.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/os.h"
+#include "exec_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+#include "oat_file_assistant_context.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "selinux/android.h"
+#include "tools/cmdline_builder.h"
#include "tools/tools.h"
namespace art {
@@ -33,19 +73,886 @@
namespace {
+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::DexoptResult;
+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::GetOptimizationStatusResult;
+using ::aidl::com::android::server::art::IArtdCancellationSignal;
+using ::aidl::com::android::server::art::MergeProfileOptions;
+using ::aidl::com::android::server::art::OutputArtifacts;
+using ::aidl::com::android::server::art::OutputProfile;
+using ::aidl::com::android::server::art::PriorityClass;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::Dirname;
using ::android::base::Error;
+using ::android::base::Join;
+using ::android::base::make_scope_guard;
+using ::android::base::ReadFileToString;
using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringReplace;
+using ::android::base::WriteStringToFd;
+using ::art::tools::CmdlineBuilder;
using ::ndk::ScopedAStatus;
+using ::fmt::literals::operator""_format; // NOLINT
+
+using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
constexpr const char* kServiceName = "artd";
+constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal";
+
+// Timeout for short operations, such as merging profiles.
+constexpr int kShortTimeoutSec = 60; // 1 minute.
+
+// Timeout for long operations, such as compilation. We set it to be smaller than the Package
+// Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation
+// is called from the Package Manager's thread handler, it will be aborted before that watchdog
+// would take down the system server.
+constexpr int kLongTimeoutSec = 570; // 9.5 minutes.
+
+std::optional<int64_t> GetSize(std::string_view path) {
+ std::error_code ec;
+ int64_t size = std::filesystem::file_size(path, ec);
+ if (ec) {
+ // It is okay if the file does not exist. We don't have to log it.
+ if (ec.value() != ENOENT) {
+ LOG(ERROR) << "Failed to get the file size of '{}': {}"_format(path, ec.message());
+ }
+ return std::nullopt;
+ }
+ return size;
+}
+
+// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
+// error occurs.
+int64_t GetSizeAndDeleteFile(const std::string& path) {
+ std::optional<int64_t> size = GetSize(path);
+ if (!size.has_value()) {
+ return 0;
+ }
+
+ std::error_code ec;
+ if (!std::filesystem::remove(path, ec)) {
+ LOG(ERROR) << "Failed to remove '{}': {}"_format(path, ec.message());
+ return 0;
+ }
+
+ return size.value();
+}
+
+std::string EscapeErrorMessage(const std::string& message) {
+ return StringReplace(message, std::string("\0", /*n=*/1), "\\0", /*all=*/true);
+}
+
+// Indicates an error that should never happen (e.g., illegal arguments passed by service-art
+// internally). System server should crash if this kind of error happens.
+ScopedAStatus Fatal(const std::string& message) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
+ EscapeErrorMessage(message).c_str());
+}
+
+// Indicates an error that service-art should handle (e.g., I/O errors, sub-process crashes).
+// The scope of the error depends on the function that throws it, so service-art should catch the
+// error at every call site and take different actions.
+// Ideally, this should be a checked exception or an additional return value that forces service-art
+// to handle it, but `ServiceSpecificException` (a separate runtime exception type) is the best
+// approximate we have given the limitation of Java and Binder.
+ScopedAStatus NonFatal(const std::string& message) {
+ constexpr int32_t kArtdNonFatalErrorCode = 1;
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(kArtdNonFatalErrorCode,
+ EscapeErrorMessage(message).c_str());
+}
+
+Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) {
+ CompilerFilter::Filter compiler_filter;
+ if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) {
+ return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str);
+ }
+ return compiler_filter;
+}
+
+OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int32_t aidl_value) {
+ OatFileAssistant::DexOptTrigger trigger{};
+ if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) {
+ trigger.targetFilterIsBetter = true;
+ }
+ if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) {
+ trigger.targetFilterIsSame = true;
+ }
+ if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) {
+ trigger.targetFilterIsWorse = true;
+ }
+ if ((aidl_value & static_cast<int32_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) {
+ trigger.primaryBootImageBecomesUsable = true;
+ }
+ return trigger;
+}
+
+ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) {
+ switch (location) {
+ case OatFileAssistant::Location::kLocationNoneOrError:
+ return ArtifactsLocation::NONE_OR_ERROR;
+ case OatFileAssistant::Location::kLocationOat:
+ return ArtifactsLocation::DALVIK_CACHE;
+ case OatFileAssistant::Location::kLocationOdex:
+ return ArtifactsLocation::NEXT_TO_DEX;
+ case OatFileAssistant::Location::kLocationDm:
+ return ArtifactsLocation::DM;
+ // No default. All cases should be explicitly handled, or the compilation will fail.
+ }
+ // This should never happen. Just in case we get a non-enumerator value.
+ LOG(FATAL) << "Unexpected Location " << location;
+}
+
+Result<void> PrepareArtifactsDir(
+ const std::string& path,
+ const FsPermission& fs_permission,
+ const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context =
+ std::nullopt) {
+ 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));
+
+ if (kIsTargetAndroid) {
+ 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);
+ }
+ }
+
+ cleanup.Disable();
+ return {};
+}
+
+Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts) {
+ 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,
+ output_artifacts.permissionSettings.seContext));
+ OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission));
+ return {};
+}
+
+Result<FileVisibility> GetFileVisibility(const std::string& file) {
+ std::error_code ec;
+ std::filesystem::file_status status = std::filesystem::status(file, ec);
+ if (!std::filesystem::status_known(status)) {
+ return Errorf("Failed to get status of '{}': {}", file, ec.message());
+ }
+ if (!std::filesystem::exists(status)) {
+ return FileVisibility::NOT_FOUND;
+ }
+
+ return (status.permissions() & std::filesystem::perms::others_read) !=
+ std::filesystem::perms::none ?
+ FileVisibility::OTHER_READABLE :
+ FileVisibility::NOT_OTHER_READABLE;
+}
+
+Result<ArtdCancellationSignal*> ToArtdCancellationSignal(IArtdCancellationSignal* input) {
+ if (input == nullptr) {
+ return Error() << "Cancellation signal must not be nullptr";
+ }
+ // We cannot use `dynamic_cast` because ART code is compiled with `-fno-rtti`, so we have to check
+ // the magic number.
+ int64_t type;
+ if (!input->getType(&type).isOk() ||
+ type != reinterpret_cast<intptr_t>(kArtdCancellationSignalType)) {
+ // The cancellation signal must be created by `Artd::createCancellationSignal`.
+ return Error() << "Invalid cancellation signal type";
+ }
+ return static_cast<ArtdCancellationSignal*>(input);
+}
+
+Result<void> CopyFile(const std::string& src_path, const NewFile& dst_file) {
+ std::string content;
+ if (!ReadFileToString(src_path, &content)) {
+ return Errorf("Failed to read file '{}': {}", src_path, strerror(errno));
+ }
+ if (!WriteStringToFd(content, dst_file.Fd())) {
+ return Errorf("Failed to write file '{}': {}", dst_file.TempPath(), strerror(errno));
+ }
+ if (fsync(dst_file.Fd()) != 0) {
+ return Errorf("Failed to flush file '{}': {}", dst_file.TempPath(), strerror(errno));
+ }
+ if (lseek(dst_file.Fd(), /*offset=*/0, SEEK_SET) != 0) {
+ return Errorf(
+ "Failed to reset the offset for file '{}': {}", dst_file.TempPath(), strerror(errno));
+ }
+ return {};
+}
+
+class FdLogger {
+ public:
+ void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); }
+ void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); }
+
+ 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::getOptimizationStatus(const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::string& in_classLoaderContext,
+ GetOptimizationStatusResult* _aidl_return) {
+ Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+ if (!ofa_context.ok()) {
+ return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
+ }
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ auto oat_file_assistant = OatFileAssistant::Create(in_dexFile.c_str(),
+ in_instructionSet.c_str(),
+ in_classLoaderContext.c_str(),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ ofa_context.value(),
+ &context,
+ &error_msg);
+ if (oat_file_assistant == nullptr) {
+ return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+ }
+
+ std::string ignored_odex_status;
+ oat_file_assistant->GetOptimizationStatus(&_aidl_return->locationDebugString,
+ &_aidl_return->compilerFilter,
+ &_aidl_return->compilationReason,
+ &ignored_odex_status);
+
+ // We ignore odex_status because it is not meaningful. It can only be either "up-to-date",
+ // "apk-more-recent", or "io-error-no-oat", which means it doesn't give us information in addition
+ // to what we can learn from compiler_filter because compiler_filter will be the actual compiler
+ // filter, "run-from-apk-fallback", and "run-from-apk" in those three cases respectively.
+ DCHECK(ignored_odex_status == "up-to-date" || ignored_odex_status == "apk-more-recent" ||
+ ignored_odex_status == "io-error-no-oat");
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile,
+ const std::string& in_dexFile,
+ bool* _aidl_return) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+ CmdlineBuilder args;
+ FdLogger fd_logger;
+ args.Add(OR_RETURN_FATAL(GetArtExec()))
+ .Add("--drop-capabilities")
+ .Add("--")
+ .Add(OR_RETURN_FATAL(GetProfman()));
+
+ Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path);
+ if (!profile.ok()) {
+ if (profile.error().code() == ENOENT) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+ return NonFatal(
+ "Failed to open profile '{}': {}"_format(profile_path, profile.error().message()));
+ }
+ args.Add("--reference-profile-file-fd=%d", profile.value()->Fd());
+ fd_logger.Add(*profile.value());
+
+ std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+ args.Add("--apk-fd=%d", dex_file->Fd());
+ fd_logger.Add(*dex_file);
+
+ LOG(INFO) << "Running profman: " << Join(args.Get(), /*separator=*/" ")
+ << "\nOpened FDs: " << fd_logger;
+
+ Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec);
+ if (!result.ok()) {
+ return NonFatal("Failed to run profman: " + result.error().message());
+ }
+
+ LOG(INFO) << "profman returned code {}"_format(result.value());
+
+ if (result.value() != ProfmanResult::kSkipCompilationSmallDelta &&
+ result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) {
+ return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+ }
+
+ *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta;
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src,
+ OutputProfile* in_dst,
+ const std::string& in_dexFile,
+ bool* _aidl_return) {
+ std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
+ std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath));
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+
+ CmdlineBuilder args;
+ FdLogger fd_logger;
+ args.Add(OR_RETURN_FATAL(GetArtExec()))
+ .Add("--drop-capabilities")
+ .Add("--")
+ .Add(OR_RETURN_FATAL(GetProfman()))
+ .Add("--copy-and-update-profile-key");
+
+ Result<std::unique_ptr<File>> src = OpenFileForReading(src_path);
+ if (!src.ok()) {
+ if (src.error().code() == ENOENT) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+ return NonFatal("Failed to open src profile '{}': {}"_format(src_path, src.error().message()));
+ }
+ args.Add("--profile-file-fd=%d", src.value()->Fd());
+ fd_logger.Add(*src.value());
+
+ std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+ args.Add("--apk-fd=%d", dex_file->Fd());
+ fd_logger.Add(*dex_file);
+
+ std::unique_ptr<NewFile> dst =
+ OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission));
+ args.Add("--reference-profile-file-fd=%d", dst->Fd());
+ fd_logger.Add(*dst);
+
+ LOG(INFO) << "Running profman: " << Join(args.Get(), /*separator=*/" ")
+ << "\nOpened FDs: " << fd_logger;
+
+ Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec);
+ if (!result.ok()) {
+ return NonFatal("Failed to run profman: " + result.error().message());
+ }
+
+ LOG(INFO) << "profman returned code {}"_format(result.value());
+
+ if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+
+ if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) {
+ return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+ }
+
+ OR_RETURN_NON_FATAL(dst->Keep());
+ *_aidl_return = true;
+ in_dst->profilePath.id = dst->TempId();
+ in_dst->profilePath.tmpPath = dst->TempPath();
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
+ std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile));
+ std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile));
+
+ std::error_code ec;
+ std::filesystem::rename(tmp_profile_path, ref_profile_path, ec);
+ if (ec) {
+ return NonFatal(
+ "Failed to move '{}' to '{}': {}"_format(tmp_profile_path, ref_profile_path, ec.message()));
+ }
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+
+ std::error_code ec;
+ if (!std::filesystem::remove(profile_path, ec) && ec.value() != ENOENT) {
+ LOG(ERROR) << "Failed to remove '{}': {}"_format(profile_path, ec.message());
+ }
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile,
+ FileVisibility* _aidl_return) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path));
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath,
+ FileVisibility* _aidl_return) {
+ std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path));
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile,
+ FileVisibility* _aidl_return) {
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile));
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile,
+ FileVisibility* _aidl_return) {
+ std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path));
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles,
+ const std::optional<ProfilePath>& in_referenceProfile,
+ OutputProfile* in_outputProfile,
+ const std::vector<std::string>& in_dexFiles,
+ const MergeProfileOptions& in_options,
+ bool* _aidl_return) {
+ std::vector<std::string> profile_paths;
+ for (const ProfilePath& profile : in_profiles) {
+ std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(profile));
+ if (profile.getTag() == ProfilePath::dexMetadataPath) {
+ return Fatal("Does not support DM file, got '{}'"_format(profile_path));
+ }
+ profile_paths.push_back(std::move(profile_path));
+ }
+ std::string output_profile_path =
+ OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath));
+ for (const std::string& dex_file : in_dexFiles) {
+ OR_RETURN_FATAL(ValidateDexPath(dex_file));
+ }
+
+ CmdlineBuilder args;
+ FdLogger fd_logger;
+ args.Add(OR_RETURN_FATAL(GetArtExec()))
+ .Add("--drop-capabilities")
+ .Add("--")
+ .Add(OR_RETURN_FATAL(GetProfman()));
+
+ std::vector<std::unique_ptr<File>> profile_files;
+ for (const std::string& profile_path : profile_paths) {
+ Result<std::unique_ptr<File>> profile_file = OpenFileForReading(profile_path);
+ if (!profile_file.ok()) {
+ if (profile_file.error().code() == ENOENT) {
+ // Skip non-existing file.
+ continue;
+ }
+ return NonFatal(
+ "Failed to open profile '{}': {}"_format(profile_path, profile_file.error().message()));
+ }
+ args.Add("--profile-file-fd=%d", profile_file.value()->Fd());
+ fd_logger.Add(*profile_file.value());
+ profile_files.push_back(std::move(profile_file.value()));
+ }
+
+ if (profile_files.empty()) {
+ LOG(INFO) << "Merge skipped because there are no existing profiles";
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+
+ std::unique_ptr<NewFile> output_profile_file =
+ OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission));
+
+ if (in_referenceProfile.has_value()) {
+ std::string reference_profile_path =
+ OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile));
+ if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) {
+ return Fatal("Does not support DM file, got '{}'"_format(reference_profile_path));
+ }
+ OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file));
+ }
+
+ // 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));
+ }
+
+ 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");
+
+ LOG(INFO) << "Running profman: " << Join(args.Get(), /*separator=*/" ")
+ << "\nOpened FDs: " << fd_logger;
+
+ Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec);
+ if (!result.ok()) {
+ return NonFatal("Failed to run profman: " + result.error().message());
+ }
+
+ LOG(INFO) << "profman returned code {}"_format(result.value());
+
+ if (result.value() == ProfmanResult::kSkipCompilationSmallDelta ||
+ result.value() == ProfmanResult::kSkipCompilationEmptyProfiles) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+
+ ProfmanResult::ProcessingResult expected_result =
+ in_options.forceMerge ? ProfmanResult::kSuccess : ProfmanResult::kCompile;
+ if (result.value() != expected_result) {
+ return NonFatal("profman returned an unexpected code: {}"_format(result.value()));
+ }
+
+ OR_RETURN_NON_FATAL(output_profile_file->Keep());
+ *_aidl_return = true;
+ in_outputProfile->profilePath.id = output_profile_file->TempId();
+ in_outputProfile->profilePath.tmpPath = output_profile_file->TempPath();
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::optional<std::string>& in_classLoaderContext,
+ const std::string& in_compilerFilter,
+ int32_t in_dexoptTrigger,
+ GetDexoptNeededResult* _aidl_return) {
+ Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext();
+ if (!ofa_context.ok()) {
+ return NonFatal("Failed to get runtime options: " + ofa_context.error().message());
+ }
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ auto oat_file_assistant = OatFileAssistant::Create(in_dexFile,
+ in_instructionSet,
+ in_classLoaderContext,
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ ofa_context.value(),
+ &context,
+ &error_msg);
+ if (oat_file_assistant == nullptr) {
+ return NonFatal("Failed to create OatFileAssistant: " + error_msg);
+ }
+
+ OatFileAssistant::DexOptStatus status;
+ _aidl_return->isDexoptNeeded =
+ oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)),
+ DexOptTriggerFromAidl(in_dexoptTrigger),
+ &status);
+ _aidl_return->isVdexUsable = status.IsVdexUsable();
+ _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation());
+
+ return ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Artd::dexopt(
+ const OutputArtifacts& in_outputArtifacts,
+ const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::optional<std::string>& in_classLoaderContext,
+ const std::string& in_compilerFilter,
+ const std::optional<ProfilePath>& in_profile,
+ const std::optional<VdexPath>& in_inputVdex,
+ const std::optional<DexMetadataPath>& in_dmFile,
+ PriorityClass in_priorityClass,
+ const DexoptOptions& in_dexoptOptions,
+ const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal,
+ DexoptResult* _aidl_return) {
+ _aidl_return->cancelled = false;
+
+ std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath));
+ std::string vdex_path = OatPathToVdexPath(oat_path);
+ std::string art_path = OatPathToArtPath(oat_path);
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+ std::optional<std::string> profile_path =
+ in_profile.has_value() ?
+ std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) :
+ std::nullopt;
+ ArtdCancellationSignal* cancellation_signal =
+ OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get()));
+
+ std::unique_ptr<ClassLoaderContext> context = nullptr;
+ if (in_classLoaderContext.has_value()) {
+ context = ClassLoaderContext::Create(in_classLoaderContext->c_str());
+ if (context == nullptr) {
+ return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext.value()));
+ }
+ }
+
+ OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts));
+
+ CmdlineBuilder args;
+ args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities");
+
+ if (in_priorityClass < PriorityClass::BOOT) {
+ args.Add("--set-task-profile=Dex2OatBootComplete").Add("--set-priority=background");
+ }
+
+ args.Add("--").Add(OR_RETURN_FATAL(GetDex2Oat()));
+ FdLogger fd_logger;
+
+ const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission;
+
+ std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile));
+ args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile);
+ fd_logger.Add(*dex_file);
+ struct stat dex_st = OR_RETURN_NON_FATAL(Fstat(*dex_file));
+ if ((dex_st.st_mode & S_IROTH) == 0) {
+ if (fs_permission.isOtherReadable) {
+ return NonFatal(
+ "Outputs cannot be other-readable because the dex file '{}' is not other-readable"_format(
+ dex_file->GetPath()));
+ }
+ // Negative numbers mean no `chown`. 0 means root.
+ // Note: this check is more strict than it needs to be. For example, it doesn't allow the
+ // outputs to belong to a group that is a subset of the dex file's group. This is for
+ // simplicity, and it's okay as we don't have to handle such complicated cases in practice.
+ if ((fs_permission.uid > 0 && static_cast<uid_t>(fs_permission.uid) != dex_st.st_uid) ||
+ (fs_permission.gid > 0 && static_cast<gid_t>(fs_permission.gid) != dex_st.st_uid &&
+ static_cast<gid_t>(fs_permission.gid) != dex_st.st_gid)) {
+ return NonFatal(
+ "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})"_format(
+ dex_file->GetPath(),
+ fs_permission.uid,
+ fs_permission.gid,
+ dex_st.st_uid,
+ dex_st.st_gid));
+ }
+ }
+
+ std::unique_ptr<NewFile> oat_file = OR_RETURN_NON_FATAL(NewFile::Create(oat_path, fs_permission));
+ args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", oat_path);
+ fd_logger.Add(*oat_file);
+
+ std::unique_ptr<NewFile> vdex_file =
+ OR_RETURN_NON_FATAL(NewFile::Create(vdex_path, fs_permission));
+ args.Add("--output-vdex-fd=%d", vdex_file->Fd());
+ fd_logger.Add(*vdex_file);
+
+ std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()};
+ std::vector<std::string_view> files_to_delete;
+
+ std::unique_ptr<NewFile> art_file = nullptr;
+ if (in_dexoptOptions.generateAppImage) {
+ art_file = OR_RETURN_NON_FATAL(NewFile::Create(art_path, fs_permission));
+ args.Add("--app-image-fd=%d", art_file->Fd());
+ args.AddIfNonEmpty("--image-format=%s", props_->GetOrEmpty("dalvik.vm.appimageformat"));
+ fd_logger.Add(*art_file);
+ files_to_commit.push_back(art_file.get());
+ } else {
+ files_to_delete.push_back(art_path);
+ }
+
+ std::unique_ptr<NewFile> swap_file = nullptr;
+ if (ShouldCreateSwapFileForDexopt()) {
+ swap_file = OR_RETURN_NON_FATAL(
+ NewFile::Create("{}.swap"_format(oat_path), FsPermission{.uid = -1, .gid = -1}));
+ args.Add("--swap-fd=%d", swap_file->Fd());
+ fd_logger.Add(*swap_file);
+ }
+
+ std::vector<std::unique_ptr<File>> context_files;
+ if (context != nullptr) {
+ std::vector<std::string> flattened_context = context->FlattenDexPaths();
+ std::string dex_dir = Dirname(in_dexFile.c_str());
+ std::vector<int> context_fds;
+ for (const std::string& context_element : flattened_context) {
+ std::string context_path = std::filesystem::path(dex_dir).append(context_element);
+ OR_RETURN_FATAL(ValidateDexPath(context_path));
+ std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path));
+ context_fds.push_back(context_file->Fd());
+ fd_logger.Add(*context_file);
+ context_files.push_back(std::move(context_file));
+ }
+ args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':'))
+ .Add("--class-loader-context=%s", in_classLoaderContext.value())
+ .Add("--classpath-dir=%s", dex_dir);
+ }
+
+ std::unique_ptr<File> input_vdex_file = nullptr;
+ if (in_inputVdex.has_value()) {
+ std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value()));
+ input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
+ args.Add("--input-vdex-fd=%d", input_vdex_file->Fd());
+ fd_logger.Add(*input_vdex_file);
+ }
+
+ std::unique_ptr<File> dm_file = nullptr;
+ if (in_dmFile.has_value()) {
+ std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value()));
+ dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path));
+ args.Add("--dm-fd=%d", dm_file->Fd());
+ fd_logger.Add(*dm_file);
+ }
+
+ std::unique_ptr<File> profile_file = nullptr;
+ if (profile_path.has_value()) {
+ profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value()));
+ args.Add("--profile-file-fd=%d", profile_file->Fd());
+ fd_logger.Add(*profile_file);
+ struct stat profile_st = OR_RETURN_NON_FATAL(Fstat(*profile_file));
+ if (fs_permission.isOtherReadable && (profile_st.st_mode & S_IROTH) == 0) {
+ return NonFatal(
+ "Outputs cannot be other-readable because the profile '{}' is not other-readable"_format(
+ profile_file->GetPath()));
+ }
+ // TODO(b/260228411): Check uid and gid.
+ }
+
+ AddBootImageFlags(args);
+ AddCompilerConfigFlags(
+ in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args);
+ AddPerfConfigFlags(in_priorityClass, args);
+
+ LOG(INFO) << "Running dex2oat: " << Join(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(args.Get(), kLongTimeoutSec, callbacks, &stat);
+ _aidl_return->wallTimeMs = stat.wall_time_ms;
+ _aidl_return->cpuTimeMs = stat.cpu_time_ms;
+ if (!result.ok()) {
+ {
+ std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
+ if (cancellation_signal->is_cancelled_) {
+ _aidl_return->cancelled = true;
+ return ScopedAStatus::ok();
+ }
+ }
+ return NonFatal("Failed to run dex2oat: " + result.error().message());
+ }
+
+ LOG(INFO) << "dex2oat returned code {}"_format(result.value());
+
+ if (result.value() != 0) {
+ return NonFatal("dex2oat returned an unexpected code: {}"_format(result.value()));
+ }
+
+ int64_t size_bytes = 0;
+ int64_t size_before_bytes = 0;
+ for (const NewFile* file : files_to_commit) {
+ size_bytes += GetSize(file->TempPath()).value_or(0);
+ size_before_bytes += GetSize(file->FinalPath()).value_or(0);
+ }
+ for (std::string_view path : files_to_delete) {
+ size_before_bytes += GetSize(path).value_or(0);
+ }
+ OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete));
+
+ _aidl_return->sizeBytes = size_bytes;
+ _aidl_return->sizeBeforeBytes = size_before_bytes;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::cancel() {
+ std::lock_guard<std::mutex> lock(mu_);
+ is_cancelled_ = true;
+ for (pid_t pid : pids_) {
+ int res = kill_(pid, SIGKILL);
+ DCHECK_EQ(res, 0);
+ }
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) {
+ *_aidl_return = reinterpret_cast<intptr_t>(kArtdCancellationSignalType);
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::createCancellationSignal(
+ std::shared_ptr<IArtdCancellationSignal>* _aidl_return) {
+ *_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_);
+ return ScopedAStatus::ok();
+}
+
Result<void> Artd::Start() {
ScopedAStatus status = ScopedAStatus::fromStatus(
AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
@@ -58,5 +965,223 @@
return {};
}
+Result<OatFileAssistantContext*> Artd::GetOatFileAssistantContext() {
+ std::lock_guard<std::mutex> lock(ofa_context_mu_);
+
+ if (ofa_context_ == nullptr) {
+ ofa_context_ = std::make_unique<OatFileAssistantContext>(
+ std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+ OatFileAssistantContext::RuntimeOptions{
+ .image_locations = *OR_RETURN(GetBootImageLocations()),
+ .boot_class_path = *OR_RETURN(GetBootClassPath()),
+ .boot_class_path_locations = *OR_RETURN(GetBootClassPath()),
+ .deny_art_apex_data_files = DenyArtApexDataFiles(),
+ }));
+ std::string error_msg;
+ if (!ofa_context_->FetchAll(&error_msg)) {
+ return Error() << error_msg;
+ }
+ }
+
+ return ofa_context_.get();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootImageLocations() {
+ std::lock_guard<std::mutex> lock(cache_mu_);
+
+ if (!cached_boot_image_locations_.has_value()) {
+ std::string location_str;
+
+ if (UseJitZygoteLocked()) {
+ location_str = GetJitZygoteBootImageLocation();
+ } else if (std::string value = GetUserDefinedBootImageLocationsLocked(); !value.empty()) {
+ location_str = std::move(value);
+ } else {
+ std::string error_msg;
+ std::string android_root = GetAndroidRootSafe(&error_msg);
+ if (!error_msg.empty()) {
+ return Errorf("Failed to get ANDROID_ROOT: {}", error_msg);
+ }
+ location_str = GetDefaultBootImageLocation(android_root, DenyArtApexDataFilesLocked());
+ }
+
+ cached_boot_image_locations_ = Split(location_str, ":");
+ }
+
+ return &cached_boot_image_locations_.value();
+}
+
+Result<const std::vector<std::string>*> Artd::GetBootClassPath() {
+ std::lock_guard<std::mutex> lock(cache_mu_);
+
+ if (!cached_boot_class_path_.has_value()) {
+ const char* env_value = getenv("BOOTCLASSPATH");
+ if (env_value == nullptr || strlen(env_value) == 0) {
+ return Errorf("Failed to get environment variable 'BOOTCLASSPATH'");
+ }
+ cached_boot_class_path_ = Split(env_value, ":");
+ }
+
+ return &cached_boot_class_path_.value();
+}
+
+bool Artd::UseJitZygote() {
+ std::lock_guard<std::mutex> lock(cache_mu_);
+ return UseJitZygoteLocked();
+}
+
+bool Artd::UseJitZygoteLocked() {
+ if (!cached_use_jit_zygote_.has_value()) {
+ cached_use_jit_zygote_ =
+ props_->GetBool("persist.device_config.runtime_native_boot.profilebootclasspath",
+ "dalvik.vm.profilebootclasspath",
+ /*default_value=*/false);
+ }
+
+ return cached_use_jit_zygote_.value();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocations() {
+ std::lock_guard<std::mutex> lock(cache_mu_);
+ return GetUserDefinedBootImageLocationsLocked();
+}
+
+const std::string& Artd::GetUserDefinedBootImageLocationsLocked() {
+ if (!cached_user_defined_boot_image_locations_.has_value()) {
+ cached_user_defined_boot_image_locations_ = props_->GetOrEmpty("dalvik.vm.boot-image");
+ }
+
+ return cached_user_defined_boot_image_locations_.value();
+}
+
+bool Artd::DenyArtApexDataFiles() {
+ std::lock_guard<std::mutex> lock(cache_mu_);
+ return DenyArtApexDataFilesLocked();
+}
+
+bool Artd::DenyArtApexDataFilesLocked() {
+ if (!cached_deny_art_apex_data_files_.has_value()) {
+ cached_deny_art_apex_data_files_ =
+ !props_->GetBool("odsign.verification.success", /*default_value=*/false);
+ }
+
+ return cached_deny_art_apex_data_files_.value();
+}
+
+Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); }
+
+Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); }
+
+bool Artd::ShouldUseDex2Oat64() {
+ return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() &&
+ props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false);
+}
+
+Result<std::string> Artd::GetDex2Oat() {
+ std::string binary_name = ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32";
+ // TODO(b/234351700): Should we use the "d" variant?
+ return BuildArtBinPath(binary_name);
+}
+
+bool Artd::ShouldCreateSwapFileForDexopt() {
+ // Create a swap file by default. Dex2oat will decide whether to use it or not.
+ return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true);
+}
+
+void Artd::AddBootImageFlags(/*out*/ CmdlineBuilder& args) {
+ if (UseJitZygote()) {
+ args.Add("--force-jit-zygote");
+ } else {
+ args.AddIfNonEmpty("--boot-image=%s", GetUserDefinedBootImageLocations());
+ }
+}
+
+void Artd::AddCompilerConfigFlags(const std::string& instruction_set,
+ const std::string& compiler_filter,
+ PriorityClass priority_class,
+ const DexoptOptions& dexopt_options,
+ /*out*/ CmdlineBuilder& args) {
+ args.Add("--instruction-set=%s", instruction_set);
+ std::string features_prop = "dalvik.vm.isa.{}.features"_format(instruction_set);
+ args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop));
+ std::string variant_prop = "dalvik.vm.isa.{}.variant"_format(instruction_set);
+ args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop));
+
+ args.Add("--compiler-filter=%s", compiler_filter)
+ .Add("--compilation-reason=%s", dexopt_options.compilationReason);
+
+ args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none");
+
+ args.AddIfNonEmpty("--max-image-block-size=%s",
+ props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size"))
+ .AddIfNonEmpty("--very-large-app-threshold=%s",
+ props_->GetOrEmpty("dalvik.vm.dex2oat-very-large"))
+ .AddIfNonEmpty(
+ "--resolve-startup-const-strings=%s",
+ props_->GetOrEmpty("persist.device_config.runtime.dex2oat_resolve_startup_strings",
+ "dalvik.vm.dex2oat-resolve-startup-strings"));
+
+ args.AddIf(dexopt_options.debuggable, "--debuggable")
+ .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false),
+ "--generate-debug-info")
+ .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false),
+ "--generate-mini-debug-info");
+
+ args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files")
+ .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion)
+ .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled");
+}
+
+void Artd::AddPerfConfigFlags(PriorityClass priority_class, /*out*/ CmdlineBuilder& 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);
+ }
+ args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads);
+
+ 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.
+ 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..484c281 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -17,18 +17,209 @@
#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 getOptimizationStatus(
+ const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::string& in_classLoaderContext,
+ aidl::com::android::server::art::GetOptimizationStatusResult* _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::DexoptResult* _aidl_return) override;
+
+ ndk::ScopedAStatus createCancellationSignal(
+ std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>* _aidl_return)
+ override;
+
android::base::Result<void> Start();
+
+ private:
+ android::base::Result<OatFileAssistantContext*> GetOatFileAssistantContext()
+ EXCLUDES(ofa_context_mu_);
+
+ android::base::Result<const std::vector<std::string>*> GetBootImageLocations()
+ EXCLUDES(cache_mu_);
+
+ android::base::Result<const std::vector<std::string>*> GetBootClassPath() EXCLUDES(cache_mu_);
+
+ bool UseJitZygote() EXCLUDES(cache_mu_);
+ bool UseJitZygoteLocked() REQUIRES(cache_mu_);
+
+ const std::string& GetUserDefinedBootImageLocations() EXCLUDES(cache_mu_);
+ const std::string& GetUserDefinedBootImageLocationsLocked() REQUIRES(cache_mu_);
+
+ bool DenyArtApexDataFiles() EXCLUDES(cache_mu_);
+ bool DenyArtApexDataFilesLocked() REQUIRES(cache_mu_);
+
+ android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+ int timeout_sec,
+ const ExecCallbacks& callbacks = ExecCallbacks(),
+ ProcessStat* stat = nullptr) const;
+
+ android::base::Result<std::string> GetProfman();
+
+ android::base::Result<std::string> GetArtExec();
+
+ bool ShouldUseDex2Oat64();
+
+ android::base::Result<std::string> GetDex2Oat();
+
+ bool ShouldCreateSwapFileForDexopt();
+
+ void AddBootImageFlags(/*out*/ art::tools::CmdlineBuilder& args);
+
+ void AddCompilerConfigFlags(const std::string& instruction_set,
+ const std::string& compiler_filter,
+ aidl::com::android::server::art::PriorityClass priority_class,
+ const aidl::com::android::server::art::DexoptOptions& dexopt_options,
+ /*out*/ art::tools::CmdlineBuilder& args);
+
+ void AddPerfConfigFlags(aidl::com::android::server::art::PriorityClass priority_class,
+ /*out*/ art::tools::CmdlineBuilder& 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..44250e5 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -16,26 +16,409 @@
#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 <vector>
+
+#include "aidl/com/android/server/art/BnArtd.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/common_art_test.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "path_utils.h"
+#include "profman/profman_result.h"
+#include "tools/system_properties.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::DexoptOptions;
+using ::aidl::com::android::server::art::DexoptResult;
+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::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::WithArg;
+
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using ::fmt::literals::operator""_format; // NOLINT
+
+ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
+ android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger));
+ return make_scope_guard([old_logger = std::move(old_logger)]() mutable {
+ android::base::SetLogger(std::move(old_logger));
+ });
+}
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+ std::string actual_content;
+ ASSERT_TRUE(ReadFileToString(path, &actual_content));
+ EXPECT_EQ(actual_content, expected_content);
+}
+
+void CheckOtherReadable(const std::string& path, bool expected_value) {
+ EXPECT_EQ((std::filesystem::status(path).permissions() & std::filesystem::perms::others_read) !=
+ std::filesystem::perms::none,
+ expected_value);
+}
+
+void WriteToFdFlagImpl(const std::vector<std::string>& args,
+ const std::string& flag,
+ const std::string& content,
+ bool assume_empty) {
+ for (const std::string& arg : args) {
+ std::string_view value(arg);
+ if (android::base::ConsumePrefix(&value, flag)) {
+ int fd;
+ ASSERT_TRUE(ParseInt(std::string(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));
+ return;
+ }
+ }
+ FAIL() << "Flag '{}' not found"_format(flag);
+}
+
+// Writes `content` to the FD specified by the `flag`.
+ACTION_P(WriteToFdFlag, flag, content) {
+ WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/true);
+}
+
+// Clears any existing content and writes `content` to the FD specified by the `flag`.
+ACTION_P(ClearAndWriteToFdFlag, flag, content) {
+ WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/false);
+}
+
+// Matches a flag that starts with `flag` and whose value matches `matcher`.
+MATCHER_P2(Flag, flag, matcher, "") {
+ std::string_view value(arg);
+ if (!android::base::ConsumePrefix(&value, flag)) {
+ return false;
+ }
+ return ExplainMatchResult(matcher, std::string(value), result_listener);
+}
+
+// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches
+// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument.
+MATCHER_P2(ListFlag, flag, matcher, "") {
+ return ExplainMatchResult(
+ Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)),
+ arg,
+ result_listener);
+}
+
+// Matches an FD of a file whose path matches `matcher`.
+MATCHER_P(FdOf, matcher, "") {
+ std::string proc_path = "/proc/self/fd/{}"_format(arg);
+ char path[PATH_MAX];
+ ssize_t len = readlink(proc_path.c_str(), path, sizeof(path));
+ if (len < 0) {
+ return false;
+ }
+ return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener);
+}
+
+// Matches an FD of a file whose content matches `matcher`.
+MATCHER_P(FdHasContent, matcher, "") {
+ int fd;
+ if (!ParseInt(arg, &fd)) {
+ return false;
+ }
+ std::string actual_content;
+ if (!ReadFdToString(fd, &actual_content)) {
+ return false;
+ }
+ return ExplainMatchResult(matcher, actual_content, result_listener);
+}
+
+// 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, "") {
+ using Value = const typename std::remove_reference<decltype(arg)>::type::value_type;
+ auto it = std::find(arg.begin(), arg.end(), separator);
+ if (it == arg.end()) {
+ return false;
+ }
+ size_t pos = it - arg.begin();
+ return ExplainMatchResult(head_matcher, ArrayRef<Value>(arg).SubArray(0, pos), result_listener) &&
+ ExplainMatchResult(tail_matcher, ArrayRef<Value>(arg).SubArray(pos + 1), result_listener);
+}
+
+class MockSystemProperties : public tools::SystemProperties {
+ public:
+ MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class MockExecUtils : public ExecUtils {
+ public:
+ // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
+ // to a conflict between gmock and android-base/logging.h (b/132668253).
+ ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
+ int,
+ const ExecCallbacks& callbacks,
+ ProcessStat* stat,
+ std::string*) const override {
+ Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat);
+ if (code.ok()) {
+ return {.status = ExecResult::kExited, .exit_code = code.value()};
+ }
+ return {.status = ExecResult::kUnknown};
+ }
+
+ MOCK_METHOD(Result<int>,
+ DoExecAndReturnCode,
+ (const std::vector<std::string>& arg_vector,
+ const ExecCallbacks& callbacks,
+ ProcessStat* stat),
+ (const));
+};
+
class ArtdTest : public CommonArtTest {
protected:
void SetUp() override {
CommonArtTest::SetUp();
- artd_ = ndk::SharedRefBase::make<Artd>();
+ auto mock_props = std::make_unique<MockSystemProperties>();
+ mock_props_ = mock_props.get();
+ EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return(""));
+ auto mock_exec_utils = std::make_unique<MockExecUtils>();
+ mock_exec_utils_ = mock_exec_utils.get();
+ artd_ = ndk::SharedRefBase::make<Artd>(std::move(mock_props),
+ std::move(mock_exec_utils),
+ mock_kill_.AsStdFunction(),
+ mock_fstat_.AsStdFunction());
+ scratch_dir_ = std::make_unique<ScratchDir>();
+ scratch_path_ = scratch_dir_->GetPath();
+ // Remove the trailing '/';
+ scratch_path_.resize(scratch_path_.length() - 1);
+
+ ON_CALL(mock_fstat_, Call).WillByDefault(fstat);
+
+ // Use an arbitrary existing directory as ART root.
+ art_root_ = scratch_path_ + "/com.android.art";
+ std::filesystem::create_directories(art_root_);
+ setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1);
+
+ // Use an arbitrary existing directory as Android data.
+ android_data_ = scratch_path_ + "/data";
+ std::filesystem::create_directories(android_data_);
+ setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1);
+
+ dex_file_ = scratch_path_ + "/a/b.apk";
+ isa_ = "arm64";
+ artifacts_path_ = ArtifactsPath{
+ .dexPath = dex_file_,
+ .isa = isa_,
+ .isInDalvikCache = false,
+ };
+ struct stat st;
+ ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0);
+ output_artifacts_ = OutputArtifacts{
+ .artifactsPath = artifacts_path_,
+ .permissionSettings =
+ OutputArtifacts::PermissionSettings{
+ .dirFsPermission =
+ FsPermission{
+ .uid = static_cast<int32_t>(st.st_uid),
+ .gid = static_cast<int32_t>(st.st_gid),
+ .isOtherReadable = true,
+ .isOtherExecutable = true,
+ },
+ .fileFsPermission =
+ FsPermission{
+ .uid = static_cast<int32_t>(st.st_uid),
+ .gid = static_cast<int32_t>(st.st_gid),
+ .isOtherReadable = true,
+ },
+ },
+ };
+ clc_1_ = GetTestDexFileName("Main");
+ clc_2_ = GetTestDexFileName("Nested");
+ class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
+ compiler_filter_ = "speed";
+ TmpProfilePath tmp_profile_path{
+ .finalPath =
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "12345"};
+ profile_path_ = tmp_profile_path;
+ vdex_path_ = artifacts_path_;
+ dm_path_ = DexMetadataPath{.dexPath = dex_file_};
+ std::filesystem::create_directories(
+ std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
}
- void TearDown() override { CommonArtTest::TearDown(); }
+ void TearDown() override {
+ scratch_dir_.reset();
+ CommonArtTest::TearDown();
+ }
+
+ void RunDexopt(binder_exception_t expected_status = EX_NONE,
+ Matcher<DexoptResult> aidl_return_matcher = Field(&DexoptResult::cancelled, false),
+ std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+ RunDexopt(Property(&ndk::ScopedAStatus::getExceptionCode, expected_status),
+ std::move(aidl_return_matcher),
+ cancellation_signal);
+ }
+
+ void RunDexopt(Matcher<ndk::ScopedAStatus> status_matcher,
+ Matcher<DexoptResult> aidl_return_matcher = Field(&DexoptResult::cancelled, false),
+ std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
+ InitFilesBeforeDexopt();
+ if (cancellation_signal == nullptr) {
+ ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
+ }
+ DexoptResult aidl_return;
+ ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_,
+ dex_file_,
+ isa_,
+ class_loader_context_,
+ compiler_filter_,
+ profile_path_,
+ vdex_path_,
+ dm_path_,
+ priority_class_,
+ dexopt_options_,
+ cancellation_signal,
+ &aidl_return);
+ ASSERT_THAT(status, std::move(status_matcher)) << status.getMessage();
+ if (status.isOk()) {
+ ASSERT_THAT(aidl_return, std::move(aidl_return_matcher));
+ }
+ }
+
+ void CreateFile(const std::string& filename, const std::string& content = "") {
+ std::filesystem::path path(filename);
+ std::filesystem::create_directories(path.parent_path());
+ ASSERT_TRUE(WriteStringToFile(content, filename));
+ }
std::shared_ptr<Artd> artd_;
+ std::unique_ptr<ScratchDir> scratch_dir_;
+ std::string scratch_path_;
+ std::string art_root_;
+ std::string android_data_;
+ MockFunction<android::base::LogFunction> mock_logger_;
+ ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+ ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
+ MockSystemProperties* mock_props_;
+ MockExecUtils* mock_exec_utils_;
+ MockFunction<int(pid_t, int)> mock_kill_;
+ MockFunction<int(int, struct stat*)> mock_fstat_;
+
+ std::string dex_file_;
+ std::string isa_;
+ ArtifactsPath artifacts_path_;
+ OutputArtifacts output_artifacts_;
+ std::string clc_1_;
+ std::string clc_2_;
+ std::optional<std::string> class_loader_context_;
+ std::string compiler_filter_;
+ std::optional<VdexPath> vdex_path_;
+ std::optional<DexMetadataPath> dm_path_;
+ PriorityClass priority_class_ = PriorityClass::BACKGROUND;
+ DexoptOptions dexopt_options_;
+ std::optional<ProfilePath> profile_path_;
+ bool dex_file_other_readable_ = true;
+ bool profile_other_readable_ = true;
+
+ private:
+ void InitFilesBeforeDexopt() {
+ // Required files.
+ CreateFile(dex_file_);
+ std::filesystem::permissions(dex_file_,
+ std::filesystem::perms::others_read,
+ dex_file_other_readable_ ? std::filesystem::perm_options::add :
+ std::filesystem::perm_options::remove);
+
+ // Optional files.
+ if (vdex_path_.has_value()) {
+ CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex");
+ }
+ if (dm_path_.has_value()) {
+ CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+ }
+ if (profile_path_.has_value()) {
+ std::string path = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(path);
+ std::filesystem::permissions(path,
+ std::filesystem::perms::others_read,
+ profile_other_readable_ ? std::filesystem::perm_options::add :
+ std::filesystem::perm_options::remove);
+ }
+
+ // Files to be replaced.
+ std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_));
+ CreateFile(oat_path, "old_oat");
+ CreateFile(OatPathToVdexPath(oat_path), "old_vdex");
+ CreateFile(OatPathToArtPath(oat_path), "old_art");
+ }
};
TEST_F(ArtdTest, isAlive) {
@@ -44,6 +427,1228 @@
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) {
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(
+ 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"))))),
+ _,
+ _))
+ .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
+ WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
+ SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
+ Return(0)));
+ RunDexopt(EX_NONE,
+ AllOf(Field(&DexoptResult::cancelled, false),
+ Field(&DexoptResult::wallTimeMs, 100),
+ Field(&DexoptResult::cpuTimeMs, 400),
+ Field(&DexoptResult::sizeBytes, strlen("oat") + strlen("vdex")),
+ Field(&DexoptResult::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");
+ CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", true);
+ CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", 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=", "Dex2OatBootComplete")),
+ Contains(Flag("--set-priority=", "background"))),
+ Not(Contains(Flag("--compact-dex-level=", _)))),
+ _,
+ _))
+ .WillOnce(Return(0));
+ RunDexopt();
+}
+
+TEST_F(ArtdTest, dexoptDexoptOptions) {
+ dexopt_options_ = DexoptOptions{
+ .compilationReason = "install",
+ .targetSdkVersion = 123,
+ .debuggable = false,
+ .generateAppImage = false,
+ .hiddenApiPolicyEnabled = false,
+ };
+
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(WhenSplitBy("--",
+ _,
+ AllOf(Contains(Flag("--compilation-reason=", "install")),
+ Contains(Flag("-Xtarget-sdk-version:", "123")),
+ Not(Contains("--debuggable")),
+ Not(Contains(Flag("--app-image-fd=", _))),
+ Not(Contains(Flag("-Xhidden-api-policy:", _))))),
+ _,
+ _))
+ .WillOnce(Return(0));
+ RunDexopt();
+}
+
+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("-Xhidden-api-policy:", "enabled")))),
+ _,
+ _))
+ .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), Return(0)));
+ RunDexopt();
+
+ CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art");
+}
+
+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(&DexoptResult::sizeBytes, 0), Field(&DexoptResult::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(&DexoptResult::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(&DexoptResult::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(&DexoptResult::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(
+ 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_))))),
+ _,
+ _))
+ .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(
+ 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_))))),
+ _,
+ _))
+ .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
+ Return(ProfmanResult::kCopyAndUpdateSuccess)));
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
+ std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath));
+ EXPECT_EQ(dst.profilePath.tmpPath, real_path);
+ CheckContent(real_path, "def");
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
+ const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+ dst.profilePath.tmpPath = "";
+
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+ .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch));
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+ EXPECT_THAT(dst.profilePath.id, IsEmpty());
+ EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
+ CreateFile(dex_file_);
+
+ const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+ dst.profilePath.tmpPath = "";
+
+ bool result;
+ EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
+ EXPECT_FALSE(result);
+ EXPECT_THAT(dst.profilePath.id, IsEmpty());
+ EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
+ const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+ std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
+ CreateFile(src_file, "abc");
+ OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ dst.profilePath.id = "";
+ dst.profilePath.tmpPath = "";
+
+ CreateFile(dex_file_);
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
+
+ bool result;
+ ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
+ EXPECT_THAT(dst.profilePath.id, IsEmpty());
+ EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
+}
+
+TEST_F(ArtdTest, commitTmpProfile) {
+ const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+ std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path));
+ CreateFile(tmp_profile_file);
+
+ EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
+
+ EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
+ EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, commitTmpProfileFailed) {
+ const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+ ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
+
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(
+ status.getMessage(),
+ ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
+
+ EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
+}
+
+TEST_F(ArtdTest, deleteProfile) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ CreateFile(profile_file);
+
+ EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+
+ EXPECT_FALSE(std::filesystem::exists(profile_file));
+}
+
+TEST_F(ArtdTest, deleteProfileDoesNotExist) {
+ std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
+ EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+TEST_F(ArtdTest, deleteProfileFailed) {
+ auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
+ EXPECT_CALL(
+ mock_logger_,
+ Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re")));
+
+ EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
+}
+
+class ArtdGetVisibilityTest : public ArtdTest {
+ protected:
+ template <typename PathType>
+ using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*);
+
+ template <typename PathType>
+ void TestGetVisibilityOtherReadable(Method<PathType> method,
+ const PathType& input,
+ const std::string& path) {
+ CreateFile(path);
+ std::filesystem::permissions(
+ path, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+ FileVisibility result;
+ ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+ }
+
+ template <typename PathType>
+ void TestGetVisibilityNotOtherReadable(Method<PathType> method,
+ const PathType& input,
+ const std::string& path) {
+ CreateFile(path);
+ std::filesystem::permissions(
+ path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+ FileVisibility result;
+ ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+ }
+
+ template <typename PathType>
+ void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) {
+ FileVisibility result;
+ ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+ }
+
+ template <typename PathType>
+ void TestGetVisibilityPermissionDenied(Method<PathType> method,
+ const PathType& input,
+ const std::string& path) {
+ CreateFile(path);
+
+ auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path());
+ auto scoped_unroot = ScopedUnroot();
+
+ FileVisibility result;
+ ndk::ScopedAStatus status = ((*artd_).*method)(input, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of"));
+ }
+};
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) {
+ TestGetVisibilityOtherReadable(&Artd::getProfileVisibility,
+ profile_path_.value(),
+ OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) {
+ TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility,
+ profile_path_.value(),
+ OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) {
+ TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) {
+ TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility,
+ profile_path_.value(),
+ OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) {
+ TestGetVisibilityOtherReadable(
+ &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) {
+ TestGetVisibilityNotOtherReadable(
+ &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) {
+ TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) {
+ TestGetVisibilityPermissionDenied(
+ &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) {
+ TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) {
+ TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) {
+ TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) {
+ TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) {
+ TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility,
+ dm_path_.value(),
+ OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) {
+ TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility,
+ dm_path_.value(),
+ OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) {
+ TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) {
+ TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility,
+ dm_path_.value(),
+ OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdTest, mergeProfiles) {
+ const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+ std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
+ CreateFile(reference_profile_file, "abc");
+
+ // Doesn't exist.
+ PrimaryCurProfilePath profile_0_path{
+ .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
+ std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
+
+ PrimaryCurProfilePath profile_1_path{
+ .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
+ std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
+ CreateFile(profile_1_file, "def");
+
+ OutputProfile output_profile{.profilePath = reference_profile_path,
+ .fsPermission = FsPermission{.uid = -1, .gid = -1}};
+ output_profile.profilePath.id = "";
+ output_profile.profilePath.tmpPath = "";
+
+ std::string dex_file_1 = scratch_path_ + "/a/b.apk";
+ std::string dex_file_2 = scratch_path_ + "/a/c.apk";
+ CreateFile(dex_file_1);
+ CreateFile(dex_file_2);
+
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(
+ 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")))),
+ _,
+ _))
+ .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, mergeProfilesWithOptions) {
+ 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()));
+}
+
} // 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/ArtifactsPath.aidl b/artd/binder/com/android/server/art/ArtifactsPath.aidl
new file mode 100644
index 0000000..f69b439
--- /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 optimized 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 optimized artifacts. */
+ @utf8InCpp String isa;
+ /** Whether the optimized artifacts are in the dalvik-cache folder. */
+ boolean isInDalvikCache;
+}
diff --git a/artd/binder/com/android/server/art/DexMetadataPath.aidl b/artd/binder/com/android/server/art/DexMetadataPath.aidl
new file mode 100644
index 0000000..5f9ab81
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexMetadataPath.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to a dex metadata file.
+ *
+ * @hide
+ */
+parcelable DexMetadataPath {
+ /**
+ * The absolute path starting with '/' to the dex file that the dex metadata file is next to.
+ */
+ @utf8InCpp String dexPath;
+}
diff --git a/artd/binder/com/android/server/art/DexoptOptions.aidl b/artd/binder/com/android/server/art/DexoptOptions.aidl
new file mode 100644
index 0000000..351c079
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptOptions.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Miscellaneous options for performing dexopt. Every field corresponds to a dex2oat command line
+ * flag.
+ *
+ * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes
+ * static flags, and flags that only depend on system properties or other passed parameters, such as
+ * the priority class.
+ *
+ * All fields are required.
+ *
+ * @hide
+ */
+parcelable DexoptOptions {
+ /** --compilation-reason */
+ @utf8InCpp String compilationReason;
+ /** -Xtarget-sdk-version */
+ int targetSdkVersion;
+ /** --debuggable */
+ boolean debuggable;
+ /** --app-image-fd */
+ boolean generateAppImage;
+ /** -Xhidden-api-policy:enabled */
+ boolean hiddenApiPolicyEnabled;
+}
diff --git a/artd/binder/com/android/server/art/DexoptResult.aidl b/artd/binder/com/android/server/art/DexoptResult.aidl
new file mode 100644
index 0000000..52df54d
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptResult.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 DexoptResult {
+ /** 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 optimized artifacts, or 0 if dex2oat fails, is cancelled, or
+ * is not run.
+ */
+ long sizeBytes;
+ /**
+ * The total size, in bytes, of the previous optimized artifacts that have been replaced, or
+ * 0 if there were no previous optimized artifacts or dex2oat fails, is cancelled, or is not
+ * run.
+ */
+ long sizeBeforeBytes;
+}
diff --git a/artd/binder/com/android/server/art/DexoptTrigger.aidl b/artd/binder/com/android/server/art/DexoptTrigger.aidl
new file mode 100644
index 0000000..58a9ec8
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the conditions where dexopt should be performed.
+ * See `OatFileAssistant::DexOptTrigger`.
+ *
+ * This is actually used as a bit field, but is declared as an enum because AIDL doesn't support bit
+ * fields.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum DexoptTrigger {
+ COMPILER_FILTER_IS_BETTER = 1 << 0,
+ COMPILER_FILTER_IS_SAME = 1 << 1,
+ COMPILER_FILTER_IS_WORSE = 1 << 2,
+ PRIMARY_BOOT_IMAGE_BECOMES_USABLE = 1 << 3,
+}
diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl
new file mode 100644
index 0000000..ceaa818
--- /dev/null
+++ b/artd/binder/com/android/server/art/FileVisibility.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Indicates the visibility of a file. I.e., whether the file has the "read" bit for "others"
+ * (S_IROTH).
+ *
+ * Theoretically, even if the value is {@code OTHER_READABLE}, others' access can still be denied
+ * due to the lack of the "exec" bit on parent directories. However, for compilation artifacts, all
+ * parent directories do have the "exec" bit for "others" in practice.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum FileVisibility {
+ NOT_FOUND = 0,
+ OTHER_READABLE = 1,
+ NOT_OTHER_READABLE = 2,
+}
diff --git a/artd/binder/com/android/server/art/FsPermission.aidl b/artd/binder/com/android/server/art/FsPermission.aidl
new file mode 100644
index 0000000..9c2ddb9
--- /dev/null
+++ b/artd/binder/com/android/server/art/FsPermission.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the Linux filesystem permission of a file or a directory.
+ *
+ * If both `uid` and `gid` are negative, no `chown` will be performed.
+ *
+ * If none of the booleans are set, the default permission bits are `rw-r-----` for a file, and
+ * `rwxr-x---` for a directory.
+ *
+ * @hide
+ */
+parcelable FsPermission {
+ int uid;
+ int gid;
+ /**
+ * Whether the file/directory should have the "read" bit for "others" (S_IROTH).
+ */
+ boolean isOtherReadable;
+ /**
+ * Whether the file/directory should have the "execute" bit for "others" (S_IXOTH).
+ */
+ boolean isOtherExecutable;
+}
diff --git a/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
new file mode 100644
index 0000000..99c4951
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getDexoptNeeded}.
+ *
+ * @hide
+ */
+parcelable GetDexoptNeededResult {
+ /** Whether dexopt is needed. */
+ boolean isDexoptNeeded;
+ /** Whether there is a usable VDEX file. Note that this can be true even if dexopt is needed. */
+ boolean isVdexUsable;
+ /** The location of the best usable artifacts. */
+ ArtifactsLocation artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+
+ enum ArtifactsLocation {
+ /** No usable artifacts. */
+ NONE_OR_ERROR = 0,
+ /** In the global "dalvik-cache" folder. */
+ DALVIK_CACHE = 1,
+ /** In the "oat" folder next to the dex file. */
+ NEXT_TO_DEX = 2,
+ /** In the dex metadata file. This means the only usable artifact is the VDEX file. */
+ DM = 3,
+ }
+}
diff --git a/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl b/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl
new file mode 100644
index 0000000..99a2e37
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetOptimizationStatusResult.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.getOptimizationStatus}. Each field corresponds to a field in
+ * {@code com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus}.
+ *
+ * @hide
+ */
+parcelable GetOptimizationStatusResult {
+ @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..237f8a9 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -16,8 +16,138 @@
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 optimization status of a dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ com.android.server.art.GetOptimizationStatusResult getOptimizationStatus(
+ @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+ @utf8InCpp String classLoaderContext);
+
+ /**
+ * Returns true if the profile exists and contains entries for the given dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ boolean isProfileUsable(in com.android.server.art.ProfilePath profile,
+ @utf8InCpp String dexFile);
+
+ /**
+ * Copies the profile and rewrites it for the given dex file. Returns true and fills
+ * `dst.profilePath.id` if the operation succeeds and `src` exists and contains entries that
+ * match the given dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ boolean copyAndRewriteProfile(in com.android.server.art.ProfilePath src,
+ inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile);
+
+ /**
+ * Moves the temporary profile to the permanent location.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ void commitTmpProfile(in com.android.server.art.ProfilePath.TmpProfilePath profile);
+
+ /**
+ * Deletes the profile. Does nothing of the profile doesn't exist.
+ *
+ * Operates on the whole DM file if given one.
+ *
+ * Throws fatal errors. Logs and ignores non-fatal errors.
+ */
+ void deleteProfile(in com.android.server.art.ProfilePath profile);
+
+ /**
+ * Returns the visibility of the profile.
+ *
+ * Operates on the whole DM file if given one.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ com.android.server.art.FileVisibility getProfileVisibility(
+ in com.android.server.art.ProfilePath profile);
+
+ /**
+ * Merges profiles. Both `profiles` and `referenceProfile` are inputs, while the difference is
+ * that `referenceProfile` is also used as the reference to calculate the diff. `profiles` that
+ * don't exist are skipped, while `referenceProfile`, if provided, must exist. Returns true,
+ * writes the merge result to `outputProfile` and fills `outputProfile.profilePath.id` and
+ * `outputProfile.profilePath.tmpPath` if a merge has been performed.
+ *
+ * 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.DexoptResult dexopt(
+ in com.android.server.art.OutputArtifacts outputArtifacts,
+ @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+ @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+ in @nullable com.android.server.art.ProfilePath profile,
+ in @nullable com.android.server.art.VdexPath inputVdex,
+ in @nullable com.android.server.art.DexMetadataPath dmFile,
+ com.android.server.art.PriorityClass priorityClass,
+ in com.android.server.art.DexoptOptions dexoptOptions,
+ in com.android.server.art.IArtdCancellationSignal cancellationSignal);
+
+ /**
+ * Returns a cancellation signal which can be used to cancel {@code dexopt} calls.
+ */
+ com.android.server.art.IArtdCancellationSignal createCancellationSignal();
}
diff --git a/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
new file mode 100644
index 0000000..fb15e64
--- /dev/null
+++ b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Similar to `android.os.CancellationSignal` but for artd. Must be created by
+ * `IArtd.createCancellationSignal`.
+ *
+ * @hide
+ */
+interface IArtdCancellationSignal {
+ oneway void cancel();
+
+ /** For artd internal type-checking. DO NOT USE. */
+ long getType();
+}
diff --git a/artd/binder/com/android/server/art/MergeProfileOptions.aidl b/artd/binder/com/android/server/art/MergeProfileOptions.aidl
new file mode 100644
index 0000000..fb7db80
--- /dev/null
+++ b/artd/binder/com/android/server/art/MergeProfileOptions.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;
+
+/**
+ * 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;
+}
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..af3e3ce
--- /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 optimized 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..45344e1
--- /dev/null
+++ b/artd/file_utils.cc
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "base/os.h"
+#include "base/unix_file/fd_file.h"
+#include "fmt/format.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::make_scope_guard;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format; // NOLINT
+
+void UnlinkIfExists(const std::string& path) {
+ std::error_code ec;
+ if (!std::filesystem::remove(path, ec)) {
+ if (ec.value() != ENOENT) {
+ LOG(WARNING) << "Failed to remove file '{}': {}"_format(path, ec.message());
+ }
+ }
+}
+
+} // namespace
+
+Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path,
+ const FsPermission& fs_permission) {
+ std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission));
+ OR_RETURN(output_file->Init());
+ return output_file;
+}
+
+NewFile::~NewFile() { Cleanup(); }
+
+Result<void> NewFile::Keep() {
+ if (close(std::exchange(fd_, -1)) != 0) {
+ return ErrnoErrorf("Failed to close file '{}'", temp_path_);
+ }
+ return {};
+}
+
+Result<void> NewFile::CommitOrAbandon() {
+ auto cleanup = make_scope_guard([this] { Unlink(); });
+ OR_RETURN(Keep());
+ std::error_code ec;
+ std::filesystem::rename(temp_path_, final_path_, ec);
+ if (ec) {
+ return Errorf(
+ "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message());
+ }
+ cleanup.Disable();
+ committed_ = true;
+ return {};
+}
+
+void NewFile::Cleanup() {
+ if (fd_ >= 0) {
+ Unlink();
+ if (close(std::exchange(fd_, -1)) != 0) {
+ // Nothing we can do. If the file is already unlinked, it will go away when the process exits.
+ PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'";
+ }
+ }
+}
+
+Result<void> NewFile::Init() {
+ mode_t mode = FileFsPermissionToMode(fs_permission_);
+ // "<path_>.XXXXXX.tmp".
+ temp_path_ = BuildTempPath(final_path_, "XXXXXX");
+ fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4);
+ if (fd_ < 0) {
+ return ErrnoErrorf("Failed to create temp file for '{}'", final_path_);
+ }
+ temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6);
+ if (fchmod(fd_, mode) != 0) {
+ return ErrnoErrorf("Failed to chmod file '{}'", temp_path_);
+ }
+ OR_RETURN(Chown(temp_path_, fs_permission_));
+ return {};
+}
+
+void NewFile::Unlink() {
+ // This should never fail. We were able to create the file, so we should be able to remove it.
+ UnlinkIfExists(temp_path_);
+}
+
+Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit,
+ const std::vector<std::string_view>& files_to_remove) {
+ std::vector<std::pair<std::string_view, std::string>> moved_files;
+
+ auto cleanup = make_scope_guard([&]() {
+ // Clean up new files.
+ for (NewFile* new_file : files_to_commit) {
+ if (new_file->committed_) {
+ UnlinkIfExists(new_file->FinalPath());
+ } else {
+ new_file->Cleanup();
+ }
+ }
+
+ // Move old files back.
+ for (const auto& [original_path, temp_path] : moved_files) {
+ std::error_code ec;
+ std::filesystem::rename(temp_path, original_path, ec);
+ if (ec) {
+ // This should never happen. We were able to move the file from `original_path` to
+ // `temp_path`. We should be able to move it back.
+ LOG(WARNING) << "Failed to move old file '{}' back from temporary path '{}': {}"_format(
+ original_path, temp_path, ec.message());
+ }
+ }
+ });
+
+ // Move old files to temporary locations.
+ std::vector<std::string_view> all_files_to_remove;
+ for (NewFile* file : files_to_commit) {
+ all_files_to_remove.push_back(file->FinalPath());
+ }
+ all_files_to_remove.insert(
+ all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end());
+
+ for (std::string_view original_path : all_files_to_remove) {
+ std::error_code ec;
+ std::filesystem::file_status status = std::filesystem::status(original_path, ec);
+ if (!std::filesystem::status_known(status)) {
+ return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message());
+ }
+ if (std::filesystem::is_directory(status)) {
+ return ErrnoErrorf("Old file '{}' is a directory", original_path);
+ }
+ if (std::filesystem::exists(status)) {
+ std::string temp_path = BuildTempPath(original_path, "XXXXXX");
+ int fd = mkstemps(temp_path.data(), /*suffixlen=*/4);
+ if (fd < 0) {
+ return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path);
+ }
+ close(fd);
+
+ std::filesystem::rename(original_path, temp_path, ec);
+ if (ec) {
+ UnlinkIfExists(temp_path);
+ return Errorf("Failed to move old file '{}' to temporary path '{}': {}",
+ original_path,
+ temp_path,
+ ec.message());
+ }
+
+ moved_files.push_back({original_path, std::move(temp_path)});
+ }
+ }
+
+ // Commit new files.
+ for (NewFile* file : files_to_commit) {
+ OR_RETURN(file->CommitOrAbandon());
+ }
+
+ cleanup.Disable();
+
+ // Clean up old files.
+ for (const auto& [original_path, temp_path] : moved_files) {
+ // This should never fail. We were able to move the file to `temp_path`. We should be able to
+ // remove it.
+ UnlinkIfExists(temp_path);
+ }
+
+ return {};
+}
+
+std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) {
+ return "{}.{}.tmp"_format(final_path, id);
+}
+
+Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) {
+ std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str()));
+ if (file == nullptr) {
+ return ErrnoErrorf("Failed to open file '{}'", path);
+ }
+ return file;
+}
+
+mode_t FileFsPermissionToMode(const FsPermission& fs_permission) {
+ return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) |
+ (fs_permission.isOtherExecutable ? S_IXOTH : 0);
+}
+
+mode_t DirFsPermissionToMode(const FsPermission& fs_permission) {
+ return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP;
+}
+
+Result<void> Chown(const std::string& path, const FsPermission& fs_permission) {
+ if (fs_permission.uid < 0 && fs_permission.gid < 0) {
+ // Keep the default owner.
+ } else if (fs_permission.uid < 0 || fs_permission.gid < 0) {
+ return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.",
+ fs_permission.uid,
+ fs_permission.gid);
+ }
+ if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) {
+ return ErrnoErrorf("Failed to chown '{}'", path);
+ }
+ return {};
+}
+
+} // namespace artd
+} // namespace art
diff --git a/artd/file_utils.h b/artd/file_utils.h
new file mode 100644
index 0000000..b5fd170
--- /dev/null
+++ b/artd/file_utils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ARTD_FILE_UTILS_H_
+#define ART_ARTD_FILE_UTILS_H_
+
+#include <sys/types.h>
+
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/result.h"
+#include "base/os.h"
+
+namespace art {
+namespace artd {
+
+// A class that creates a new file that will eventually be committed to the given path. The new file
+// is created at a temporary location. It will not overwrite the file at the given path until
+// `CommitOrAbandon` has been called and will be automatically cleaned up on object destruction
+// unless `CommitOrAbandon` has been called.
+// The new file is opened without O_CLOEXEC so that it can be passed to subprocesses.
+class NewFile {
+ public:
+ // Creates a new file at the given path with the given permission.
+ static android::base::Result<std::unique_ptr<NewFile>> Create(
+ const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+ NewFile(const NewFile&) = delete;
+ NewFile& operator=(const NewFile&) = delete;
+ NewFile(NewFile&& other) noexcept
+ : fd_(std::exchange(other.fd_, -1)),
+ final_path_(std::move(other.final_path_)),
+ temp_path_(std::move(other.temp_path_)),
+ temp_id_(std::move(other.temp_id_)),
+ fs_permission_(other.fs_permission_) {}
+
+ // Deletes the file if it is not committed.
+ virtual ~NewFile();
+
+ int Fd() const { return fd_; }
+
+ // The path that the file will eventually be committed to.
+ const std::string& FinalPath() const { return final_path_; }
+
+ // The path to the new file.
+ const std::string& TempPath() const { return temp_path_; }
+
+ // The unique ID of the new file. Can be used by `BuildTempPath` for reconstructing the path to
+ // the file.
+ const std::string& TempId() const { return temp_id_; }
+
+ // Closes the new file, keeps it, moves the file to the final path, and overwrites any existing
+ // file at that path, or abandons the file on failure. The fd will be invalid after this function
+ // is called.
+ android::base::Result<void> CommitOrAbandon();
+
+ // Closes the new file and keeps it at the temporary location. The file will not be automatically
+ // cleaned up on object destruction. The file can be found at `TempPath()` (i.e.,
+ // `BuildTempPath(FinalPath(), TempId())`). The fd will be invalid after this function is called.
+ virtual android::base::Result<void> Keep();
+
+ // Unlinks and closes the new file if it is not committed. The fd will be invalid after this
+ // function is called.
+ void Cleanup();
+
+ // Commits all new files, replacing old files, and removes given files in addition. Or abandons
+ // new files and restores old files at best effort if any error occurs. The fds will be invalid
+ // after this function is called.
+ //
+ // Note: This function is NOT thread-safe. It is intended to be used in single-threaded code or in
+ // cases where some race condition is acceptable.
+ //
+ // Usage:
+ //
+ // Commit `file_1` and `file_2`, and remove the file at "path_3":
+ // CommitAllOrAbandon({file_1, file_2}, {"path_3"});
+ static android::base::Result<void> CommitAllOrAbandon(
+ const std::vector<NewFile*>& files_to_commit,
+ const std::vector<std::string_view>& files_to_remove = {});
+
+ // Returns the path to a temporary file. See `Keep`.
+ static std::string BuildTempPath(std::string_view final_path, const std::string& id);
+
+ private:
+ NewFile(const std::string& path,
+ const aidl::com::android::server::art::FsPermission& fs_permission)
+ : final_path_(path), fs_permission_(fs_permission) {}
+
+ android::base::Result<void> Init();
+
+ // Unlinks the new file. The fd will still be valid after this function is called.
+ void Unlink();
+
+ int fd_ = -1;
+ std::string final_path_;
+ std::string temp_path_;
+ std::string temp_id_;
+ aidl::com::android::server::art::FsPermission fs_permission_;
+ bool committed_ = false;
+};
+
+// Opens a file for reading.
+android::base::Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path);
+
+// Converts FsPermission to Linux access mode for a file.
+mode_t FileFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Converts FsPermission to Linux access mode for a directory.
+mode_t DirFsPermissionToMode(const aidl::com::android::server::art::FsPermission& fs_permission);
+
+// Changes the owner based on FsPermission.
+android::base::Result<void> Chown(
+ const std::string& path, const aidl::com::android::server::art::FsPermission& fs_permission);
+
+} // namespace artd
+} // namespace art
+
+#endif // ART_ARTD_FILE_UTILS_H_
diff --git a/artd/file_utils_test.cc b/artd/file_utils_test.cc
new file mode 100644
index 0000000..8f79d5d
--- /dev/null
+++ b/artd/file_utils_test.cc
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "file_utils.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+
+#include "aidl/com/android/server/art/FsPermission.h"
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/result-gmock.h"
+#include "android-base/result.h"
+#include "base/common_art_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::FsPermission;
+using ::android::base::Error;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::Ok;
+using ::android::base::testing::WithMessage;
+using ::testing::ContainsRegex;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+
+void CheckContent(const std::string& path, const std::string& expected_content) {
+ std::string actual_content;
+ ASSERT_TRUE(ReadFileToString(path, &actual_content));
+ EXPECT_EQ(actual_content, expected_content);
+}
+
+// A file that will always fail on `Commit`.
+class UncommittableFile : public NewFile {
+ public:
+ static Result<std::unique_ptr<UncommittableFile>> Create(const std::string& path,
+ const FsPermission& fs_permission) {
+ std::unique_ptr<NewFile> new_file = OR_RETURN(NewFile::Create(path, fs_permission));
+ return std::unique_ptr<UncommittableFile>(new UncommittableFile(std::move(*new_file)));
+ }
+
+ Result<void> Keep() override { return Error() << "Uncommittable file"; }
+
+ private:
+ explicit UncommittableFile(NewFile&& other) : NewFile(std::move(other)) {}
+};
+
+class FileUtilsTest : public CommonArtTest {
+ protected:
+ void SetUp() override {
+ CommonArtTest::SetUp();
+ scratch_dir_ = std::make_unique<ScratchDir>();
+ struct stat st;
+ ASSERT_EQ(stat(scratch_dir_->GetPath().c_str(), &st), 0);
+ fs_permission_ = FsPermission{.uid = static_cast<int32_t>(st.st_uid),
+ .gid = static_cast<int32_t>(st.st_gid)};
+ }
+
+ void TearDown() override {
+ scratch_dir_.reset();
+ CommonArtTest::TearDown();
+ }
+
+ FsPermission fs_permission_;
+ std::unique_ptr<ScratchDir> scratch_dir_;
+};
+
+TEST_F(FileUtilsTest, NewFileCreate) {
+ std::string path = scratch_dir_->GetPath() + "/file.tmp";
+
+ Result<std::unique_ptr<NewFile>> new_file = NewFile::Create(path, fs_permission_);
+ ASSERT_THAT(new_file, HasValue(NotNull()));
+ EXPECT_GE((*new_file)->Fd(), 0);
+ EXPECT_EQ((*new_file)->FinalPath(), path);
+ EXPECT_THAT((*new_file)->TempPath(), Not(IsEmpty()));
+ EXPECT_THAT((*new_file)->TempId(), Not(IsEmpty()));
+
+ EXPECT_FALSE(std::filesystem::exists((*new_file)->FinalPath()));
+ EXPECT_TRUE(std::filesystem::exists((*new_file)->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCreateNonExistentDir) {
+ std::string path = scratch_dir_->GetPath() + "/non_existent_dir/file.tmp";
+
+ EXPECT_THAT(NewFile::Create(path, fs_permission_),
+ HasError(WithMessage(
+ ContainsRegex("Failed to create temp file for .*/non_existent_dir/file.tmp"))));
+}
+
+TEST_F(FileUtilsTest, NewFileExplicitCleanup) {
+ std::string path = scratch_dir_->GetPath() + "/file.tmp";
+ std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+ new_file->Cleanup();
+
+ EXPECT_FALSE(std::filesystem::exists(path));
+ EXPECT_FALSE(std::filesystem::exists(new_file->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileImplicitCleanup) {
+ std::string path = scratch_dir_->GetPath() + "/file.tmp";
+ std::string temp_path;
+
+ // Cleanup on object destruction.
+ {
+ std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+ temp_path = new_file->TempPath();
+ }
+
+ EXPECT_FALSE(std::filesystem::exists(path));
+ EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommit) {
+ std::string path = scratch_dir_->GetPath() + "/file.tmp";
+ std::string temp_path;
+
+ {
+ std::unique_ptr<NewFile> new_file = OR_FATAL(NewFile::Create(path, fs_permission_));
+ temp_path = new_file->TempPath();
+ new_file->CommitOrAbandon();
+ }
+
+ EXPECT_TRUE(std::filesystem::exists(path));
+ EXPECT_FALSE(std::filesystem::exists(temp_path));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllNoOldFile) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+ // New files are committed.
+ CheckContent(file_1_path, "new_file_1");
+ CheckContent(file_2_path, "new_file_2");
+
+ // New files are no longer at the temporary paths.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesOldFiles) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+ ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+ // New files are committed.
+ CheckContent(file_1_path, "new_file_1");
+ CheckContent(file_2_path, "new_file_2");
+
+ // New files are no longer at the temporary paths.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesLessOldFiles) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+
+ ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path)); // No old_file_2.
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}), Ok());
+
+ // New files are committed.
+ CheckContent(file_1_path, "new_file_1");
+ CheckContent(file_2_path, "new_file_2");
+
+ // New files are no longer at the temporary paths.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllReplacesMoreOldFiles) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+ std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+ ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file.
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+ Ok());
+
+ // New files are committed.
+ CheckContent(file_1_path, "new_file_1");
+ CheckContent(file_2_path, "new_file_2");
+ EXPECT_FALSE(std::filesystem::exists(file_3_path)); // Extra file removed.
+
+ // New files are no longer at the temporary paths.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToCommit) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+ std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+ ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_2", file_2_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file.
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ // Uncommittable file.
+ std::unique_ptr<NewFile> new_file_2 =
+ OR_FATAL(UncommittableFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+ HasError(WithMessage("Uncommittable file")));
+
+ // Old files are fine.
+ CheckContent(file_1_path, "old_file_1");
+ CheckContent(file_2_path, "old_file_2");
+ CheckContent(file_3_path, "old_file_3");
+
+ // New files are abandoned.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, NewFileCommitAllFailedToMoveOldFile) {
+ std::string file_1_path = scratch_dir_->GetPath() + "/file_1";
+ std::string file_2_path = scratch_dir_->GetPath() + "/file_2";
+ std::filesystem::create_directory(file_2_path);
+ std::string file_3_path = scratch_dir_->GetPath() + "/file_3";
+
+ ASSERT_TRUE(WriteStringToFile("old_file_1", file_1_path));
+ ASSERT_TRUE(WriteStringToFile("old_file_3", file_3_path)); // Extra file.
+
+ std::unique_ptr<NewFile> new_file_1 = OR_FATAL(NewFile::Create(file_1_path, fs_permission_));
+ std::unique_ptr<NewFile> new_file_2 = OR_FATAL(NewFile::Create(file_2_path, fs_permission_));
+
+ ASSERT_TRUE(WriteStringToFd("new_file_1", new_file_1->Fd()));
+ ASSERT_TRUE(WriteStringToFd("new_file_2", new_file_2->Fd()));
+
+ // file_2 is not movable because it is a directory.
+ EXPECT_THAT(NewFile::CommitAllOrAbandon({new_file_1.get(), new_file_2.get()}, {file_3_path}),
+ HasError(WithMessage(ContainsRegex("Old file '.*/file_2' is a directory"))));
+
+ // Old files are fine.
+ CheckContent(file_1_path, "old_file_1");
+ EXPECT_TRUE(std::filesystem::is_directory(file_2_path));
+ CheckContent(file_3_path, "old_file_3");
+
+ // New files are abandoned.
+ EXPECT_FALSE(std::filesystem::exists(new_file_1->TempPath()));
+ EXPECT_FALSE(std::filesystem::exists(new_file_2->TempPath()));
+}
+
+TEST_F(FileUtilsTest, BuildTempPath) {
+ EXPECT_EQ(NewFile::BuildTempPath("/a/b/original_path", "123456"),
+ "/a/b/original_path.123456.tmp");
+}
+
+TEST_F(FileUtilsTest, OpenFileForReading) {
+ std::string path = scratch_dir_->GetPath() + "/foo";
+ ASSERT_TRUE(WriteStringToFile("foo", path));
+
+ EXPECT_THAT(OpenFileForReading(path), HasValue(NotNull()));
+}
+
+TEST_F(FileUtilsTest, OpenFileForReadingFailed) {
+ std::string path = scratch_dir_->GetPath() + "/foo";
+
+ EXPECT_THAT(OpenFileForReading(path),
+ HasError(WithMessage(ContainsRegex("Failed to open file .*/foo"))));
+}
+
+TEST_F(FileUtilsTest, FileFsPermissionToMode) {
+ EXPECT_EQ(FileFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IRGRP);
+ EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ EXPECT_EQ(FileFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IXOTH);
+ EXPECT_EQ(
+ FileFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXOTH);
+}
+
+TEST_F(FileUtilsTest, DirFsPermissionToMode) {
+ EXPECT_EQ(DirFsPermissionToMode(FsPermission{}), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
+ EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true}),
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH);
+ EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherExecutable = true}),
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IXOTH);
+ EXPECT_EQ(DirFsPermissionToMode(FsPermission{.isOtherReadable = true, .isOtherExecutable = true}),
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+}
+
+} // namespace
+} // namespace artd
+} // namespace art
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
new file mode 100644
index 0000000..295b023
--- /dev/null
+++ b/artd/path_utils.cc
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "path_utils.h"
+
+#include <filesystem>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/errors.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
+#include "base/file_utils.h"
+#include "file_utils.h"
+#include "fmt/format.h"
+#include "oat_file_assistant.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::EndsWith;
+using ::android::base::Error;
+using ::android::base::Result;
+
+using ::fmt::literals::operator""_format; // NOLINT
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+using WritableProfilePath = ProfilePath::WritableProfilePath;
+
+Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
+ if (path_str.empty()) {
+ return Errorf("Path is empty");
+ }
+ if (path_str.find('\0') != std::string::npos) {
+ return Errorf("Path '{}' has invalid character '\\0'", path_str);
+ }
+ std::filesystem::path path(path_str);
+ if (!path.is_absolute()) {
+ return Errorf("Path '{}' is not an absolute path", path_str);
+ }
+ if (path.lexically_normal() != path_str) {
+ return Errorf("Path '{}' is not in normal form", path_str);
+ }
+ return {};
+}
+
+Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+ const std::string& name) {
+ if (path_element_substring.empty()) {
+ return Errorf("{} is empty", name);
+ }
+ if (path_element_substring.find('/') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
+ }
+ if (path_element_substring.find('\0') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
+ }
+ return {};
+}
+
+Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
+ OR_RETURN(ValidatePathElementSubstring(path_element, name));
+ if (path_element == "." || path_element == "..") {
+ return Errorf("Invalid {} '{}'", name, path_element);
+ }
+ return {};
+}
+
+Result<std::string> GetAndroidDataOrError() {
+ std::string error_msg;
+ std::string result = GetAndroidDataSafe(&error_msg);
+ if (!error_msg.empty()) {
+ return Error() << error_msg;
+ }
+ return result;
+}
+
+Result<std::string> GetArtRootOrError() {
+ std::string error_msg;
+ std::string result = GetArtRootSafe(&error_msg);
+ if (!error_msg.empty()) {
+ return Error() << error_msg;
+ }
+ return result;
+}
+
+} // namespace
+
+Result<void> ValidateDexPath(const std::string& dex_path) {
+ OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+ if (!EndsWith(dex_path, ".apk") && !EndsWith(dex_path, ".jar")) {
+ return Errorf("Dex path '{}' has an invalid extension", dex_path);
+ }
+ return {};
+}
+
+Result<std::string> BuildArtBinPath(const std::string& binary_name) {
+ return "{}/bin/{}"_format(OR_RETURN(GetArtRootOrError()), binary_name);
+}
+
+Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) {
+ OR_RETURN(ValidateDexPath(artifacts_path.dexPath));
+
+ InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str());
+ if (isa == InstructionSet::kNone) {
+ return Errorf("Instruction set '{}' is invalid", artifacts_path.isa);
+ }
+
+ std::string error_msg;
+ std::string path;
+ if (artifacts_path.isInDalvikCache) {
+ // Apps' OAT files are never in ART APEX data.
+ if (!OatFileAssistant::DexLocationToOatFilename(
+ artifacts_path.dexPath, isa, /*deny_art_apex_data_files=*/true, &path, &error_msg)) {
+ return Error() << error_msg;
+ }
+ return path;
+ } else {
+ if (!OatFileAssistant::DexLocationToOdexFilename(
+ artifacts_path.dexPath, isa, &path, &error_msg)) {
+ return Error() << error_msg;
+ }
+ return path;
+ }
+}
+
+Result<std::string> BuildPrimaryRefProfilePath(
+ const PrimaryRefProfilePath& primary_ref_profile_path) {
+ OR_RETURN(ValidatePathElement(primary_ref_profile_path.packageName, "packageName"));
+ OR_RETURN(ValidatePathElementSubstring(primary_ref_profile_path.profileName, "profileName"));
+ return "{}/misc/profiles/ref/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+ primary_ref_profile_path.packageName,
+ primary_ref_profile_path.profileName);
+}
+
+Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) {
+ OR_RETURN(ValidateDexPath(prebuilt_profile_path.dexPath));
+ return prebuilt_profile_path.dexPath + ".prof";
+}
+
+Result<std::string> BuildPrimaryCurProfilePath(
+ const PrimaryCurProfilePath& primary_cur_profile_path) {
+ OR_RETURN(ValidatePathElement(primary_cur_profile_path.packageName, "packageName"));
+ OR_RETURN(ValidatePathElementSubstring(primary_cur_profile_path.profileName, "profileName"));
+ return "{}/misc/profiles/cur/{}/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
+ primary_cur_profile_path.userId,
+ primary_cur_profile_path.packageName,
+ primary_cur_profile_path.profileName);
+}
+
+Result<std::string> BuildSecondaryRefProfilePath(
+ const SecondaryRefProfilePath& secondary_ref_profile_path) {
+ OR_RETURN(ValidateDexPath(secondary_ref_profile_path.dexPath));
+ std::filesystem::path dex_path(secondary_ref_profile_path.dexPath);
+ return "{}/oat/{}.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildSecondaryCurProfilePath(
+ const SecondaryCurProfilePath& secondary_cur_profile_path) {
+ OR_RETURN(ValidateDexPath(secondary_cur_profile_path.dexPath));
+ std::filesystem::path dex_path(secondary_cur_profile_path.dexPath);
+ return "{}/oat/{}.cur.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) {
+ const WritableProfilePath& final_path = tmp_profile_path.finalPath;
+ switch (final_path.getTag()) {
+ case WritableProfilePath::forPrimary:
+ return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>());
+ case WritableProfilePath::forSecondary:
+ return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>());
+ // No default. All cases should be explicitly handled, or the compilation will fail.
+ }
+ // This should never happen. Just in case we get a non-enumerator value.
+ LOG(FATAL) << "Unexpected writable profile path type {}"_format(final_path.getTag());
+}
+
+Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) {
+ OR_RETURN(ValidatePathElementSubstring(tmp_profile_path.id, "id"));
+ return NewFile::BuildTempPath(OR_RETURN(BuildFinalProfilePath(tmp_profile_path)),
+ tmp_profile_path.id);
+}
+
+Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) {
+ OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath));
+ return ReplaceFileExtension(dex_metadata_path.dexPath, "dm");
+}
+
+Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
+ switch (profile_path.getTag()) {
+ case ProfilePath::primaryRefProfilePath:
+ return BuildPrimaryRefProfilePath(profile_path.get<ProfilePath::primaryRefProfilePath>());
+ case ProfilePath::prebuiltProfilePath:
+ return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>());
+ case ProfilePath::primaryCurProfilePath:
+ return BuildPrimaryCurProfilePath(profile_path.get<ProfilePath::primaryCurProfilePath>());
+ case ProfilePath::secondaryRefProfilePath:
+ return BuildSecondaryRefProfilePath(profile_path.get<ProfilePath::secondaryRefProfilePath>());
+ case ProfilePath::secondaryCurProfilePath:
+ return BuildSecondaryCurProfilePath(profile_path.get<ProfilePath::secondaryCurProfilePath>());
+ case ProfilePath::tmpProfilePath:
+ return BuildTmpProfilePath(profile_path.get<ProfilePath::tmpProfilePath>());
+ case ProfilePath::dexMetadataPath:
+ return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>());
+ // No default. All cases should be explicitly handled, or the compilation will fail.
+ }
+ // This should never happen. Just in case we get a non-enumerator value.
+ LOG(FATAL) << "Unexpected profile path type {}"_format(profile_path.getTag());
+}
+
+Result<std::string> BuildVdexPath(const VdexPath& vdex_path) {
+ DCHECK(vdex_path.getTag() == VdexPath::artifactsPath);
+ return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>())));
+}
+
+} // namespace artd
+} // namespace art
diff --git a/artd/path_utils.h b/artd/path_utils.h
new file mode 100644
index 0000000..0cc017e
--- /dev/null
+++ b/artd/path_utils.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ARTD_PATH_UTILS_H_
+#define ART_ARTD_PATH_UTILS_H_
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace artd {
+
+android::base::Result<void> ValidateDexPath(const std::string& dex_path);
+
+android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name);
+
+// Returns the absolute path to the OAT file built from the `ArtifactsPath`.
+android::base::Result<std::string> BuildOatPath(
+ const aidl::com::android::server::art::ArtifactsPath& artifacts_path);
+
+// Returns the path to the VDEX file that corresponds to the OAT file.
+inline std::string OatPathToVdexPath(const std::string& oat_path) {
+ return ReplaceFileExtension(oat_path, "vdex");
+}
+
+// Returns the path to the ART file that corresponds to the OAT file.
+inline std::string OatPathToArtPath(const std::string& oat_path) {
+ return ReplaceFileExtension(oat_path, "art");
+}
+
+android::base::Result<std::string> BuildPrimaryRefProfilePath(
+ const aidl::com::android::server::art::ProfilePath::PrimaryRefProfilePath&
+ primary_ref_profile_path);
+
+android::base::Result<std::string> BuildPrebuiltProfilePath(
+ const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path);
+
+android::base::Result<std::string> BuildPrimaryCurProfilePath(
+ const aidl::com::android::server::art::ProfilePath::PrimaryCurProfilePath&
+ primary_cur_profile_path);
+
+android::base::Result<std::string> BuildSecondaryRefProfilePath(
+ const aidl::com::android::server::art::ProfilePath::SecondaryRefProfilePath&
+ secondary_ref_profile_path);
+
+android::base::Result<std::string> BuildSecondaryCurProfilePath(
+ const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath&
+ secondary_cur_profile_path);
+
+android::base::Result<std::string> BuildFinalProfilePath(
+ const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildTmpProfilePath(
+ const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildDexMetadataPath(
+ const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path);
+
+android::base::Result<std::string> BuildProfileOrDmPath(
+ const aidl::com::android::server::art::ProfilePath& profile_path);
+
+android::base::Result<std::string> BuildVdexPath(
+ const aidl::com::android::server::art::VdexPath& vdex_path);
+
+} // namespace artd
+} // namespace art
+
+#endif // ART_ARTD_PATH_UTILS_H_
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
new file mode 100644
index 0000000..b0e1a25
--- /dev/null
+++ b/artd/path_utils_test.cc
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "path_utils.h"
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result-gmock.h"
+#include "base/common_art_test.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
+using ::aidl::com::android::server::art::ProfilePath;
+using ::aidl::com::android::server::art::VdexPath;
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::WithMessage;
+
+using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+
+using std::literals::operator""s; // NOLINT
+
+class PathUtilsTest : public CommonArtTest {};
+
+TEST_F(PathUtilsTest, BuildArtBinPath) {
+ auto scratch_dir = std::make_unique<ScratchDir>();
+ auto art_root_env = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
+ setenv("ANDROID_ART_ROOT", scratch_dir->GetPath().c_str(), /*overwrite=*/1);
+ EXPECT_THAT(BuildArtBinPath("foo"), HasValue(scratch_dir->GetPath() + "/bin/foo"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPath) {
+ EXPECT_THAT(
+ BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+ HasValue("/a/oat/arm64/b.odex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathDalvikCache) {
+ EXPECT_THAT(
+ BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = true}),
+ HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.dex"));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) {
+ EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Path is empty")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) {
+ EXPECT_THAT(
+ BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Path 'a/b.apk' is not an absolute path")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) {
+ EXPECT_THAT(BuildOatPath(ArtifactsPath{
+ .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathNul) {
+ EXPECT_THAT(BuildOatPath(ArtifactsPath{
+ .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidDexExtension) {
+ EXPECT_THAT(BuildOatPath(ArtifactsPath{
+ .dexPath = "/a/b.invalid", .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Dex path '/a/b.invalid' has an invalid extension")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) {
+ EXPECT_THAT(BuildOatPath(
+ ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}),
+ HasError(WithMessage("Instruction set 'invalid' is invalid")));
+}
+
+TEST_F(PathUtilsTest, OatPathToVdexPath) {
+ EXPECT_EQ(OatPathToVdexPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.vdex");
+}
+
+TEST_F(PathUtilsTest, OatPathToArtPath) {
+ EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePath) {
+ EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) {
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) {
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}),
+ HasError(WithMessage("packageName is empty")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}),
+ HasError(WithMessage("Invalid packageName '.'")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}),
+ HasError(WithMessage("Invalid packageName '..'")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}),
+ HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
+ HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) {
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "!@#$%^&*()_+-="}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) {
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
+ HasError(WithMessage("profileName is empty")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
+ HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
+ EXPECT_THAT(BuildPrimaryRefProfilePath(
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
+ HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) {
+ EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+ .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForSecondary) {
+ EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+ .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+ "/user/0/com.android.foo/a.apk"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForPrimary) {
+ EXPECT_THAT(
+ BuildTmpProfilePath(TmpProfilePath{
+ .finalPath =
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForSecondary) {
+ EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+ .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+ "/user/0/com.android.foo/a.apk"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) {
+ EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+ .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"},
+ .id = ""}),
+ HasError(WithMessage("id is empty")));
+ EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+ .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"},
+ .id = "123/45"}),
+ HasError(WithMessage("id '123/45' has invalid character '/'")));
+ EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+ .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"},
+ .id = "123\0a"s}),
+ HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
+}
+
+TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) {
+ EXPECT_THAT(BuildPrebuiltProfilePath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+ HasValue("/a/b.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildPrimaryCurProfilePath) {
+ EXPECT_THAT(BuildPrimaryCurProfilePath(PrimaryCurProfilePath{
+ .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryRefProfilePath) {
+ EXPECT_THAT(BuildSecondaryRefProfilePath(SecondaryRefProfilePath{
+ .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+ HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryCurProfilePath) {
+ EXPECT_THAT(BuildSecondaryCurProfilePath(SecondaryCurProfilePath{
+ .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+ HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.cur.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildDexMetadataPath) {
+ EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildProfilePath) {
+ EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+ .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+ EXPECT_THAT(
+ BuildProfileOrDmPath(TmpProfilePath{
+ .finalPath =
+ PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+ .id = "12345"}),
+ HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
+ EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
+ HasValue("/a/b.apk.prof"));
+ EXPECT_THAT(BuildProfileOrDmPath(PrimaryCurProfilePath{
+ .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
+ HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
+ EXPECT_THAT(BuildProfileOrDmPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
+}
+
+TEST_F(PathUtilsTest, BuildVdexPath) {
+ EXPECT_THAT(
+ BuildVdexPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
+ HasValue("/a/oat/arm64/b.vdex"));
+}
+
+} // namespace
+} // namespace artd
+} // namespace art
diff --git a/build/Android.bp b/build/Android.bp
index 382b0a3..29481d9 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -42,9 +42,9 @@
"performance-faster-string-find",
"performance-for-range-copy",
"performance-implicit-conversion-in-loop",
- "performance-noexcept-move-constructor",
"performance-unnecessary-copy-initialization",
"performance-unnecessary-value-param",
+ "performance-noexcept-move-constructor",
]
art_clang_tidy_disabled = [
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 07b37f9..96ebacb 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -270,6 +270,7 @@
"libartservice",
],
binaries: [
+ "art_exec",
"artd",
],
multilib: {
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 900ada7..39b6c1a 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -551,6 +551,7 @@
# removed in Android R.
# Check binaries for ART.
+ self._checker.check_executable('art_exec')
self._checker.check_executable('artd')
self._checker.check_executable('oatdump')
self._checker.check_executable("odrefresh")
diff --git a/build/boot/boot-image-profile.txt b/build/boot/boot-image-profile.txt
index ca2f8f2..120cb5b 100644
--- a/build/boot/boot-image-profile.txt
+++ b/build/boot/boot-image-profile.txt
@@ -33,7 +33,7 @@
HSPLandroid/system/Os;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;
HSPLandroid/system/Os;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
HSPLandroid/system/Os;->getpgid(I)I
-HSPLandroid/system/Os;->getpid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
+HSPLandroid/system/Os;->getpid()I
HSPLandroid/system/Os;->gettid()I
HSPLandroid/system/Os;->getuid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;
HSPLandroid/system/Os;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
@@ -805,10 +805,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 +880,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 +932,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 +960,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
@@ -1065,7 +1065,7 @@
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;->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;
HSPLcom/android/org/kxml2/io/KXmlParser;->readXmlDeclaration()V
HSPLcom/android/org/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V
HSPLcom/android/org/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V
@@ -1092,7 +1092,7 @@
HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;)V
HSPLdalvik/system/BaseDexClassLoader;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;[Ljava/lang/ClassLoader;Z)V
HSPLdalvik/system/BaseDexClassLoader;->addNativePath(Ljava/util/Collection;)V
-HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ldalvik/system/DexPathList;Ldalvik/system/DexPathList;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/ClassLoader;Ldalvik/system/PathClassLoader;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;]Ljava/lang/ClassNotFoundException;Ljava/lang/ClassNotFoundException;
+HSPLdalvik/system/BaseDexClassLoader;->findClass(Ljava/lang/String;)Ljava/lang/Class;
HSPLdalvik/system/BaseDexClassLoader;->findLibrary(Ljava/lang/String;)Ljava/lang/String;
HSPLdalvik/system/BaseDexClassLoader;->findResource(Ljava/lang/String;)Ljava/net/URL;
HSPLdalvik/system/BaseDexClassLoader;->findResources(Ljava/lang/String;)Ljava/util/Enumeration;
@@ -1118,7 +1118,7 @@
HSPLdalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard;
HSPLdalvik/system/CloseGuard;->getReporter()Ldalvik/system/CloseGuard$Reporter;
HSPLdalvik/system/CloseGuard;->open(Ljava/lang/String;)V
-HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V
HSPLdalvik/system/CloseGuard;->setEnabled(Z)V
HSPLdalvik/system/CloseGuard;->setReporter(Ldalvik/system/CloseGuard$Reporter;)V
HSPLdalvik/system/CloseGuard;->warnIfOpen()V
@@ -1140,7 +1140,7 @@
HSPLdalvik/system/DexPathList$Element;->findClass(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/util/List;)Ljava/lang/Class;
HSPLdalvik/system/DexPathList$Element;->findResource(Ljava/lang/String;)Ljava/net/URL;
HSPLdalvik/system/DexPathList$Element;->maybeInit()V
-HSPLdalvik/system/DexPathList$Element;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Boolean;Ljava/lang/Boolean;
+HSPLdalvik/system/DexPathList$Element;->toString()Ljava/lang/String;
HSPLdalvik/system/DexPathList$NativeLibraryElement;-><init>(Ljava/io/File;)V
HSPLdalvik/system/DexPathList$NativeLibraryElement;-><init>(Ljava/io/File;Ljava/lang/String;)V
HSPLdalvik/system/DexPathList$NativeLibraryElement;->equals(Ljava/lang/Object;)Z
@@ -1177,7 +1177,7 @@
HSPLdalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;
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;
+HSPLdalvik/system/VMRuntime;->notifyNativeAllocation()V
HSPLdalvik/system/VMRuntime;->registerNativeAllocation(I)V
HSPLdalvik/system/VMRuntime;->registerNativeFree(I)V
HSPLdalvik/system/VMRuntime;->runFinalization(J)V
@@ -1206,12 +1206,11 @@
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
@@ -1236,7 +1235,7 @@
HSPLjava/io/BufferedReader;->read([CII)I
HSPLjava/io/BufferedReader;->read1([CII)I
HSPLjava/io/BufferedReader;->readLine()Ljava/lang/String;
-HSPLjava/io/BufferedReader;->readLine(Z)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/io/BufferedReader;->readLine(Z)Ljava/lang/String;
HSPLjava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
HSPLjava/io/BufferedWriter;-><init>(Ljava/io/Writer;I)V
HSPLjava/io/BufferedWriter;->close()V
@@ -1285,14 +1284,14 @@
HSPLjava/io/DataInputStream;->readBoolean()Z
HSPLjava/io/DataInputStream;->readByte()B
HSPLjava/io/DataInputStream;->readFully([B)V
-HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/FileInputStream;
-HSPLjava/io/DataInputStream;->readInt()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readFully([BII)V
+HSPLjava/io/DataInputStream;->readInt()I
HSPLjava/io/DataInputStream;->readLong()J
HSPLjava/io/DataInputStream;->readShort()S
HSPLjava/io/DataInputStream;->readUTF()Ljava/lang/String;
-HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;+]Ljava/io/DataInput;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;
HSPLjava/io/DataInputStream;->readUnsignedByte()I
-HSPLjava/io/DataInputStream;->readUnsignedShort()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream;
+HSPLjava/io/DataInputStream;->readUnsignedShort()I
HSPLjava/io/DataInputStream;->skipBytes(I)I
HSPLjava/io/DataOutputStream;-><init>(Ljava/io/OutputStream;)V
HSPLjava/io/DataOutputStream;->flush()V
@@ -1305,7 +1304,7 @@
HSPLjava/io/DataOutputStream;->writeLong(J)V
HSPLjava/io/DataOutputStream;->writeShort(I)V
HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;)V
-HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I+]Ljava/io/DataOutput;Ljava/io/DataOutputStream;
+HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;Ljava/io/DataOutput;)I
HSPLjava/io/EOFException;-><init>()V
HSPLjava/io/EOFException;-><init>(Ljava/lang/String;)V
HSPLjava/io/ExpiringCache;->clear()V
@@ -1343,7 +1342,7 @@
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;
@@ -1388,12 +1387,12 @@
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,9 +1408,9 @@
HSPLjava/io/FilterInputStream;->close()V
HSPLjava/io/FilterInputStream;->mark(I)V
HSPLjava/io/FilterInputStream;->markSupported()Z
-HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/PushbackInputStream;,Ljava/io/ByteArrayInputStream;
-HSPLjava/io/FilterInputStream;->read([B)I
-HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types
+HSPLjava/io/FilterInputStream;->read()I
+HSPLjava/io/FilterInputStream;->read([B)I+]Ljava/io/FilterInputStream;Ljava/util/zip/InflaterInputStream;
+HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/FileInputStream;
HSPLjava/io/FilterInputStream;->reset()V
HSPLjava/io/FilterInputStream;->skip(J)J
HSPLjava/io/FilterOutputStream;-><init>(Ljava/io/OutputStream;)V
@@ -1441,14 +1440,14 @@
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;->peekByte()B
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;->read([BIIZ)I
HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBoolean()Z
HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
@@ -1467,6 +1466,7 @@
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;->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;
@@ -1490,19 +1490,19 @@
HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream;
-HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream;
+HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V
HSPLjava/io/ObjectInputStream$ValidationList;-><init>()V
HSPLjava/io/ObjectInputStream$ValidationList;->clear()V
HSPLjava/io/ObjectInputStream$ValidationList;->doCallbacks()V
-HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;,Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
HSPLjava/io/ObjectInputStream;->checkResolve(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/io/ObjectInputStream;->clear()V
-HSPLjava/io/ObjectInputStream;->close()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;
+HSPLjava/io/ObjectInputStream;->close()V
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;
@@ -1622,9 +1622,9 @@
HSPLjava/io/ObjectStreamClass$FieldReflector;->getFields()[Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass$FieldReflector;->getObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
HSPLjava/io/ObjectStreamClass$FieldReflector;->getPrimFieldValues(Ljava/lang/Object;[B)V
-HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V
HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V
-HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
+HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]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
HSPLjava/io/ObjectStreamClass$MemberSignature;-><init>(Ljava/lang/reflect/Constructor;)V
@@ -1656,17 +1656,17 @@
HSPLjava/io/ObjectStreamClass;->checkDefaultSerialize()V
HSPLjava/io/ObjectStreamClass;->checkDeserialize()V
HSPLjava/io/ObjectStreamClass;->checkSerialize()V
-HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/io/ObjectStreamClass;->classNamesEqual(Ljava/lang/String;Ljava/lang/String;)Z
HSPLjava/io/ObjectStreamClass;->computeDefaultSUID(Ljava/lang/Class;)J
HSPLjava/io/ObjectStreamClass;->computeFieldOffsets()V+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass;->forClass()Ljava/lang/Class;
HSPLjava/io/ObjectStreamClass;->getClassDataLayout()[Ljava/io/ObjectStreamClass$ClassDataSlot;
-HSPLjava/io/ObjectStreamClass;->getClassDataLayout0()[Ljava/io/ObjectStreamClass$ClassDataSlot;+]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/util/ArrayList;Ljava/util/ArrayList;
+HSPLjava/io/ObjectStreamClass;->getClassDataLayout0()[Ljava/io/ObjectStreamClass$ClassDataSlot;+]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/util/ArrayList;Ljava/util/ArrayList;]Ljava/lang/Class;Ljava/lang/Class;
HSPLjava/io/ObjectStreamClass;->getClassSignature(Ljava/lang/Class;)Ljava/lang/String;
HSPLjava/io/ObjectStreamClass;->getDeclaredSUID(Ljava/lang/Class;)Ljava/lang/Long;
HSPLjava/io/ObjectStreamClass;->getDeclaredSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass;->getDefaultSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField;
-HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass;->getFields(Z)[Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass;->getInheritableMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
HSPLjava/io/ObjectStreamClass;->getMethodSignature([Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/String;
@@ -1677,10 +1677,10 @@
HSPLjava/io/ObjectStreamClass;->getPrimDataSize()I
HSPLjava/io/ObjectStreamClass;->getPrimFieldValues(Ljava/lang/Object;[B)V
HSPLjava/io/ObjectStreamClass;->getPrivateMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method;
-HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture;
+HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]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;
+HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J
HSPLjava/io/ObjectStreamClass;->getSerializableConstructor(Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
HSPLjava/io/ObjectStreamClass;->getSuperDesc()Ljava/io/ObjectStreamClass;
HSPLjava/io/ObjectStreamClass;->getVariantFor(Ljava/lang/Class;)Ljava/io/ObjectStreamClass;
@@ -1689,7 +1689,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
@@ -1698,7 +1698,7 @@
HSPLjava/io/ObjectStreamClass;->isExternalizable()Z
HSPLjava/io/ObjectStreamClass;->isInstantiable()Z
HSPLjava/io/ObjectStreamClass;->isProxy()Z
-HSPLjava/io/ObjectStreamClass;->lookup(Ljava/lang/Class;Z)Ljava/io/ObjectStreamClass;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/io/ObjectStreamClass;->lookup(Ljava/lang/Class;Z)Ljava/io/ObjectStreamClass;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;
HSPLjava/io/ObjectStreamClass;->matchFields([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)[Ljava/io/ObjectStreamField;
HSPLjava/io/ObjectStreamClass;->newInstance()Ljava/lang/Object;
HSPLjava/io/ObjectStreamClass;->packageEquals(Ljava/lang/Class;Ljava/lang/Class;)Z
@@ -1752,23 +1752,23 @@
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;->flush()V
HSPLjava/io/PrintWriter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter;
HSPLjava/io/PrintWriter;->newLine()V
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
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/Object;)V
+HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V
HSPLjava/io/PrintWriter;->write(I)V
-HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V
HSPLjava/io/PrintWriter;->write(Ljava/lang/String;II)V
-HSPLjava/io/PrintWriter;->write([CII)V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter;
+HSPLjava/io/PrintWriter;->write([CII)V
HSPLjava/io/PushbackInputStream;-><init>(Ljava/io/InputStream;I)V
HSPLjava/io/PushbackInputStream;->close()V
HSPLjava/io/PushbackInputStream;->ensureOpen()V
@@ -1793,11 +1793,11 @@
HSPLjava/io/RandomAccessFile;->read([B)I
HSPLjava/io/RandomAccessFile;->read([BII)I
HSPLjava/io/RandomAccessFile;->readByte()B
-HSPLjava/io/RandomAccessFile;->readBytes([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
+HSPLjava/io/RandomAccessFile;->readBytes([BII)I
HSPLjava/io/RandomAccessFile;->readFully([B)V
HSPLjava/io/RandomAccessFile;->readFully([BII)V
HSPLjava/io/RandomAccessFile;->readInt()I
-HSPLjava/io/RandomAccessFile;->seek(J)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Llibcore/io/IoTracker;Llibcore/io/IoTracker;
+HSPLjava/io/RandomAccessFile;->seek(J)V
HSPLjava/io/RandomAccessFile;->setLength(J)V
HSPLjava/io/RandomAccessFile;->write(I)V
HSPLjava/io/RandomAccessFile;->write([B)V
@@ -1852,7 +1852,7 @@
HSPLjava/io/UnixFileSystem;->getSpace(Ljava/io/File;I)J
HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I
HSPLjava/io/UnixFileSystem;->isAbsolute(Ljava/io/File;)Z
-HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy;
+HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;
HSPLjava/io/UnixFileSystem;->normalize(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
HSPLjava/io/UnixFileSystem;->prefixLength(Ljava/lang/String;)I
HSPLjava/io/UnixFileSystem;->rename(Ljava/io/File;Ljava/io/File;)Z
@@ -1872,8 +1872,8 @@
HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;
HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder;
HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/AbstractStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;
-HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;
+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;
HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/String;Ljava/lang/String;
HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder;
HSPLjava/lang/AbstractStringBuilder;->append(Z)Ljava/lang/AbstractStringBuilder;
@@ -1944,10 +1944,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
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
@@ -1998,8 +1998,8 @@
HSPLjava/lang/Character;->toUpperCase(I)I
HSPLjava/lang/Character;->valueOf(C)Ljava/lang/Character;
HSPLjava/lang/Class;->asSubclass(Ljava/lang/Class;)Ljava/lang/Class;
-HSPLjava/lang/Class;->cast(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Class;Ljava/lang/Class;
-HSPLjava/lang/Class;->classNameImpliesTopLevel()Z+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->cast(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/lang/Class;->classNameImpliesTopLevel()Z
HSPLjava/lang/Class;->desiredAssertionStatus()Z
HSPLjava/lang/Class;->findInterfaceMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
HSPLjava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
@@ -2038,20 +2038,20 @@
HSPLjava/lang/Class;->getPublicMethodsInternal(Ljava/util/List;)V
HSPLjava/lang/Class;->getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String;
-HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;
HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;
HSPLjava/lang/Class;->getTypeName()Ljava/lang/String;
HSPLjava/lang/Class;->getTypeParameters()[Ljava/lang/reflect/TypeVariable;
HSPLjava/lang/Class;->isAnnotation()Z
HSPLjava/lang/Class;->isAnnotationPresent(Ljava/lang/Class;)Z
-HSPLjava/lang/Class;->isArray()Z+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->isArray()Z
HSPLjava/lang/Class;->isAssignableFrom(Ljava/lang/Class;)Z+]Ljava/lang/Class;Ljava/lang/Class;
HSPLjava/lang/Class;->isEnum()Z
-HSPLjava/lang/Class;->isInstance(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->isInstance(Ljava/lang/Object;)Z+]Ljava/lang/Class;Ljava/lang/Class;
HSPLjava/lang/Class;->isInterface()Z
-HSPLjava/lang/Class;->isLocalClass()Z+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Class;->isLocalClass()Z
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 +2083,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
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 +2102,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 +2116,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 +2138,7 @@
HSPLjava/lang/Enum;->name()Ljava/lang/String;
HSPLjava/lang/Enum;->ordinal()I
HSPLjava/lang/Enum;->toString()Ljava/lang/String;
-HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;+]Ljava/lang/Enum;Landroid/net/NetworkInfo$State;,Landroid/net/NetworkInfo$DetailedState;
HSPLjava/lang/Error;-><init>(Ljava/lang/String;)V
HSPLjava/lang/Exception;-><init>()V
HSPLjava/lang/Exception;-><init>(Ljava/lang/String;)V
@@ -2148,7 +2148,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
@@ -2182,10 +2182,10 @@
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;->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;
+HSPLjava/lang/Integer;->equals(Ljava/lang/Object;)Z
HSPLjava/lang/Integer;->floatValue()F
HSPLjava/lang/Integer;->formatUnsignedInt(II[BII)V
HSPLjava/lang/Integer;->getChars(II[B)I
@@ -2224,7 +2224,7 @@
HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;)Ljava/lang/Integer;
HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;I)Ljava/lang/Integer;
HSPLjava/lang/InterruptedException;-><init>()V
-HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/lang/Iterable;Ljava/util/HashSet;,Ljava/util/WeakHashMap$KeySet;,Ljava/util/WeakHashMap$EntrySet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/WeakHashMap$KeyIterator;,Ljava/util/WeakHashMap$EntryIterator;]Ljava/util/function/Consumer;missing_types
+HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V
HSPLjava/lang/LinkageError;-><init>(Ljava/lang/String;)V
HSPLjava/lang/Long;-><init>(J)V
HSPLjava/lang/Long;->bitCount(J)I
@@ -2235,7 +2235,7 @@
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 +2249,7 @@
HSPLjava/lang/Long;->lowestOneBit(J)J
HSPLjava/lang/Long;->numberOfLeadingZeros(J)I
HSPLjava/lang/Long;->numberOfTrailingZeros(J)I
+HSPLjava/lang/Long;->parseLong(Ljava/lang/CharSequence;III)J+]Ljava/lang/CharSequence;Ljava/lang/String;
HSPLjava/lang/Long;->parseLong(Ljava/lang/String;)J
HSPLjava/lang/Long;->parseLong(Ljava/lang/String;I)J
HSPLjava/lang/Long;->reverse(J)J
@@ -2318,6 +2319,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;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
HSPLjava/lang/Object;-><init>()V
HSPLjava/lang/Object;->clone()Ljava/lang/Object;
HSPLjava/lang/Object;->equals(Ljava/lang/Object;)Z
@@ -2325,7 +2327,7 @@
HSPLjava/lang/Object;->getClass()Ljava/lang/Class;
HSPLjava/lang/Object;->hashCode()I
HSPLjava/lang/Object;->identityHashCode(Ljava/lang/Object;)I
-HSPLjava/lang/Object;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;missing_types]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Object;->toString()Ljava/lang/String;
HSPLjava/lang/Object;->wait()V
HSPLjava/lang/Object;->wait(J)V
HSPLjava/lang/Package;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/net/URL;Ljava/lang/ClassLoader;)V
@@ -2378,9 +2380,9 @@
HSPLjava/lang/StackTraceElement;->getFileName()Ljava/lang/String;
HSPLjava/lang/StackTraceElement;->getLineNumber()I
HSPLjava/lang/StackTraceElement;->getMethodName()Ljava/lang/String;
-HSPLjava/lang/StackTraceElement;->hashCode()I+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/StackTraceElement;->hashCode()I
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/StackTraceElement;->toString()Ljava/lang/String;
HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/String;Ljava/lang/String;)I
HSPLjava/lang/String;->checkBoundsBeginEnd(III)V
@@ -2389,17 +2391,17 @@
HSPLjava/lang/String;->codePointCount(II)I
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;
+HSPLjava/lang/String;->contains(Ljava/lang/CharSequence;)Z
HSPLjava/lang/String;->contentEquals(Ljava/lang/CharSequence;)Z
HSPLjava/lang/String;->copyValueOf([C)Ljava/lang/String;
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;->equalsIgnoreCase(Ljava/lang/String;)Z
+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;
HSPLjava/lang/String;->getBytes()[B
-HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;
-HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B+]Ljava/nio/charset/Charset;missing_types
+HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B
+HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B
HSPLjava/lang/String;->getChars(II[CI)V
HSPLjava/lang/String;->getChars([CI)V
HSPLjava/lang/String;->hashCode()I
@@ -2408,22 +2410,19 @@
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;->isEmpty()Z
HSPLjava/lang/String;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
HSPLjava/lang/String;->join(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
HSPLjava/lang/String;->lastIndexOf(I)I+]Ljava/lang/String;Ljava/lang/String;
HSPLjava/lang/String;->lastIndexOf(II)I
-HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I
HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;I)I
HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;Ljava/lang/String;I)I
-HSPLjava/lang/String;->lastIndexOf([CIILjava/lang/String;I)I
HSPLjava/lang/String;->lastIndexOf([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;->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
@@ -2475,7 +2474,7 @@
HSPLjava/lang/StringBuilder;-><init>(I)V
HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/CharSequence;)V
HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
-HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/Appendable;
HSPLjava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
HSPLjava/lang/StringBuilder;->append(D)Ljava/lang/StringBuilder;
HSPLjava/lang/StringBuilder;->append(F)Ljava/lang/StringBuilder;
@@ -2483,7 +2482,7 @@
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/AbstractStringBuilder;
HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;
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;
@@ -2537,7 +2536,7 @@
HSPLjava/lang/System;->clearProperty(Ljava/lang/String;)Ljava/lang/String;
HSPLjava/lang/System;->gc()V
HSPLjava/lang/System;->getProperties()Ljava/util/Properties;
-HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/lang/System$PropertiesWithNonOverrideableDefaults;
+HSPLjava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;
HSPLjava/lang/System;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
HSPLjava/lang/System;->getSecurityManager()Ljava/lang/SecurityManager;
HSPLjava/lang/System;->getenv(Ljava/lang/String;)Ljava/lang/String;
@@ -2558,7 +2557,7 @@
HSPLjava/lang/Thread;-><init>(Ljava/lang/String;)V
HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V
HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;J)V
-HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;JLjava/security/AccessControlContext;Z)V+]Ljava/lang/Thread;missing_types]Ljava/lang/ThreadGroup;Ljava/lang/ThreadGroup;
+HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;JLjava/security/AccessControlContext;Z)V
HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/String;)V
HSPLjava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V
HSPLjava/lang/Thread;->activeCount()I
@@ -2573,7 +2572,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
@@ -2641,7 +2640,7 @@
HSPLjava/lang/ThreadLocal;->initialValue()Ljava/lang/Object;
HSPLjava/lang/ThreadLocal;->nextHashCode()I
HSPLjava/lang/ThreadLocal;->remove()V
-HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;megamorphic_types
+HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V
HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;
HSPLjava/lang/ThreadLocal;->withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;
HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>()V
@@ -2651,12 +2650,12 @@
HSPLjava/lang/Throwable$WrappedPrintStream;->println(Ljava/lang/Object;)V
HSPLjava/lang/Throwable$WrappedPrintWriter;-><init>(Ljava/io/PrintWriter;)V
HSPLjava/lang/Throwable$WrappedPrintWriter;->lock()Ljava/lang/Object;
-HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;
+HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V
HSPLjava/lang/Throwable;-><init>()V
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V+]Ljava/lang/Throwable;missing_types
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;ZZ)V
-HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types
+HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V
HSPLjava/lang/Throwable;->addSuppressed(Ljava/lang/Throwable;)V
HSPLjava/lang/Throwable;->fillInStackTrace()Ljava/lang/Throwable;
HSPLjava/lang/Throwable;->getCause()Ljava/lang/Throwable;
@@ -2664,16 +2663,16 @@
HSPLjava/lang/Throwable;->getMessage()Ljava/lang/String;
HSPLjava/lang/Throwable;->getOurStackTrace()[Ljava/lang/StackTraceElement;
HSPLjava/lang/Throwable;->getStackTrace()[Ljava/lang/StackTraceElement;
-HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable;+]Ljava/util/List;Ljava/util/Collections$EmptyList;
+HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable;
HSPLjava/lang/Throwable;->initCause(Ljava/lang/Throwable;)Ljava/lang/Throwable;
-HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;Ljava/util/concurrent/CancellationException;,Ljava/io/IOException;,Ljava/lang/Throwable;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement;]Ljava/util/Set;Ljava/util/Collections$SetFromMap;
+HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V
HSPLjava/lang/Throwable;->printStackTrace()V
HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V
HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintWriter;)V
-HSPLjava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;missing_types]Ljava/util/Set;Ljava/util/Collections$SetFromMap;
+HSPLjava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V
HSPLjava/lang/Throwable;->readObject(Ljava/io/ObjectInputStream;)V
HSPLjava/lang/Throwable;->setStackTrace([Ljava/lang/StackTraceElement;)V
-HSPLjava/lang/Throwable;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Object;missing_types]Ljava/lang/Throwable;missing_types]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/lang/Throwable;->toString()Ljava/lang/String;
HSPLjava/lang/Throwable;->writeObject(Ljava/io/ObjectOutputStream;)V
HSPLjava/lang/UNIXProcess$2;-><init>(Ljava/lang/UNIXProcess;[I)V
HSPLjava/lang/UNIXProcess$2;->run()Ljava/lang/Object;
@@ -2793,7 +2792,7 @@
HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V
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;
+HSPLjava/lang/reflect/Array;->newArray(Ljava/lang/Class;I)Ljava/lang/Object;
HSPLjava/lang/reflect/Array;->newInstance(Ljava/lang/Class;I)Ljava/lang/Object;
HSPLjava/lang/reflect/Array;->newInstance(Ljava/lang/Class;[I)Ljava/lang/Object;
HSPLjava/lang/reflect/Array;->set(Ljava/lang/Object;ILjava/lang/Object;)V
@@ -2937,7 +2936,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,6 +2945,7 @@
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;->setScale(II)Ljava/math/BigDecimal;
HSPLjava/math/BigDecimal;->setScale(ILjava/math/RoundingMode;)Ljava/math/BigDecimal;
@@ -2996,8 +2996,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 +3008,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+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/math/BigInteger;Ljava/math/BigInteger;]Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;
HSPLjava/math/BigInteger;->stripLeadingZeroBytes([BII)[I
HSPLjava/math/BigInteger;->stripLeadingZeroInts([I)[I
HSPLjava/math/BigInteger;->subtract(Ljava/math/BigInteger;)Ljava/math/BigInteger;
HSPLjava/math/BigInteger;->subtract([I[I)[I
HSPLjava/math/BigInteger;->testBit(I)Z
-HSPLjava/math/BigInteger;->toByteArray()[B+]Ljava/math/BigInteger;Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->toByteArray()[B
HSPLjava/math/BigInteger;->toString()Ljava/lang/String;
HSPLjava/math/BigInteger;->toString(I)Ljava/lang/String;
+HSPLjava/math/BigInteger;->toString(Ljava/math/BigInteger;Ljava/lang/StringBuilder;II)V
HSPLjava/math/BigInteger;->trustedStripLeadingZeroInts([I)[I
HSPLjava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger;
HSPLjava/math/MathContext;->equals(Ljava/lang/Object;)Z
@@ -3044,6 +3046,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 +3074,7 @@
HSPLjava/net/AbstractPlainSocketImpl;->isConnectionReset()Z
HSPLjava/net/AbstractPlainSocketImpl;->isConnectionResetPending()Z
HSPLjava/net/AbstractPlainSocketImpl;->listen(I)V
-HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;
+HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V
HSPLjava/net/AbstractPlainSocketImpl;->setOption(ILjava/lang/Object;)V
HSPLjava/net/AbstractPlainSocketImpl;->socketClose()V
HSPLjava/net/AbstractPlainSocketImpl;->socketPreClose()V
@@ -3140,8 +3143,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 +3179,7 @@
HSPLjava/net/IDN;->toASCII(Ljava/lang/String;I)Ljava/lang/String;
HSPLjava/net/InMemoryCookieStore;-><init>()V
HSPLjava/net/InMemoryCookieStore;-><init>(I)V
-HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;
+HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V
HSPLjava/net/InMemoryCookieStore;->addIndex(Ljava/util/Map;Ljava/lang/Object;Ljava/net/HttpCookie;)V
HSPLjava/net/InMemoryCookieStore;->get(Ljava/net/URI;)Ljava/util/List;
HSPLjava/net/InMemoryCookieStore;->getEffectiveURI(Ljava/net/URI;)Ljava/net/URI;
@@ -3379,12 +3382,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 +3437,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 +3472,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 +3506,14 @@
HSPLjava/net/URLConnection;->setReadTimeout(I)V
HSPLjava/net/URLConnection;->setUseCaches(Z)V
HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;
HSPLjava/net/URLDecoder;->isValidHexChar(C)Z
HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/BitSet;Ljava/util/BitSet;]Ljava/io/CharArrayWriter;Ljava/io/CharArrayWriter;
+HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;
HSPLjava/net/URLStreamHandler;-><init>()V
-HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V+]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler;]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URL;Ljava/net/URL;
+HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V
HSPLjava/net/URLStreamHandler;->setURL(Ljava/net/URL;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/net/URL;Ljava/net/URL;
+HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;
HSPLjava/net/UnknownHostException;-><init>(Ljava/lang/String;)V
HSPLjava/nio/Bits;->byteOrder()Ljava/nio/ByteOrder;
HSPLjava/nio/Bits;->char0(C)B
@@ -3519,12 +3522,12 @@
HSPLjava/nio/Bits;->getFloatL(Ljava/nio/ByteBuffer;I)F
HSPLjava/nio/Bits;->getInt(Ljava/nio/ByteBuffer;IZ)I
HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I
-HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I
HSPLjava/nio/Bits;->getLong(Ljava/nio/ByteBuffer;IZ)J
HSPLjava/nio/Bits;->getLongB(Ljava/nio/ByteBuffer;I)J
HSPLjava/nio/Bits;->getLongL(Ljava/nio/ByteBuffer;I)J
HSPLjava/nio/Bits;->getShort(Ljava/nio/ByteBuffer;IZ)S
-HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S
HSPLjava/nio/Bits;->getShortL(Ljava/nio/ByteBuffer;I)S
HSPLjava/nio/Bits;->int0(I)B
HSPLjava/nio/Bits;->int1(I)B
@@ -3548,8 +3551,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 +3562,7 @@
HSPLjava/nio/Bits;->short0(S)B
HSPLjava/nio/Bits;->short1(S)B
HSPLjava/nio/Bits;->unsafe()Lsun/misc/Unsafe;
-HSPLjava/nio/Buffer;-><init>(IIIII)V+]Ljava/nio/Buffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/HeapCharBuffer;,Ljava/nio/DirectByteBuffer;,Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/Buffer;-><init>(IIIII)V
HSPLjava/nio/Buffer;->capacity()I
HSPLjava/nio/Buffer;->checkBounds(III)V
HSPLjava/nio/Buffer;->checkIndex(I)I
@@ -3602,7 +3605,7 @@
HSPLjava/nio/ByteBuffer;->order()Ljava/nio/ByteOrder;
HSPLjava/nio/ByteBuffer;->order(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;
HSPLjava/nio/ByteBuffer;->position(I)Ljava/nio/Buffer;
-HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;
HSPLjava/nio/ByteBuffer;->put([B)Ljava/nio/ByteBuffer;
HSPLjava/nio/ByteBuffer;->reset()Ljava/nio/Buffer;
HSPLjava/nio/ByteBuffer;->rewind()Ljava/nio/Buffer;
@@ -3624,7 +3627,9 @@
HSPLjava/nio/ByteBufferAsIntBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
HSPLjava/nio/ByteBufferAsIntBuffer;->get([III)Ljava/nio/IntBuffer;
HSPLjava/nio/ByteBufferAsIntBuffer;->ix(I)I
-HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;+]Ljava/nio/ByteBufferAsIntBuffer;Ljava/nio/ByteBufferAsIntBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(I)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put(II)Ljava/nio/IntBuffer;
+HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;
HSPLjava/nio/ByteBufferAsLongBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V
HSPLjava/nio/ByteBufferAsLongBuffer;->get([JII)Ljava/nio/LongBuffer;
HSPLjava/nio/ByteBufferAsLongBuffer;->ix(I)I
@@ -3638,13 +3643,13 @@
HSPLjava/nio/CharBuffer;->allocate(I)Ljava/nio/CharBuffer;
HSPLjava/nio/CharBuffer;->array()[C
HSPLjava/nio/CharBuffer;->arrayOffset()I
-HSPLjava/nio/CharBuffer;->charAt(I)C+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;,Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/CharBuffer;->charAt(I)C
HSPLjava/nio/CharBuffer;->clear()Ljava/nio/Buffer;
HSPLjava/nio/CharBuffer;->flip()Ljava/nio/Buffer;
HSPLjava/nio/CharBuffer;->get([C)Ljava/nio/CharBuffer;
HSPLjava/nio/CharBuffer;->get([CII)Ljava/nio/CharBuffer;
HSPLjava/nio/CharBuffer;->hasArray()Z
-HSPLjava/nio/CharBuffer;->length()I+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/CharBuffer;->length()I
HSPLjava/nio/CharBuffer;->limit(I)Ljava/nio/Buffer;
HSPLjava/nio/CharBuffer;->position(I)Ljava/nio/Buffer;
HSPLjava/nio/CharBuffer;->toString()Ljava/lang/String;
@@ -3652,7 +3657,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
@@ -3670,7 +3675,7 @@
HSPLjava/nio/DirectByteBuffer;->get()B
HSPLjava/nio/DirectByteBuffer;->get(I)B
HSPLjava/nio/DirectByteBuffer;->get(J)B
-HSPLjava/nio/DirectByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer;
+HSPLjava/nio/DirectByteBuffer;->get([BII)Ljava/nio/ByteBuffer;
HSPLjava/nio/DirectByteBuffer;->getChar()C
HSPLjava/nio/DirectByteBuffer;->getChar(I)C
HSPLjava/nio/DirectByteBuffer;->getCharUnchecked(I)C
@@ -3680,7 +3685,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
@@ -3721,25 +3726,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(I)B
HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer;
HSPLjava/nio/HeapByteBuffer;->getFloat()F
HSPLjava/nio/HeapByteBuffer;->getFloat(I)F
HSPLjava/nio/HeapByteBuffer;->getInt()I
-HSPLjava/nio/HeapByteBuffer;->getInt(I)I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getInt(I)I
HSPLjava/nio/HeapByteBuffer;->getLong()J
HSPLjava/nio/HeapByteBuffer;->getLong(I)J
HSPLjava/nio/HeapByteBuffer;->getShort()S
-HSPLjava/nio/HeapByteBuffer;->getShort(I)S+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->getShort(I)S
HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[III)V
HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[SII)V
HSPLjava/nio/HeapByteBuffer;->isDirect()Z
HSPLjava/nio/HeapByteBuffer;->isReadOnly()Z
HSPLjava/nio/HeapByteBuffer;->ix(I)I
-HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer;
+HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;
HSPLjava/nio/HeapByteBuffer;->put(IB)Ljava/nio/ByteBuffer;
HSPLjava/nio/HeapByteBuffer;->put([BII)Ljava/nio/ByteBuffer;
HSPLjava/nio/HeapByteBuffer;->putChar(C)Ljava/nio/ByteBuffer;
@@ -3756,9 +3761,9 @@
HSPLjava/nio/HeapCharBuffer;-><init>([CII)V
HSPLjava/nio/HeapCharBuffer;-><init>([CIIIIIZ)V
HSPLjava/nio/HeapCharBuffer;-><init>([CIIZ)V
-HSPLjava/nio/HeapCharBuffer;->get(I)C+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;
+HSPLjava/nio/HeapCharBuffer;->get(I)C
HSPLjava/nio/HeapCharBuffer;->ix(I)I
-HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/CharBuffer;Ljava/nio/ByteBufferAsCharBuffer;
+HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;
HSPLjava/nio/HeapCharBuffer;->put([CII)Ljava/nio/CharBuffer;
HSPLjava/nio/HeapCharBuffer;->slice()Ljava/nio/CharBuffer;
HSPLjava/nio/HeapCharBuffer;->toString(II)Ljava/lang/String;
@@ -3824,8 +3829,8 @@
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;->begin()V
+HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V
HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V
HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->end(Z)V
HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->isOpen()Z
@@ -3871,27 +3876,27 @@
HSPLjava/nio/charset/Charset;->forName(Ljava/lang/String;)Ljava/nio/charset/Charset;
HSPLjava/nio/charset/Charset;->forNameUEE(Ljava/lang/String;)Ljava/nio/charset/Charset;
HSPLjava/nio/charset/Charset;->isSupported(Ljava/lang/String;)Z
-HSPLjava/nio/charset/Charset;->lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;
+HSPLjava/nio/charset/Charset;->lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;
HSPLjava/nio/charset/Charset;->lookup2(Ljava/lang/String;)Ljava/nio/charset/Charset;
HSPLjava/nio/charset/Charset;->name()Ljava/lang/String;
HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FF)V
HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FFLjava/lang/String;)V
HSPLjava/nio/charset/CharsetDecoder;->averageCharsPerByte()F
HSPLjava/nio/charset/CharsetDecoder;->charset()Ljava/nio/charset/Charset;
-HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
-HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;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;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;
+HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;Z)Ljava/nio/charset/CoderResult;
+HSPLjava/nio/charset/CharsetDecoder;->flush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;
HSPLjava/nio/charset/CharsetDecoder;->implFlush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;
HSPLjava/nio/charset/CharsetDecoder;->implOnMalformedInput(Ljava/nio/charset/CodingErrorAction;)V
HSPLjava/nio/charset/CharsetDecoder;->implOnUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)V
HSPLjava/nio/charset/CharsetDecoder;->implReset()V
HSPLjava/nio/charset/CharsetDecoder;->malformedInputAction()Ljava/nio/charset/CodingErrorAction;
HSPLjava/nio/charset/CharsetDecoder;->maxCharsPerByte()F
-HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
-HSPLjava/nio/charset/CharsetDecoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
-HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
+HSPLjava/nio/charset/CharsetDecoder;->onMalformedInput(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;
+HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;
HSPLjava/nio/charset/CharsetDecoder;->replacement()Ljava/lang/String;
-HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU;
+HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;
HSPLjava/nio/charset/CharsetDecoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction;
HSPLjava/nio/charset/CharsetEncoder;-><init>(Ljava/nio/charset/Charset;FF)V
HSPLjava/nio/charset/CharsetEncoder;-><init>(Ljava/nio/charset/Charset;FF[B)V
@@ -3944,6 +3949,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+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
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
@@ -4205,7 +4211,6 @@
HSPLjava/security/spec/EllipticCurve;->getField()Ljava/security/spec/ECField;
HSPLjava/security/spec/EncodedKeySpec;-><init>([B)V
HSPLjava/security/spec/EncodedKeySpec;->getEncoded()[B
-HSPLjava/security/spec/NamedParameterSpec;->getName()Ljava/lang/String;
HSPLjava/security/spec/PKCS8EncodedKeySpec;-><init>([B)V
HSPLjava/security/spec/PKCS8EncodedKeySpec;->getEncoded()[B
HSPLjava/security/spec/X509EncodedKeySpec;-><init>([B)V
@@ -4255,7 +4260,7 @@
HSPLjava/text/DateFormatSymbols;->initializeSupplementaryData(Llibcore/icu/LocaleData;)V
HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;)V
HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;Ljava/text/DecimalFormatSymbols;)V
-HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;
HSPLjava/text/DecimalFormat;->equals(Ljava/lang/Object;)Z
HSPLjava/text/DecimalFormat;->format(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;
@@ -4277,15 +4282,15 @@
HSPLjava/text/DecimalFormat;->setDecimalSeparatorAlwaysShown(Z)V
HSPLjava/text/DecimalFormat;->setGroupingUsed(Z)V
HSPLjava/text/DecimalFormat;->setMaximumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->setMaximumIntegerDigits(I)V
HSPLjava/text/DecimalFormat;->setMinimumFractionDigits(I)V
-HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V+]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat;
+HSPLjava/text/DecimalFormat;->setMinimumIntegerDigits(I)V
HSPLjava/text/DecimalFormat;->setParseIntegerOnly(Z)V
HSPLjava/text/DecimalFormat;->toPattern()Ljava/lang/String;
HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V
HSPLjava/text/DecimalFormatSymbols;-><init>(Ljava/util/Locale;)V
HSPLjava/text/DecimalFormatSymbols;->clone()Ljava/lang/Object;
-HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;]Landroid/icu/util/Currency;Landroid/icu/util/Currency;
+HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;
HSPLjava/text/DecimalFormatSymbols;->getCurrency()Ljava/util/Currency;
HSPLjava/text/DecimalFormatSymbols;->getDecimalSeparator()C
HSPLjava/text/DecimalFormatSymbols;->getGroupingSeparator()C
@@ -4294,8 +4299,8 @@
HSPLjava/text/DecimalFormatSymbols;->getInstance(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols;
HSPLjava/text/DecimalFormatSymbols;->getNaN()Ljava/lang/String;
HSPLjava/text/DecimalFormatSymbols;->getZeroDigit()C
-HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V+]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData;
-HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V+]Ljava/util/Currency;Ljava/util/Currency;]Ljava/util/Locale;Ljava/util/Locale;
+HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V
+HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V
HSPLjava/text/DecimalFormatSymbols;->maybeStripMarkers(Ljava/lang/String;C)C
HSPLjava/text/DecimalFormatSymbols;->setCurrency(Ljava/util/Currency;)V
HSPLjava/text/DecimalFormatSymbols;->setCurrencySymbol(Ljava/lang/String;)V
@@ -4330,9 +4335,9 @@
HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;
HSPLjava/text/IcuIteratorWrapper;-><init>(Landroid/icu/text/BreakIterator;)V
HSPLjava/text/IcuIteratorWrapper;->checkOffset(ILjava/text/CharacterIterator;)V
-HSPLjava/text/IcuIteratorWrapper;->following(I)I+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->following(I)I
HSPLjava/text/IcuIteratorWrapper;->getText()Ljava/text/CharacterIterator;
-HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper;
+HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z
HSPLjava/text/IcuIteratorWrapper;->next()I
HSPLjava/text/IcuIteratorWrapper;->preceding(I)I
HSPLjava/text/IcuIteratorWrapper;->setText(Ljava/lang/String;)V
@@ -4341,7 +4346,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;
@@ -4378,7 +4383,7 @@
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;
@@ -4387,7 +4392,7 @@
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 +4403,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;->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/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;->useDateFormatSymbols()Z
+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
@@ -4511,7 +4516,7 @@
HSPLjava/time/LocalDateTime;->isBefore(Ljava/time/chrono/ChronoLocalDateTime;)Z
HSPLjava/time/LocalDateTime;->isSupported(Ljava/time/temporal/TemporalField;)Z
HSPLjava/time/LocalDateTime;->now()Ljava/time/LocalDateTime;
-HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;+]Ljava/time/Clock;Ljava/time/Clock$SystemClock;]Ljava/time/Instant;Ljava/time/Instant;]Ljava/time/ZoneId;Ljava/time/ZoneRegion;]Ljava/time/zone/ZoneRules;Ljava/time/zone/ZoneRules;
+HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;
HSPLjava/time/LocalDateTime;->of(Ljava/time/LocalDate;Ljava/time/LocalTime;)Ljava/time/LocalDateTime;
HSPLjava/time/LocalDateTime;->ofEpochSecond(JILjava/time/ZoneOffset;)Ljava/time/LocalDateTime;
HSPLjava/time/LocalDateTime;->ofInstant(Ljava/time/Instant;Ljava/time/ZoneId;)Ljava/time/LocalDateTime;
@@ -4597,7 +4602,7 @@
HSPLjava/time/chrono/ChronoZonedDateTime;->getChronology()Ljava/time/chrono/Chronology;
HSPLjava/time/chrono/ChronoZonedDateTime;->query(Ljava/time/temporal/TemporalQuery;)Ljava/lang/Object;
HSPLjava/time/chrono/ChronoZonedDateTime;->toEpochSecond()J+]Ljava/time/ZoneOffset;Ljava/time/ZoneOffset;]Ljava/time/LocalTime;Ljava/time/LocalTime;]Ljava/time/chrono/ChronoZonedDateTime;Ljava/time/ZonedDateTime;]Ljava/time/chrono/ChronoLocalDate;Ljava/time/LocalDate;
-HSPLjava/time/chrono/ChronoZonedDateTime;->toInstant()Ljava/time/Instant;+]Ljava/time/LocalTime;Ljava/time/LocalTime;]Ljava/time/chrono/ChronoZonedDateTime;Ljava/time/ZonedDateTime;
+HSPLjava/time/chrono/ChronoZonedDateTime;->toInstant()Ljava/time/Instant;
HSPLjava/time/chrono/IsoChronology;->isLeapYear(J)Z
HSPLjava/time/chrono/IsoChronology;->resolveDate(Ljava/util/Map;Ljava/time/format/ResolverStyle;)Ljava/time/LocalDate;
HSPLjava/time/chrono/IsoChronology;->resolveDate(Ljava/util/Map;Ljava/time/format/ResolverStyle;)Ljava/time/chrono/ChronoLocalDate;
@@ -4761,17 +4766,17 @@
HSPLjava/time/zone/ZoneRulesProvider;->getProvider(Ljava/lang/String;)Ljava/time/zone/ZoneRulesProvider;
HSPLjava/time/zone/ZoneRulesProvider;->getRules(Ljava/lang/String;Z)Ljava/time/zone/ZoneRules;
HSPLjava/util/AbstractCollection;-><init>()V
-HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z
HSPLjava/util/AbstractCollection;->clear()V
HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types
+HSPLjava/util/AbstractCollection;->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;->toString()Ljava/lang/String;
+HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Iterator;megamorphic_types
+HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/AbstractCollection;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Iterator;missing_types
HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;)V
HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;Ljava/util/AbstractList$Itr-IA;)V
HSPLjava/util/AbstractList$Itr;->checkForComodification()V
@@ -4805,8 +4810,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;
@@ -4833,11 +4838,10 @@
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;->get(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/AbstractMap;->hashCode()I+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/AbstractMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;,Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/HashMap$EntrySet;
-HSPLjava/util/AbstractMap;->isEmpty()Z+]Ljava/util/AbstractMap;missing_types
+HSPLjava/util/AbstractMap;->isEmpty()Z
HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V
-HSPLjava/util/AbstractMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/AbstractMap;->size()I
HSPLjava/util/AbstractMap;->toString()Ljava/lang/String;
HSPLjava/util/AbstractMap;->values()Ljava/util/Collection;
@@ -4850,40 +4854,35 @@
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$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;->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;->add(Ljava/lang/Object;)Z
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;->clear()V
HSPLjava/util/ArrayDeque;->contains(Ljava/lang/Object;)Z
HSPLjava/util/ArrayDeque;->delete(I)Z
HSPLjava/util/ArrayDeque;->descendingIterator()Ljava/util/Iterator;
-HSPLjava/util/ArrayDeque;->doubleCapacity()V
HSPLjava/util/ArrayDeque;->getFirst()Ljava/lang/Object;
HSPLjava/util/ArrayDeque;->getLast()Ljava/lang/Object;
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;
+HSPLjava/util/ArrayDeque;->poll()Ljava/lang/Object;
HSPLjava/util/ArrayDeque;->pollFirst()Ljava/lang/Object;
HSPLjava/util/ArrayDeque;->pollLast()Ljava/lang/Object;
HSPLjava/util/ArrayDeque;->pop()Ljava/lang/Object;
@@ -4903,7 +4902,7 @@
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;->remove()V
@@ -4912,44 +4911,50 @@
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;->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;->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;->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;)[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
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;->addAll(Ljava/util/Collection;)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;->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/Collections$UnmodifiableRandomAccessList;,Ljava/util/Collections$EmptyList;,Ljava/util/ArrayList;,Ljava/util/Collections$SingletonList;]Ljava/util/ArrayList;Ljava/util/ArrayList;
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;->grow()[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->grow(I)[Ljava/lang/Object;
+HSPLjava/util/ArrayList;->hashCode()I
+HSPLjava/util/ArrayList;->hashCodeRange(II)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;
HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types
+HSPLjava/util/ArrayList;->indexOfRange(Ljava/lang/Object;II)I
HSPLjava/util/ArrayList;->isEmpty()Z
HSPLjava/util/ArrayList;->iterator()Ljava/util/Iterator;
HSPLjava/util/ArrayList;->lastIndexOf(Ljava/lang/Object;)I
HSPLjava/util/ArrayList;->listIterator()Ljava/util/ListIterator;
HSPLjava/util/ArrayList;->listIterator(I)Ljava/util/ListIterator;
+HSPLjava/util/ArrayList;->newCapacity(I)I
+HSPLjava/util/ArrayList;->rangeCheckForAdd(I)V
HSPLjava/util/ArrayList;->readObject(Ljava/io/ObjectInputStream;)V
HSPLjava/util/ArrayList;->remove(I)Ljava/lang/Object;
-HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types
+HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z
HSPLjava/util/ArrayList;->removeAll(Ljava/util/Collection;)Z
HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;)Z
HSPLjava/util/ArrayList;->removeRange(II)V
@@ -4968,16 +4973,16 @@
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
HSPLjava/util/Arrays$ArrayList;->sort(Ljava/util/Comparator;)V
HSPLjava/util/Arrays$ArrayList;->spliterator()Ljava/util/Spliterator;
-HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;+][Ljava/lang/Object;missing_types
+HSPLjava/util/Arrays$ArrayList;->toArray()[Ljava/lang/Object;
HSPLjava/util/Arrays$ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
HSPLjava/util/Arrays;->asList([Ljava/lang/Object;)Ljava/util/List;
HSPLjava/util/Arrays;->binarySearch([CC)I
@@ -5035,7 +5040,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
@@ -5056,7 +5061,7 @@
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
@@ -5105,6 +5110,7 @@
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;->defaultTimeZone(Ljava/util/Locale;)Ljava/util/TimeZone;+]Ljava/util/Locale;Ljava/util/Locale;
HSPLjava/util/Calendar;->get(I)I
HSPLjava/util/Calendar;->getFirstDayOfWeek()I
HSPLjava/util/Calendar;->getInstance()Ljava/util/Calendar;
@@ -5139,7 +5145,7 @@
HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V
HSPLjava/util/Calendar;->setZoneShared(Z)V
HSPLjava/util/Calendar;->updateTime()V
-HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/LinkedList;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/LinkedList$ListItr;
+HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;
HSPLjava/util/Collection;->spliterator()Ljava/util/Spliterator;
HSPLjava/util/Collection;->stream()Ljava/util/stream/Stream;+]Ljava/util/Collection;megamorphic_types
HSPLjava/util/Collections$1;-><init>(Ljava/lang/Object;)V
@@ -5226,6 +5232,7 @@
HSPLjava/util/Collections$SynchronizedCollection;->size()I
HSPLjava/util/Collections$SynchronizedCollection;->toArray()[Ljava/lang/Object;
HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLjava/util/Collections$SynchronizedCollection;->toString()Ljava/lang/String;
HSPLjava/util/Collections$SynchronizedList;-><init>(Ljava/util/List;)V
HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object;
HSPLjava/util/Collections$SynchronizedMap;-><init>(Ljava/util/Map;)V
@@ -5237,7 +5244,7 @@
HSPLjava/util/Collections$SynchronizedMap;->isEmpty()Z
HSPLjava/util/Collections$SynchronizedMap;->keySet()Ljava/util/Set;
HSPLjava/util/Collections$SynchronizedMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map;Ljava/util/HashMap;
+HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V
HSPLjava/util/Collections$SynchronizedMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/Collections$SynchronizedMap;->size()I
HSPLjava/util/Collections$SynchronizedMap;->values()Ljava/util/Collection;
@@ -5245,19 +5252,19 @@
HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;)V
HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;Ljava/lang/Object;)V
HSPLjava/util/Collections$SynchronizedSet;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;-><init>(Ljava/util/Collections$UnmodifiableCollection;)V
HSPLjava/util/Collections$UnmodifiableCollection$1;->hasNext()Z+]Ljava/util/Iterator;missing_types
-HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;megamorphic_types
HSPLjava/util/Collections$UnmodifiableCollection;-><init>(Ljava/util/Collection;)V
-HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z+]Ljava/util/Collection;megamorphic_types
-HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;Ljava/util/HashSet;
+HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z
+HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z
HSPLjava/util/Collections$UnmodifiableCollection;->forEach(Ljava/util/function/Consumer;)V
-HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z
HSPLjava/util/Collections$UnmodifiableCollection;->iterator()Ljava/util/Iterator;
-HSPLjava/util/Collections$UnmodifiableCollection;->size()I+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->size()I
HSPLjava/util/Collections$UnmodifiableCollection;->stream()Ljava/util/stream/Stream;
-HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/Collection;megamorphic_types
-HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;missing_types
+HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;
+HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
HSPLjava/util/Collections$UnmodifiableCollection;->toString()Ljava/lang/String;
HSPLjava/util/Collections$UnmodifiableList$1;-><init>(Ljava/util/Collections$UnmodifiableList;I)V
HSPLjava/util/Collections$UnmodifiableList$1;->hasNext()Z
@@ -5265,18 +5272,18 @@
HSPLjava/util/Collections$UnmodifiableList$1;->nextIndex()I
HSPLjava/util/Collections$UnmodifiableList;-><init>(Ljava/util/List;)V
HSPLjava/util/Collections$UnmodifiableList;->equals(Ljava/lang/Object;)Z
-HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;+]Ljava/util/List;missing_types
+HSPLjava/util/Collections$UnmodifiableList;->get(I)Ljava/lang/Object;
HSPLjava/util/Collections$UnmodifiableList;->hashCode()I
HSPLjava/util/Collections$UnmodifiableList;->indexOf(Ljava/lang/Object;)I
HSPLjava/util/Collections$UnmodifiableList;->listIterator()Ljava/util/ListIterator;
HSPLjava/util/Collections$UnmodifiableList;->listIterator(I)Ljava/util/ListIterator;
HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;-><init>(Ljava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet;)V
-HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->hasNext()Z+]Ljava/util/Iterator;missing_types
+HSPLjava/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1;->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
@@ -5284,7 +5291,7 @@
HSPLjava/util/Collections$UnmodifiableMap;->entrySet()Ljava/util/Set;
HSPLjava/util/Collections$UnmodifiableMap;->equals(Ljava/lang/Object;)Z
HSPLjava/util/Collections$UnmodifiableMap;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;missing_types
+HSPLjava/util/Collections$UnmodifiableMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/Collections$UnmodifiableMap;->hashCode()I
HSPLjava/util/Collections$UnmodifiableMap;->isEmpty()Z
HSPLjava/util/Collections$UnmodifiableMap;->keySet()Ljava/util/Set;
@@ -5329,7 +5336,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;
@@ -5372,11 +5379,14 @@
HSPLjava/util/Comparator;->lambda$comparingInt$7b0bb60$1(Ljava/util/function/ToIntFunction;Ljava/lang/Object;Ljava/lang/Object;)I
HSPLjava/util/Comparator;->lambda$thenComparing$36697e65$1(Ljava/util/Comparator;Ljava/util/Comparator;Ljava/lang/Object;Ljava/lang/Object;)I
HSPLjava/util/Comparator;->naturalOrder()Ljava/util/Comparator;
+HSPLjava/util/Comparator;->nullsFirst(Ljava/util/Comparator;)Ljava/util/Comparator;
HSPLjava/util/Comparator;->reversed()Ljava/util/Comparator;
HSPLjava/util/Comparator;->thenComparing(Ljava/util/Comparator;)Ljava/util/Comparator;
HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;
HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLjava/util/Comparators$NullComparator;-><init>(ZLjava/util/Comparator;)V
+HSPLjava/util/Comparators$NullComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
HSPLjava/util/Currency;-><init>(Landroid/icu/util/Currency;)V
HSPLjava/util/Currency;->getCurrencyCode()Ljava/lang/String;
HSPLjava/util/Currency;->getInstance(Ljava/lang/String;)Ljava/util/Currency;
@@ -5405,16 +5415,19 @@
HSPLjava/util/Date;->toInstant()Ljava/time/Instant;
HSPLjava/util/Date;->toString()Ljava/lang/String;
HSPLjava/util/Dictionary;-><init>()V
-HSPLjava/util/DualPivotQuicksort;->doSort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->doSort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([CIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([CII[CII)V
-HSPLjava/util/DualPivotQuicksort;->sort([FIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([FII[FII)V
-HSPLjava/util/DualPivotQuicksort;->sort([IIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([III[III)V
-HSPLjava/util/DualPivotQuicksort;->sort([JIIZ)V
-HSPLjava/util/DualPivotQuicksort;->sort([JII[JII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([CII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([FII)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([III)V
+HSPLjava/util/DualPivotQuicksort;->insertionSort([JII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[JIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([CIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([FIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([IIII)V
+HSPLjava/util/DualPivotQuicksort;->sort([JIII)V
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[III)Z
+HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[JII)Z
HSPLjava/util/EnumMap$EntryIterator$Entry;-><init>(Ljava/util/EnumMap$EntryIterator;I)V
HSPLjava/util/EnumMap$EntryIterator$Entry;->checkIndexForEntryUse()V
HSPLjava/util/EnumMap$EntryIterator$Entry;->getKey()Ljava/lang/Enum;
@@ -5445,9 +5458,9 @@
HSPLjava/util/EnumMap;->clear()V
HSPLjava/util/EnumMap;->containsKey(Ljava/lang/Object;)Z
HSPLjava/util/EnumMap;->entrySet()Ljava/util/Set;
-HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Enum;missing_types
+HSPLjava/util/EnumMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/EnumMap;->getKeyUniverse(Ljava/lang/Class;)[Ljava/lang/Enum;
-HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types
+HSPLjava/util/EnumMap;->isValidKey(Ljava/lang/Object;)Z
HSPLjava/util/EnumMap;->keySet()Ljava/util/Set;
HSPLjava/util/EnumMap;->maskNull(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/EnumMap;->put(Ljava/lang/Enum;Ljava/lang/Object;)Ljava/lang/Object;
@@ -5481,7 +5494,7 @@
HSPLjava/util/Formatter$DateTime;->isValid(C)Z
HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;)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;-><init>(I)V
HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;
HSPLjava/util/Formatter$Flags;->contains(Ljava/util/Formatter$Flags;)Z+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
@@ -5491,31 +5504,31 @@
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;->adjustWidth(ILjava/util/Formatter$Flags;Z)I
-HSPLjava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;
+HSPLjava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V
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;->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;->flags(Ljava/lang/String;)Ljava/util/Formatter$Flags;
+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;[CLjava/util/Formatter$Flags;ILjava/util/Locale;)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;)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
@@ -5524,8 +5537,8 @@
HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;)V
HSPLjava/util/Formatter$FormatSpecifier;->printDateTime(Ljava/lang/Object;Ljava/util/Locale;)V
HSPLjava/util/Formatter$FormatSpecifier;->printFloat(Ljava/lang/Object;Ljava/util/Locale;)V
-HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer;
-HSPLjava/util/Formatter$FormatSpecifier;->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
+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;->width(Ljava/lang/String;)I
HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V
@@ -5547,13 +5560,13 @@
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;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;
HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C
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/Formatter;->parse(Ljava/lang/String;)[Ljava/util/Formatter$FormatString;
+HSPLjava/util/Formatter;->toString()Ljava/lang/String;
HSPLjava/util/GregorianCalendar;-><init>()V
HSPLjava/util/GregorianCalendar;-><init>(IIIIII)V
HSPLjava/util/GregorianCalendar;-><init>(IIIIIII)V
@@ -5588,8 +5601,8 @@
HSPLjava/util/GregorianCalendar;->setGregorianChange(Ljava/util/Date;)V
HSPLjava/util/GregorianCalendar;->setTimeZone(Ljava/util/TimeZone;)V
HSPLjava/util/HashMap$EntryIterator;-><init>(Ljava/util/HashMap;)V
-HSPLjava/util/HashMap$EntryIterator;->next()Ljava/lang/Object;+]Ljava/util/HashMap$EntryIterator;Ljava/util/HashMap$EntryIterator;
-HSPLjava/util/HashMap$EntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/HashMap$EntryIterator;Ljava/util/HashMap$EntryIterator;
+HSPLjava/util/HashMap$EntryIterator;->next()Ljava/lang/Object;
+HSPLjava/util/HashMap$EntryIterator;->next()Ljava/util/Map$Entry;
HSPLjava/util/HashMap$EntrySet;-><init>(Ljava/util/HashMap;)V
HSPLjava/util/HashMap$EntrySet;->iterator()Ljava/util/Iterator;
HSPLjava/util/HashMap$EntrySet;->size()I
@@ -5604,16 +5617,19 @@
HSPLjava/util/HashMap$HashMapSpliterator;->estimateSize()J
HSPLjava/util/HashMap$HashMapSpliterator;->getFence()I
HSPLjava/util/HashMap$KeyIterator;-><init>(Ljava/util/HashMap;)V
-HSPLjava/util/HashMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/HashMap$KeyIterator;Ljava/util/HashMap$KeyIterator;
+HSPLjava/util/HashMap$KeyIterator;->next()Ljava/lang/Object;
HSPLjava/util/HashMap$KeySet;-><init>(Ljava/util/HashMap;)V
HSPLjava/util/HashMap$KeySet;->contains(Ljava/lang/Object;)Z
HSPLjava/util/HashMap$KeySet;->forEach(Ljava/util/function/Consumer;)V
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+]Ljava/util/HashMap$KeySpliterator;Ljava/util/HashMap$KeySpliterator;]Ljava/util/function/Consumer;Ljava/util/stream/ReferencePipeline$2$1;,Ljava/util/stream/MatchOps$1MatchSink;
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;
@@ -5641,6 +5657,8 @@
HSPLjava/util/HashMap$Values;->iterator()Ljava/util/Iterator;
HSPLjava/util/HashMap$Values;->size()I
HSPLjava/util/HashMap$Values;->spliterator()Ljava/util/Spliterator;
+HSPLjava/util/HashMap$Values;->toArray()[Ljava/lang/Object;+]Ljava/util/HashMap;Ljava/util/HashMap;
+HSPLjava/util/HashMap$Values;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/HashMap;Ljava/util/HashMap;
HSPLjava/util/HashMap;-><init>()V
HSPLjava/util/HashMap;-><init>(I)V
HSPLjava/util/HashMap;-><init>(IF)V
@@ -5652,52 +5670,55 @@
HSPLjava/util/HashMap;->clear()V
HSPLjava/util/HashMap;->clone()Ljava/lang/Object;
HSPLjava/util/HashMap;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
-HSPLjava/util/HashMap;->containsKey(Ljava/lang/Object;)Z+]Ljava/util/HashMap;missing_types
+HSPLjava/util/HashMap;->containsKey(Ljava/lang/Object;)Z
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;Ljava/util/HashMap;
+HSPLjava/util/HashMap;->getNode(Ljava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;missing_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;->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;->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;->putMapEntries(Ljava/util/Map;Z)V
+HSPLjava/util/HashMap;->putVal(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types
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;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;
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
HSPLjava/util/HashSet;-><init>(IF)V
HSPLjava/util/HashSet;-><init>(IFZ)V
HSPLjava/util/HashSet;-><init>(Ljava/util/Collection;)V
-HSPLjava/util/HashSet;->add(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
+HSPLjava/util/HashSet;->add(Ljava/lang/Object;)Z
HSPLjava/util/HashSet;->clear()V
HSPLjava/util/HashSet;->clone()Ljava/lang/Object;
-HSPLjava/util/HashSet;->contains(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
+HSPLjava/util/HashSet;->contains(Ljava/lang/Object;)Z
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;->size()I+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;
+HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z
+HSPLjava/util/HashSet;->size()I
HSPLjava/util/HashSet;->spliterator()Ljava/util/Spliterator;
HSPLjava/util/HashSet;->writeObject(Ljava/io/ObjectOutputStream;)V
HSPLjava/util/Hashtable$EntrySet;-><init>(Ljava/util/Hashtable;)V
@@ -5727,7 +5748,7 @@
HSPLjava/util/Hashtable;->clone()Ljava/lang/Object;
HSPLjava/util/Hashtable;->containsKey(Ljava/lang/Object;)Z
HSPLjava/util/Hashtable;->entrySet()Ljava/util/Set;
-HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Ljava/lang/String;
+HSPLjava/util/Hashtable;->get(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/Hashtable;->getEnumeration(I)Ljava/util/Enumeration;
HSPLjava/util/Hashtable;->getIterator(I)Ljava/util/Iterator;
HSPLjava/util/Hashtable;->isEmpty()Z
@@ -5748,6 +5769,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 +5805,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$ListItr;-><init>(Ljava/util/List;I)V
+HSPLjava/util/ImmutableCollections$ListItr;->hasNext()Z
+HSPLjava/util/ImmutableCollections$ListItr;->next()Ljava/lang/Object;+]Ljava/util/List;Ljava/util/ImmutableCollections$ListN;,Ljava/util/ImmutableCollections$List12;
HSPLjava/util/ImmutableCollections$ListN;-><init>([Ljava/lang/Object;)V
HSPLjava/util/ImmutableCollections$ListN;->get(I)Ljava/lang/Object;
HSPLjava/util/ImmutableCollections$ListN;->size()I
HSPLjava/util/ImmutableCollections$Map1;->entrySet()Ljava/util/Set;
HSPLjava/util/ImmutableCollections$MapN;-><init>([Ljava/lang/Object;)V
HSPLjava/util/ImmutableCollections$MapN;->get(Ljava/lang/Object;)Ljava/lang/Object;
-HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Landroid/hardware/biometrics/BiometricSourceType;
-HSPLjava/util/ImmutableCollections$Set0;->instance()Ljava/util/ImmutableCollections$Set0;
-HSPLjava/util/ImmutableCollections$Set1;-><init>(Ljava/lang/Object;)V
-HSPLjava/util/ImmutableCollections$Set1;->iterator()Ljava/util/Iterator;
+HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I
HSPLjava/util/ImmutableCollections$SetN;-><init>([Ljava/lang/Object;)V
HSPLjava/util/ImmutableCollections$SetN;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/util/Pair;,Ljava/lang/String;
+HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/util/Pair;
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;,Ljava/util/AbstractList$Itr;
HSPLjava/util/JumboEnumSet$EnumSetIterator;-><init>(Ljava/util/JumboEnumSet;)V
HSPLjava/util/JumboEnumSet$EnumSetIterator;->hasNext()Z
HSPLjava/util/JumboEnumSet$EnumSetIterator;->next()Ljava/lang/Enum;
@@ -5821,24 +5844,24 @@
HSPLjava/util/KeyValueHolder;->getKey()Ljava/lang/Object;
HSPLjava/util/KeyValueHolder;->getValue()Ljava/lang/Object;
HSPLjava/util/LinkedHashMap$LinkedEntryIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
-HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/LinkedHashMap$LinkedEntryIterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/lang/Object;
+HSPLjava/util/LinkedHashMap$LinkedEntryIterator;->next()Ljava/util/Map$Entry;
HSPLjava/util/LinkedHashMap$LinkedEntrySet;-><init>(Ljava/util/LinkedHashMap;)V
HSPLjava/util/LinkedHashMap$LinkedEntrySet;->iterator()Ljava/util/Iterator;
HSPLjava/util/LinkedHashMap$LinkedEntrySet;->size()I
HSPLjava/util/LinkedHashMap$LinkedHashIterator;-><init>(Ljava/util/LinkedHashMap;)V
HSPLjava/util/LinkedHashMap$LinkedHashIterator;->hasNext()Z
HSPLjava/util/LinkedHashMap$LinkedHashIterator;->nextNode()Ljava/util/LinkedHashMap$LinkedHashMapEntry;
-HSPLjava/util/LinkedHashMap$LinkedHashIterator;->remove()V+]Ljava/util/LinkedHashMap;Ljava/util/LinkedHashMap;
+HSPLjava/util/LinkedHashMap$LinkedHashIterator;->remove()V
HSPLjava/util/LinkedHashMap$LinkedHashMapEntry;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V
HSPLjava/util/LinkedHashMap$LinkedKeyIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedHashMapEntry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/LinkedHashMap$LinkedKeyIterator;Ljava/util/LinkedHashMap$LinkedKeyIterator;
+HSPLjava/util/LinkedHashMap$LinkedKeyIterator;->next()Ljava/lang/Object;
HSPLjava/util/LinkedHashMap$LinkedKeySet;-><init>(Ljava/util/LinkedHashMap;)V
HSPLjava/util/LinkedHashMap$LinkedKeySet;->contains(Ljava/lang/Object;)Z
HSPLjava/util/LinkedHashMap$LinkedKeySet;->iterator()Ljava/util/Iterator;
HSPLjava/util/LinkedHashMap$LinkedKeySet;->size()I
HSPLjava/util/LinkedHashMap$LinkedValueIterator;-><init>(Ljava/util/LinkedHashMap;)V
-HSPLjava/util/LinkedHashMap$LinkedValueIterator;->next()Ljava/lang/Object;+]Ljava/util/LinkedHashMap$LinkedValueIterator;Ljava/util/LinkedHashMap$LinkedValueIterator;
+HSPLjava/util/LinkedHashMap$LinkedValueIterator;->next()Ljava/lang/Object;
HSPLjava/util/LinkedHashMap$LinkedValues;-><init>(Ljava/util/LinkedHashMap;)V
HSPLjava/util/LinkedHashMap$LinkedValues;->iterator()Ljava/util/Iterator;
HSPLjava/util/LinkedHashMap$LinkedValues;->size()I
@@ -5857,6 +5880,7 @@
HSPLjava/util/LinkedHashMap;->keySet()Ljava/util/Set;
HSPLjava/util/LinkedHashMap;->linkNodeLast(Ljava/util/LinkedHashMap$LinkedHashMapEntry;)V
HSPLjava/util/LinkedHashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node;
+HSPLjava/util/LinkedHashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
HSPLjava/util/LinkedHashMap;->reinitialize()V
HSPLjava/util/LinkedHashMap;->removeEldestEntry(Ljava/util/Map$Entry;)Z
HSPLjava/util/LinkedHashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode;
@@ -5866,12 +5890,12 @@
HSPLjava/util/LinkedHashSet;-><init>(I)V
HSPLjava/util/LinkedHashSet;-><init>(Ljava/util/Collection;)V
HSPLjava/util/LinkedList$ListItr;-><init>(Ljava/util/LinkedList;I)V
-HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V+]Ljava/util/LinkedList;Ljava/util/LinkedList;]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V
HSPLjava/util/LinkedList$ListItr;->checkForComodification()V
HSPLjava/util/LinkedList$ListItr;->hasNext()Z
HSPLjava/util/LinkedList$ListItr;->hasPrevious()Z
HSPLjava/util/LinkedList$ListItr;->next()Ljava/lang/Object;
-HSPLjava/util/LinkedList$ListItr;->previous()Ljava/lang/Object;+]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr;
+HSPLjava/util/LinkedList$ListItr;->previous()Ljava/lang/Object;
HSPLjava/util/LinkedList$ListItr;->remove()V
HSPLjava/util/LinkedList$ListItr;->set(Ljava/lang/Object;)V
HSPLjava/util/LinkedList$Node;-><init>(Ljava/util/LinkedList$Node;Ljava/lang/Object;Ljava/util/LinkedList$Node;)V
@@ -5905,6 +5929,7 @@
HSPLjava/util/LinkedList;->peekLast()Ljava/lang/Object;
HSPLjava/util/LinkedList;->poll()Ljava/lang/Object;
HSPLjava/util/LinkedList;->pollFirst()Ljava/lang/Object;
+HSPLjava/util/LinkedList;->pollLast()Ljava/lang/Object;
HSPLjava/util/LinkedList;->pop()Ljava/lang/Object;
HSPLjava/util/LinkedList;->push(Ljava/lang/Object;)V
HSPLjava/util/LinkedList;->remove()Ljava/lang/Object;
@@ -5919,11 +5944,13 @@
HSPLjava/util/LinkedList;->unlink(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
HSPLjava/util/LinkedList;->unlinkFirst(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
HSPLjava/util/LinkedList;->unlinkLast(Ljava/util/LinkedList$Node;)Ljava/lang/Object;
+HSPLjava/util/List;->copyOf(Ljava/util/Collection;)Ljava/util/List;
HSPLjava/util/List;->of()Ljava/util/List;
HSPLjava/util/List;->of(Ljava/lang/Object;)Ljava/util/List;
HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
-HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;]Ljava/util/List;Ljava/util/LinkedList;
+HSPLjava/util/List;->of([Ljava/lang/Object;)Ljava/util/List;
+HSPLjava/util/List;->sort(Ljava/util/Comparator;)V
HSPLjava/util/List;->spliterator()Ljava/util/Spliterator;
HSPLjava/util/Locale$Builder;-><init>()V
HSPLjava/util/Locale$Builder;->build()Ljava/util/Locale;
@@ -5932,10 +5959,10 @@
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;)V+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
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;->hashCode()I
@@ -5946,8 +5973,8 @@
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;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale;
+HSPLjava/util/Locale;->convertOldISOCodes(Ljava/lang/String;)Ljava/lang/String;
+HSPLjava/util/Locale;->equals(Ljava/lang/Object;)Z
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;
@@ -5966,9 +5993,11 @@
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;->getUnicodeLocaleType(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Locale;Ljava/util/Locale;
HSPLjava/util/Locale;->getVariant()Ljava/lang/String;
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;
@@ -5981,15 +6010,15 @@
HSPLjava/util/Locale;->writeObject(Ljava/io/ObjectOutputStream;)V
HSPLjava/util/Map;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;+]Ljava/util/function/Function;missing_types]Ljava/util/Map;Landroid/util/ArrayMap;
HSPLjava/util/Map;->forEach(Ljava/util/function/BiConsumer;)V
-HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;,Ljava/util/ImmutableCollections$MapN;
+HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;
HSPLjava/util/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
HSPLjava/util/Objects;->checkFromIndexSize(III)I
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;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
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
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 +6075,7 @@
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;->siftUp(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpComparable(ILjava/lang/Object;)V
-HSPLjava/util/PriorityQueue;->siftUpUsingComparator(ILjava/lang/Object;)V
HSPLjava/util/PriorityQueue;->size()I
HSPLjava/util/PriorityQueue;->toArray()[Ljava/lang/Object;
HSPLjava/util/PriorityQueue;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
@@ -6059,13 +6084,13 @@
HSPLjava/util/Properties$LineReader;->readLine()I
HSPLjava/util/Properties;-><init>()V
HSPLjava/util/Properties;-><init>(Ljava/util/Properties;)V
-HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/Properties;Ljava/util/Properties;
+HSPLjava/util/Properties;->getProperty(Ljava/lang/String;)Ljava/lang/String;
HSPLjava/util/Properties;->getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
HSPLjava/util/Properties;->load(Ljava/io/InputStream;)V
HSPLjava/util/Properties;->load(Ljava/io/Reader;)V
HSPLjava/util/Properties;->load0(Ljava/util/Properties$LineReader;)V
HSPLjava/util/Properties;->loadConvert([CII[C)Ljava/lang/String;
-HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;
+HSPLjava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String;
HSPLjava/util/Properties;->setProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
HSPLjava/util/Properties;->store(Ljava/io/OutputStream;Ljava/lang/String;)V
HSPLjava/util/Properties;->store0(Ljava/io/BufferedWriter;Ljava/lang/String;Z)V
@@ -6195,7 +6220,7 @@
HSPLjava/util/SimpleTimeZone;->getOffsets(J[I)I
HSPLjava/util/SimpleTimeZone;->getRawOffset()I
HSPLjava/util/SimpleTimeZone;->hasSameRules(Ljava/util/TimeZone;)Z
-HSPLjava/util/Spliterator$OfInt;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/Spliterator$OfInt;Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/stream/Streams$RangeIntSpliterator;
+HSPLjava/util/Spliterator$OfInt;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/Spliterator$OfInt;Ljava/util/Spliterators$IntArraySpliterator;
HSPLjava/util/Spliterator;->getExactSizeIfKnown()J+]Ljava/util/Spliterator;megamorphic_types
HSPLjava/util/Spliterators$ArraySpliterator;-><init>([Ljava/lang/Object;I)V
HSPLjava/util/Spliterators$ArraySpliterator;-><init>([Ljava/lang/Object;III)V
@@ -6205,6 +6230,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
@@ -6212,7 +6238,7 @@
HSPLjava/util/Spliterators$IntArraySpliterator;-><init>([IIII)V
HSPLjava/util/Spliterators$IntArraySpliterator;->characteristics()I
HSPLjava/util/Spliterators$IntArraySpliterator;->estimateSize()J
-HSPLjava/util/Spliterators$IntArraySpliterator;->forEachRemaining(Ljava/util/function/IntConsumer;)V
+HSPLjava/util/Spliterators$IntArraySpliterator;->forEachRemaining(Ljava/util/function/IntConsumer;)V+]Ljava/util/function/IntConsumer;Ljava/util/stream/IntPipeline$4$1;
HSPLjava/util/Spliterators$IntArraySpliterator;->tryAdvance(Ljava/util/function/IntConsumer;)Z
HSPLjava/util/Spliterators$IteratorSpliterator;-><init>(Ljava/util/Collection;I)V
HSPLjava/util/Spliterators$IteratorSpliterator;->characteristics()I
@@ -6257,7 +6283,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 +6301,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 +6318,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
@@ -6308,8 +6335,8 @@
HSPLjava/util/TreeMap$DescendingSubMap;->keyIterator()Ljava/util/Iterator;
HSPLjava/util/TreeMap$DescendingSubMap;->subLowest()Ljava/util/TreeMap$TreeMapEntry;
HSPLjava/util/TreeMap$EntryIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeMap$TreeMapEntry;)V
-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$EntryIterator;->next()Ljava/lang/Object;
+HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/util/Map$Entry;
HSPLjava/util/TreeMap$EntrySet;-><init>(Ljava/util/TreeMap;)V
HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;
HSPLjava/util/TreeMap$EntrySet;->size()I
@@ -6317,10 +6344,10 @@
HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$KeyIterator;Ljava/util/TreeMap$KeyIterator;
HSPLjava/util/TreeMap$KeySet;-><init>(Ljava/util/NavigableMap;)V
HSPLjava/util/TreeMap$KeySet;->isEmpty()Z
-HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;
HSPLjava/util/TreeMap$KeySet;->size()I
HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;-><init>(Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;)V
-HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;
+HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;
HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;-><init>(Ljava/util/TreeMap$NavigableSubMap;)V
HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->isEmpty()Z
HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->size()I
@@ -6374,7 +6401,7 @@
HSPLjava/util/TreeMap;->clone()Ljava/lang/Object;
HSPLjava/util/TreeMap;->colorOf(Ljava/util/TreeMap$TreeMapEntry;)Z
HSPLjava/util/TreeMap;->comparator()Ljava/util/Comparator;
-HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer;
+HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
HSPLjava/util/TreeMap;->computeRedLevel(I)I
HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z
HSPLjava/util/TreeMap;->deleteEntry(Ljava/util/TreeMap$TreeMapEntry;)V
@@ -6398,7 +6425,7 @@
HSPLjava/util/TreeMap;->getLowerEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;
HSPLjava/util/TreeMap;->headMap(Ljava/lang/Object;Z)Ljava/util/NavigableMap;
HSPLjava/util/TreeMap;->key(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object;
-HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator;
HSPLjava/util/TreeMap;->keyOrNull(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object;
HSPLjava/util/TreeMap;->keySet()Ljava/util/Set;
HSPLjava/util/TreeMap;->lastKey()Ljava/lang/Object;
@@ -6408,9 +6435,9 @@
HSPLjava/util/TreeMap;->parentOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
HSPLjava/util/TreeMap;->pollFirstEntry()Ljava/util/Map$Entry;
HSPLjava/util/TreeMap;->predecessor(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
-HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer;
+HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer;,Ljava/lang/Long;
HSPLjava/util/TreeMap;->putAll(Ljava/util/Map;)V
-HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/TreeMap;->rightOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry;
HSPLjava/util/TreeMap;->rotateLeft(Ljava/util/TreeMap$TreeMapEntry;)V
HSPLjava/util/TreeMap;->rotateRight(Ljava/util/TreeMap$TreeMapEntry;)V
@@ -6434,9 +6461,9 @@
HSPLjava/util/TreeSet;->contains(Ljava/lang/Object;)Z
HSPLjava/util/TreeSet;->descendingSet()Ljava/util/NavigableSet;
HSPLjava/util/TreeSet;->first()Ljava/lang/Object;
-HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;
+HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/TreeSet;->isEmpty()Z
-HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;]Ljava/util/NavigableSet;Ljava/util/TreeMap$KeySet;
+HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;
HSPLjava/util/TreeSet;->last()Ljava/lang/Object;
HSPLjava/util/TreeSet;->remove(Ljava/lang/Object;)Z
HSPLjava/util/TreeSet;->size()I
@@ -6494,7 +6521,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
@@ -6524,8 +6551,8 @@
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 +6585,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 +6593,7 @@
HSPLjava/util/concurrent/CompletableFuture$Signaller;->tryFire(I)Ljava/util/concurrent/CompletableFuture;
HSPLjava/util/concurrent/CompletableFuture;-><init>()V
HSPLjava/util/concurrent/CompletableFuture;->asyncRunStage(Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)Ljava/util/concurrent/CompletableFuture;
-HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;+]Ljava/util/concurrent/Executor;Ljava/util/concurrent/ForkJoinPool;
+HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;
HSPLjava/util/concurrent/CompletableFuture;->complete(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/CompletableFuture;->completeNull()Z
HSPLjava/util/concurrent/CompletableFuture;->completeValue(Ljava/lang/Object;)Z
@@ -6627,8 +6654,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;
HSPLjava/util/concurrent/ConcurrentHashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/concurrent/ConcurrentHashMap;->helpTransfer([Ljava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$Node;)[Ljava/util/concurrent/ConcurrentHashMap$Node;
HSPLjava/util/concurrent/ConcurrentHashMap;->initTable()[Ljava/util/concurrent/ConcurrentHashMap$Node;
@@ -6640,6 +6668,7 @@
HSPLjava/util/concurrent/ConcurrentHashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;
HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ConcurrentHashMap;->replace(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/concurrent/ConcurrentHashMap;->resizeStamp(I)I
@@ -6686,13 +6715,13 @@
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->bulkRemove(Ljava/util/function/Predicate;)Z
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->clear()V
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->contains(Ljava/lang/Object;)Z
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->isEmpty()Z
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->iterator()Ljava/util/Iterator;
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->lambda$clear$2(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->offer(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->peek()Ljava/lang/Object;
-HSPLjava/util/concurrent/ConcurrentLinkedQueue;->poll()Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue;]Ljava/util/concurrent/ConcurrentLinkedQueue$Node;Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
+HSPLjava/util/concurrent/ConcurrentLinkedQueue;->poll()Ljava/lang/Object;
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->remove(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->size()I
HSPLjava/util/concurrent/ConcurrentLinkedQueue;->succ(Ljava/util/concurrent/ConcurrentLinkedQueue$Node;)Ljava/util/concurrent/ConcurrentLinkedQueue$Node;
@@ -6723,7 +6752,7 @@
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$COWIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;
HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V
HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V
HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>([Ljava/lang/Object;)V
@@ -6740,8 +6769,8 @@
HSPLjava/util/concurrent/CopyOnWriteArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object;
HSPLjava/util/concurrent/CopyOnWriteArrayList;->get(I)Ljava/lang/Object;
HSPLjava/util/concurrent/CopyOnWriteArrayList;->getArray()[Ljava/lang/Object;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList;
-HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/media/ImageReader$SurfaceImage;
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I
+HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I
HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z
HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;
HSPLjava/util/concurrent/CopyOnWriteArrayList;->lambda$removeAll$0(Ljava/util/Collection;Ljava/lang/Object;)Z
@@ -6755,7 +6784,7 @@
HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;
HSPLjava/util/concurrent/CopyOnWriteArrayList;->toString()Ljava/lang/String;
HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>()V
-HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArraySet;
+HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V
HSPLjava/util/concurrent/CopyOnWriteArraySet;->add(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/CopyOnWriteArraySet;->addAll(Ljava/util/Collection;)Z
HSPLjava/util/concurrent/CopyOnWriteArraySet;->clear()V
@@ -6781,7 +6810,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 +6818,13 @@
HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;-><init>(Ljava/util/concurrent/ScheduledExecutorService;)V
HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
-HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledExecutorService;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V
HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;->finalize()V
HSPLjava/util/concurrent/Executors$RunnableAdapter;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V
-HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object;+]Ljava/lang/Runnable;missing_types
+HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object;
HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;)Ljava/util/concurrent/Callable;
HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;Ljava/lang/Object;)Ljava/util/concurrent/Callable;
HSPLjava/util/concurrent/Executors;->defaultThreadFactory()Ljava/util/concurrent/ThreadFactory;
@@ -6819,7 +6848,7 @@
HSPLjava/util/concurrent/FutureTask;->awaitDone(ZJ)I
HSPLjava/util/concurrent/FutureTask;->cancel(Z)Z
HSPLjava/util/concurrent/FutureTask;->done()V
-HSPLjava/util/concurrent/FutureTask;->finishCompletion()V+]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/FutureTask;->finishCompletion()V
HSPLjava/util/concurrent/FutureTask;->get()Ljava/lang/Object;
HSPLjava/util/concurrent/FutureTask;->get(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
HSPLjava/util/concurrent/FutureTask;->handlePossibleCancellationInterrupt(I)V
@@ -6827,7 +6856,7 @@
HSPLjava/util/concurrent/FutureTask;->isDone()Z
HSPLjava/util/concurrent/FutureTask;->removeWaiter(Ljava/util/concurrent/FutureTask$WaitNode;)V
HSPLjava/util/concurrent/FutureTask;->report(I)Ljava/lang/Object;
-HSPLjava/util/concurrent/FutureTask;->run()V+]Ljava/util/concurrent/Callable;Ljava/util/concurrent/Executors$RunnableAdapter;]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;,Landroid/os/AsyncTask$4;
+HSPLjava/util/concurrent/FutureTask;->run()V
HSPLjava/util/concurrent/FutureTask;->runAndReset()Z
HSPLjava/util/concurrent/FutureTask;->set(Ljava/lang/Object;)V
HSPLjava/util/concurrent/FutureTask;->setException(Ljava/lang/Throwable;)V
@@ -6863,7 +6892,7 @@
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
@@ -6894,8 +6923,8 @@
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->next()Ljava/lang/Runnable;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->remove()V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;-><init>()V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;)I
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;I)I
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->finishPoll(Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
@@ -6903,16 +6932,16 @@
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->indexOf(Ljava/lang/Object;)I
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->isEmpty()Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/RunnableScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->setIndex(Ljava/util/concurrent/RunnableScheduledFuture;I)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->size()I
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->toArray()[Ljava/lang/Object;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJ)V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJJ)V
@@ -6920,23 +6949,23 @@
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/lang/Object;)I+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/util/concurrent/Delayed;)I
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->isPeriodic()Z
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->setNextRunTime()V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(I)V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;)V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/lang/Runnable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/util/concurrent/Callable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getContinueExistingPeriodicTasksAfterShutdownPolicy()Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getExecuteExistingDelayedTasksAfterShutdownPolicy()Z
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->onShutdown()V
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->reExecutePeriodic(Ljava/util/concurrent/RunnableScheduledFuture;)V
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;
@@ -6946,7 +6975,7 @@
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(J)J
-HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J
HSPLjava/util/concurrent/Semaphore$FairSync;-><init>(I)V
HSPLjava/util/concurrent/Semaphore$FairSync;->tryAcquireShared(I)I
HSPLjava/util/concurrent/Semaphore$NonfairSync;-><init>(I)V
@@ -6971,13 +7000,13 @@
HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryCancel()V
HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryMatch(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z
HSPLjava/util/concurrent/SynchronousQueue$TransferStack;-><init>()V
-HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->awaitFulfill(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;ZJ)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/lang/Thread;Ljava/lang/Thread;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack;
+HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->awaitFulfill(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;ZJ)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;
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
@@ -7002,12 +7031,12 @@
HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->interruptIfStarted()V
HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->isHeldExclusively()Z
HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->isLocked()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->lock()V+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->lock()V
HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->run()V
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryAcquire(I)Z+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryAcquire(I)Z
HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryLock()Z
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryRelease(I)Z+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
-HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->unlock()V+]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->tryRelease(I)Z
+HSPLjava/util/concurrent/ThreadPoolExecutor$Worker;->unlock()V
HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;)V
HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;)V
HSPLjava/util/concurrent/ThreadPoolExecutor;-><init>(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;)V
@@ -7024,20 +7053,20 @@
HSPLjava/util/concurrent/ThreadPoolExecutor;->ctlOf(II)I
HSPLjava/util/concurrent/ThreadPoolExecutor;->decrementWorkerCount()V
HSPLjava/util/concurrent/ThreadPoolExecutor;->drainQueue()Ljava/util/List;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
-HSPLjava/util/concurrent/ThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/LinkedBlockingDeque;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->ensurePrestart()V
+HSPLjava/util/concurrent/ThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V
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
HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptWorkers()V
HSPLjava/util/concurrent/ThreadPoolExecutor;->isRunning(I)Z
-HSPLjava/util/concurrent/ThreadPoolExecutor;->isShutdown()Z+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/ThreadPoolExecutor;->isShutdown()Z
HSPLjava/util/concurrent/ThreadPoolExecutor;->isTerminated()Z
HSPLjava/util/concurrent/ThreadPoolExecutor;->onShutdown()V
HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartAllCoreThreads()I
@@ -7048,7 +7077,7 @@
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
HSPLjava/util/concurrent/ThreadPoolExecutor;->setCorePoolSize(I)V
HSPLjava/util/concurrent/ThreadPoolExecutor;->setKeepAliveTime(JLjava/util/concurrent/TimeUnit;)V
HSPLjava/util/concurrent/ThreadPoolExecutor;->setMaximumPoolSize(I)V
@@ -7060,7 +7089,7 @@
HSPLjava/util/concurrent/ThreadPoolExecutor;->toString()Ljava/lang/String;
HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V
HSPLjava/util/concurrent/ThreadPoolExecutor;->workerCountOf(I)I
-HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit;
+HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J
HSPLjava/util/concurrent/TimeUnit;->cvt(JJJ)J
HSPLjava/util/concurrent/TimeUnit;->sleep(J)V
HSPLjava/util/concurrent/TimeUnit;->toDays(J)J
@@ -7092,11 +7121,12 @@
HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndIncrement()I
HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndSet(I)I
HSPLjava/util/concurrent/atomic/AtomicInteger;->incrementAndGet()I
-HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;
+HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I
HSPLjava/util/concurrent/atomic/AtomicInteger;->lazySet(I)V
HSPLjava/util/concurrent/atomic/AtomicInteger;->set(I)V
+HSPLjava/util/concurrent/atomic/AtomicInteger;->weakCompareAndSetVolatile(II)Z
HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V
-HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V
HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;II)Z
HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->decrementAndGet(Ljava/lang/Object;)I
HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I
@@ -7121,7 +7151,7 @@
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->addAndGet(Ljava/lang/Object;J)J
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->compareAndSet(Ljava/lang/Object;JJ)Z
-HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->incrementAndGet(Ljava/lang/Object;)J
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;-><init>()V
HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicLongFieldUpdater;
@@ -7138,21 +7168,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;->accessCheck(Ljava/lang/Object;)V
HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
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;->valueCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class;
+HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->valueCheck(Ljava/lang/Object;)V
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
@@ -7171,10 +7203,10 @@
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;->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$Node;-><init>()V
@@ -7186,13 +7218,13 @@
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;-><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;->acquire(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireQueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireSharedInterruptibly(I)V
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->addWaiter(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cancelAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)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
@@ -7200,7 +7232,7 @@
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;->doReleaseShared()V
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
@@ -7211,8 +7243,8 @@
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isOnSyncQueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->owns(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->parkAndCheckInterrupt()Z
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
-HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/CountDownLatch$Sync;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker;
+HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z
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
@@ -7225,7 +7257,7 @@
HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->unparkSuccessor(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V
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;->parkNanos(Ljava/lang/Object;J)V
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/ReentrantLock$FairSync;-><init>()V
@@ -7233,29 +7265,29 @@
HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;-><init>()V
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;->newCondition()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;
HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->nonfairTryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryRelease(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryRelease(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;
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;
+HSPLjava/util/concurrent/locks/ReentrantLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock;->lockInterruptibly()V
HSPLjava/util/concurrent/locks/ReentrantLock;->newCondition()Ljava/util/concurrent/locks/Condition;
HSPLjava/util/concurrent/locks/ReentrantLock;->tryLock()Z
HSPLjava/util/concurrent/locks/ReentrantLock;->tryLock(JLjava/util/concurrent/TimeUnit;)Z
-HSPLjava/util/concurrent/locks/ReentrantLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$FairSync;
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;-><init>()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->readerShouldBlock()Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;->writerShouldBlock()Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;-><init>()V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->readerShouldBlock()Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;->writerShouldBlock()Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->lock()V
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;->unlock()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$HoldCounter;-><init>()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;-><init>()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;->initialValue()Ljava/lang/Object;
@@ -7263,23 +7295,18 @@
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;-><init>()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->exclusiveCount(I)I
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->fullTryAcquireShared(Ljava/lang/Thread;)I
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getWriteHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->sharedCount(I)I
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryRelease(I)Z
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;-><init>(Ljava/util/concurrent/locks/ReentrantReadWriteLock;)V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->lock()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->unlock()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>()V
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;-><init>(Z)V
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->getReadHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->getWriteHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;
-HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;+]Ljava/util/concurrent/locks/ReentrantReadWriteLock;Ljava/util/concurrent/locks/ReentrantReadWriteLock;
+HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock;
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/Lock;
HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;
@@ -7288,7 +7315,7 @@
HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;-><init>(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;)V
HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;->applyAsDouble(D)D
HSPLjava/util/function/DoubleUnaryOperator;->andThen(Ljava/util/function/DoubleUnaryOperator;)Ljava/util/function/DoubleUnaryOperator;
-HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D+]Ljava/util/function/DoubleUnaryOperator;Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda3;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda1;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda0;
+HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D
HSPLjava/util/function/Function$$ExternalSyntheticLambda1;-><init>()V
HSPLjava/util/function/Function$$ExternalSyntheticLambda1;->apply(Ljava/lang/Object;)Ljava/lang/Object;
HSPLjava/util/function/Function$$ExternalSyntheticLambda2;->apply(Ljava/lang/Object;)Ljava/lang/Object;
@@ -7484,7 +7511,7 @@
HSPLjava/util/logging/Logger;->getResourceBundleName()Ljava/lang/String;
HSPLjava/util/logging/Logger;->getUseParentHandlers()Z
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;->isLoggable(Ljava/util/logging/Level;)Z
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
@@ -7513,20 +7540,21 @@
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;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V
+HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuffer;Ljava/lang/String;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/util/regex/Matcher;
HSPLjava/util/regex/Matcher;->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;->end()I
+HSPLjava/util/regex/Matcher;->end(I)I
HSPLjava/util/regex/Matcher;->ensureMatch()V
-HSPLjava/util/regex/Matcher;->find()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
-HSPLjava/util/regex/Matcher;->find(I)Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->find()Z
+HSPLjava/util/regex/Matcher;->find(I)Z
+HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;
HSPLjava/util/regex/Matcher;->getTextLength()I
HSPLjava/util/regex/Matcher;->group()Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;+]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->groupCount()I+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
+HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;
+HSPLjava/util/regex/Matcher;->groupCount()I
HSPLjava/util/regex/Matcher;->hitEnd()Z
HSPLjava/util/regex/Matcher;->lookingAt()Z
HSPLjava/util/regex/Matcher;->matches()Z
@@ -7534,20 +7562,20 @@
HSPLjava/util/regex/Matcher;->region(II)Ljava/util/regex/Matcher;
HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String;
HSPLjava/util/regex/Matcher;->replaceFirst(Ljava/lang/String;)Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;
-HSPLjava/util/regex/Matcher;->resetForInput()V+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;
+HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->resetForInput()V
HSPLjava/util/regex/Matcher;->start()I
HSPLjava/util/regex/Matcher;->start(I)I
HSPLjava/util/regex/Matcher;->useAnchoringBounds(Z)Ljava/util/regex/Matcher;
-HSPLjava/util/regex/Matcher;->usePattern(Ljava/util/regex/Pattern;)Ljava/util/regex/Matcher;+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;
+HSPLjava/util/regex/Matcher;->usePattern(Ljava/util/regex/Pattern;)Ljava/util/regex/Matcher;
HSPLjava/util/regex/Matcher;->useTransparentBounds(Z)Ljava/util/regex/Matcher;
HSPLjava/util/regex/Pattern;-><init>(Ljava/lang/String;I)V
HSPLjava/util/regex/Pattern;->compile()V
HSPLjava/util/regex/Pattern;->compile(Ljava/lang/String;)Ljava/util/regex/Pattern;
HSPLjava/util/regex/Pattern;->compile(Ljava/lang/String;I)Ljava/util/regex/Pattern;
-HSPLjava/util/regex/Pattern;->fastSplit(Ljava/lang/String;Ljava/lang/String;I)[Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;
+HSPLjava/util/regex/Pattern;->fastSplit(Ljava/lang/String;Ljava/lang/String;I)[Ljava/lang/String;
HSPLjava/util/regex/Pattern;->matcher(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;
HSPLjava/util/regex/Pattern;->matches(Ljava/lang/String;Ljava/lang/CharSequence;)Z
HSPLjava/util/regex/Pattern;->pattern()Ljava/lang/String;
@@ -7556,10 +7584,9 @@
HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;
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/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,14 +7595,15 @@
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;
-HSPLjava/util/stream/AbstractPipeline;->wrapSink(Ljava/util/stream/Sink;)Ljava/util/stream/Sink;
+HSPLjava/util/stream/AbstractPipeline;->wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
+HSPLjava/util/stream/AbstractPipeline;->wrapSink(Ljava/util/stream/Sink;)Ljava/util/stream/Sink;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;
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+]Ljava/util/Collection;Ljava/util/ArrayList;
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 +7613,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 +7626,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,6 +7686,7 @@
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;->apply(I)Ljava/lang/Object;
@@ -7666,8 +7695,8 @@
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 +7712,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 +7736,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 +7769,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;
@@ -7764,7 +7797,7 @@
HSPLjava/util/stream/ReduceOps$2ReducingSink;->get()Ljava/lang/Object;
HSPLjava/util/stream/ReduceOps$2ReducingSink;->get()Ljava/util/Optional;
HSPLjava/util/stream/ReduceOps$3;-><init>(Ljava/util/stream/StreamShape;Ljava/util/function/BinaryOperator;Ljava/util/function/BiConsumer;Ljava/util/function/Supplier;Ljava/util/stream/Collector;)V
-HSPLjava/util/stream/ReduceOps$3;->getOpFlags()I
+HSPLjava/util/stream/ReduceOps$3;->getOpFlags()I+]Ljava/util/stream/Collector;Ljava/util/stream/Collectors$CollectorImpl;]Ljava/util/Set;Ljava/util/Collections$UnmodifiableSet;
HSPLjava/util/stream/ReduceOps$3;->makeSink()Ljava/util/stream/ReduceOps$3ReducingSink;
HSPLjava/util/stream/ReduceOps$3;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink;
HSPLjava/util/stream/ReduceOps$3ReducingSink;-><init>(Ljava/util/function/Supplier;Ljava/util/function/BiConsumer;Ljava/util/function/BinaryOperator;)V
@@ -7774,6 +7807,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+]Ljava/util/function/IntBinaryOperator;Ljava/util/stream/IntPipeline$$ExternalSyntheticLambda13;
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;
@@ -7788,12 +7822,12 @@
HSPLjava/util/stream/ReduceOps$Box;-><init>()V
HSPLjava/util/stream/ReduceOps$Box;->get()Ljava/lang/Object;
HSPLjava/util/stream/ReduceOps$ReduceOp;-><init>(Ljava/util/stream/StreamShape;)V
-HSPLjava/util/stream/ReduceOps$ReduceOp;->evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;
+HSPLjava/util/stream/ReduceOps$ReduceOp;->evaluateSequential(Ljava/util/stream/PipelineHelper;Ljava/util/Spliterator;)Ljava/lang/Object;+]Ljava/util/stream/ReduceOps$AccumulatingSink;Ljava/util/stream/ReduceOps$3ReducingSink;]Ljava/util/stream/PipelineHelper;Ljava/util/stream/IntPipeline$4;]Ljava/util/stream/ReduceOps$ReduceOp;Ljava/util/stream/ReduceOps$3;
HSPLjava/util/stream/ReduceOps;->makeDouble(Ljava/util/function/DoubleBinaryOperator;)Ljava/util/stream/TerminalOp;
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 +7852,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 +7874,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+]Ljava/util/Spliterator;megamorphic_types]Ljava/util/stream/Sink;Ljava/util/stream/ReferencePipeline$7$1;,Ljava/util/stream/ReferencePipeline$2$1;,Ljava/util/stream/ReferencePipeline$3$1;,Ljava/util/stream/MatchOps$1MatchSink;
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 +7901,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;
@@ -7978,9 +8018,9 @@
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+]Ljava/io/InputStream;missing_types]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
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+]Ljava/util/zip/InflaterInputStream;Ljava/util/zip/InflaterInputStream;,Ljava/util/zip/ZipInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater;
HSPLjava/util/zip/ZStreamRef;-><init>(J)V
HSPLjava/util/zip/ZStreamRef;->address()J
HSPLjava/util/zip/ZStreamRef;->clear()V
@@ -7988,7 +8028,7 @@
HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;
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/ZipEntry;-><init>()V
@@ -8040,7 +8080,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+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;
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 +8118,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 +8292,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 +8313,20 @@
HSPLjdk/internal/math/FDBigInteger;->rightInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger;
HSPLjdk/internal/math/FDBigInteger;->size()I
HSPLjdk/internal/math/FDBigInteger;->trimLeadingZeros()V
-HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;
HSPLjdk/internal/math/FDBigInteger;->valueOfPow2(I)Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;
HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljava/lang/Object;
HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;-><init>(ZI[CI)V
-HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
-HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D
+HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$mdtoa(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;IJIZ)V
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$msetSign(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;Z)V
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;-><init>()V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->developLongDigits(IJI)V
-HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;
+HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->estimateDecExp(JI)I
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getChars([C)I
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getDecimalExponent()I
@@ -8296,14 +8338,14 @@
HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->toJavaFormatString()Ljava/lang/String;
HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->doubleValue()D
HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->floatValue()F
-HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V+]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
-HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FloatingDecimal$1;
+HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V
+HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;
HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(DZ)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;
-HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D+]Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;
+HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D
HSPLjdk/internal/math/FloatingDecimal;->parseFloat(Ljava/lang/String;)F
-HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;
HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(D)Ljava/lang/String;
HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(F)Ljava/lang/String;
HSPLjdk/internal/math/FormattedFloatingDecimal$1;-><init>()V
@@ -8313,11 +8355,13 @@
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;->getAndAddInt(Ljava/lang/Object;JI)I
@@ -8345,7 +8389,7 @@
HSPLjdk/internal/util/ArraysSupport;->mismatch([I[II)I
HSPLjdk/internal/util/ArraysSupport;->mismatch([J[JI)I
HSPLjdk/internal/util/ArraysSupport;->mismatch([Z[ZI)I
-HSPLjdk/internal/util/ArraysSupport;->vectorizedMismatch(Ljava/lang/Object;JLjava/lang/Object;JII)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe;
+HSPLjdk/internal/util/ArraysSupport;->vectorizedMismatch(Ljava/lang/Object;JLjava/lang/Object;JII)I
HSPLjdk/internal/util/Preconditions;->checkFromIndexSize(IIILjava/util/function/BiFunction;)I
HSPLjdk/internal/util/Preconditions;->checkIndex(IILjava/util/function/BiFunction;)I
HSPLlibcore/content/type/MimeMap$Builder$Element;-><init>(Ljava/lang/String;Z)V
@@ -8369,7 +8413,7 @@
HSPLlibcore/icu/DecimalFormatData;->getExponentSeparator()Ljava/lang/String;
HSPLlibcore/icu/DecimalFormatData;->getGroupingSeparator()C
HSPLlibcore/icu/DecimalFormatData;->getInfinity()Ljava/lang/String;
-HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;+]Ljava/util/Locale;Ljava/util/Locale;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;
+HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;
HSPLlibcore/icu/DecimalFormatData;->getMinusSign()Ljava/lang/String;
HSPLlibcore/icu/DecimalFormatData;->getNaN()Ljava/lang/String;
HSPLlibcore/icu/DecimalFormatData;->getNumberPattern()Ljava/lang/String;
@@ -8409,7 +8453,7 @@
HSPLlibcore/io/BlockGuardOs;->close(Ljava/io/FileDescriptor;)V
HSPLlibcore/io/BlockGuardOs;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V
HSPLlibcore/io/BlockGuardOs;->fdatasync(Ljava/io/FileDescriptor;)V
-HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;
+HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;
HSPLlibcore/io/BlockGuardOs;->ftruncate(Ljava/io/FileDescriptor;J)V
HSPLlibcore/io/BlockGuardOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B
HSPLlibcore/io/BlockGuardOs;->isInetDomain(I)Z
@@ -8422,7 +8466,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,7 +8478,7 @@
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;
@@ -8470,7 +8514,7 @@
HSPLlibcore/io/ForwardingOs;->getnameinfo(Ljava/net/InetAddress;I)Ljava/lang/String;
HSPLlibcore/io/ForwardingOs;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;
HSPLlibcore/io/ForwardingOs;->getpgid(I)I
-HSPLlibcore/io/ForwardingOs;->getpid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux;
+HSPLlibcore/io/ForwardingOs;->getpid()I
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;
@@ -8480,7 +8524,7 @@
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 +8532,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 +8548,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 +8556,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 +8564,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 +8576,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;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 +8586,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
@@ -8620,6 +8664,7 @@
HSPLlibcore/reflect/ListOfVariables;->add(Ljava/lang/reflect/TypeVariable;)V
HSPLlibcore/reflect/ListOfVariables;->getArray()[Ljava/lang/reflect/TypeVariable;
HSPLlibcore/reflect/ParameterizedTypeImpl;-><init>(Llibcore/reflect/ParameterizedTypeImpl;Ljava/lang/String;Llibcore/reflect/ListOfTypes;Ljava/lang/ClassLoader;)V
+HSPLlibcore/reflect/ParameterizedTypeImpl;->equals(Ljava/lang/Object;)Z+]Llibcore/reflect/ListOfTypes;Llibcore/reflect/ListOfTypes;]Llibcore/reflect/ParameterizedTypeImpl;Llibcore/reflect/ParameterizedTypeImpl;]Ljava/lang/reflect/ParameterizedType;Llibcore/reflect/ParameterizedTypeImpl;
HSPLlibcore/reflect/ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type;
HSPLlibcore/reflect/ParameterizedTypeImpl;->getOwnerType()Ljava/lang/reflect/Type;
HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/Class;
@@ -8671,20 +8716,20 @@
HSPLlibcore/util/NativeAllocationRegistry;->createMalloced(Ljava/lang/ClassLoader;J)Llibcore/util/NativeAllocationRegistry;
HSPLlibcore/util/NativeAllocationRegistry;->createMalloced(Ljava/lang/ClassLoader;JJ)Llibcore/util/NativeAllocationRegistry;
HSPLlibcore/util/NativeAllocationRegistry;->createNonmalloced(Ljava/lang/ClassLoader;JJ)Llibcore/util/NativeAllocationRegistry;
-HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
-HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(Ljava/lang/Object;J)Ljava/lang/Runnable;+]Llibcore/util/NativeAllocationRegistry$CleanerThunk;Llibcore/util/NativeAllocationRegistry$CleanerThunk;
-HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;
+HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(J)V
+HSPLlibcore/util/NativeAllocationRegistry;->registerNativeAllocation(Ljava/lang/Object;J)Ljava/lang/Runnable;
+HSPLlibcore/util/NativeAllocationRegistry;->registerNativeFree(J)V
HSPLlibcore/util/SneakyThrow;->sneakyThrow(Ljava/lang/Throwable;)V
HSPLlibcore/util/SneakyThrow;->sneakyThrow_(Ljava/lang/Throwable;)V
HSPLlibcore/util/XmlObjectFactory;->newXmlPullParser()Lorg/xmlpull/v1/XmlPullParser;
-HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;
+HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V
HSPLlibcore/util/ZoneInfo;->clone()Ljava/lang/Object;
HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;)Llibcore/util/ZoneInfo;
-HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Ljava/lang/Integer;Ljava/lang/Integer;
+HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;
HSPLlibcore/util/ZoneInfo;->getDSTSavings()I
-HSPLlibcore/util/ZoneInfo;->getOffset(J)I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
+HSPLlibcore/util/ZoneInfo;->getOffset(J)I
HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I
-HSPLlibcore/util/ZoneInfo;->getRawOffset()I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;
+HSPLlibcore/util/ZoneInfo;->getRawOffset()I
HSPLlibcore/util/ZoneInfo;->hasSameRules(Ljava/util/TimeZone;)Z
HSPLlibcore/util/ZoneInfo;->hashCode()I
HSPLlibcore/util/ZoneInfo;->inDaylightTime(Ljava/util/Date;)Z
@@ -8708,6 +8753,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 +8789,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 +8800,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
@@ -8891,7 +8939,7 @@
HSPLsun/misc/ASCIICaseInsensitiveComparator;->toLower(I)I
HSPLsun/misc/Cleaner;-><init>(Ljava/lang/Object;Ljava/lang/Runnable;)V
HSPLsun/misc/Cleaner;->add(Lsun/misc/Cleaner;)Lsun/misc/Cleaner;
-HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Llibcore/util/NativeAllocationRegistry$CleanerThunk;,Lsun/nio/ch/FileChannelImpl$Unmapper;
+HSPLsun/misc/Cleaner;->clean()V
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
@@ -8950,7 +8998,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 +9009,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
@@ -9160,6 +9208,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;+]Lsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;Lsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;
+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 +9222,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 +9233,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+]Lsun/nio/fs/UnixFileAttributes;Lsun/nio/fs/UnixFileAttributes;
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
@@ -9223,16 +9275,16 @@
HSPLsun/nio/fs/UnixPath;->checkNotNul(Ljava/lang/String;C)V
HSPLsun/nio/fs/UnixPath;->checkRead()V
HSPLsun/nio/fs/UnixPath;->checkWrite()V
-HSPLsun/nio/fs/UnixPath;->encode(Lsun/nio/fs/UnixFileSystem;Ljava/lang/String;)[B
+HSPLsun/nio/fs/UnixPath;->encode(Lsun/nio/fs/UnixFileSystem;Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/ref/SoftReference;Ljava/lang/ref/SoftReference;]Ljava/nio/charset/Charset;Lcom/android/icu/charset/CharsetICU;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/lang/ThreadLocal;Ljava/lang/ThreadLocal;]Lsun/nio/fs/UnixFileSystem;Lsun/nio/fs/LinuxFileSystem;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;
HSPLsun/nio/fs/UnixPath;->getByteArrayForSysCalls()[B
HSPLsun/nio/fs/UnixPath;->getFileSystem()Ljava/nio/file/FileSystem;
HSPLsun/nio/fs/UnixPath;->getFileSystem()Lsun/nio/fs/UnixFileSystem;
HSPLsun/nio/fs/UnixPath;->getParent()Ljava/nio/file/Path;
HSPLsun/nio/fs/UnixPath;->getParent()Lsun/nio/fs/UnixPath;
HSPLsun/nio/fs/UnixPath;->getPathForExceptionMessage()Ljava/lang/String;
-HSPLsun/nio/fs/UnixPath;->initOffsets()V
+HSPLsun/nio/fs/UnixPath;->initOffsets()V+]Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixPath;
HSPLsun/nio/fs/UnixPath;->isEmpty()Z
-HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
+HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;
HSPLsun/nio/fs/UnixPath;->normalizeAndCheck(Ljava/lang/String;)Ljava/lang/String;
HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Ljava/nio/file/Path;
HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Lsun/nio/fs/UnixPath;
@@ -9521,7 +9573,7 @@
HSPLsun/security/util/DerInputStream;->mark(I)V
HSPLsun/security/util/DerInputStream;->peekByte()I
HSPLsun/security/util/DerInputStream;->readVector(I)[Lsun/security/util/DerValue;
-HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/util/Vector;Ljava/util/Vector;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream;
+HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;
HSPLsun/security/util/DerInputStream;->reset()V
HSPLsun/security/util/DerInputStream;->subStream(IZ)Lsun/security/util/DerInputStream;
HSPLsun/security/util/DerInputStream;->toByteArray()[B
@@ -9539,7 +9591,7 @@
HSPLsun/security/util/DerValue;-><init>(B[B)V
HSPLsun/security/util/DerValue;-><init>(Ljava/io/InputStream;)V
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>(Lsun/security/util/DerInputBuffer;Z)V
HSPLsun/security/util/DerValue;-><init>([B)V
HSPLsun/security/util/DerValue;->encode(Lsun/security/util/DerOutputStream;)V
HSPLsun/security/util/DerValue;->getBigInteger()Ljava/math/BigInteger;
@@ -9623,7 +9675,7 @@
HSPLsun/security/x509/AVA;->parseString(Ljava/io/Reader;IILjava/lang/StringBuilder;)Lsun/security/util/DerValue;
HSPLsun/security/x509/AVA;->readChar(Ljava/io/Reader;Ljava/lang/String;)I
HSPLsun/security/x509/AVA;->toKeyword(ILjava/util/Map;)Ljava/lang/String;
-HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/util/DerValue;Lsun/security/util/DerValue;
+HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;
HSPLsun/security/x509/AVA;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String;
HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;
HSPLsun/security/x509/AVAKeyword;->getOID(Ljava/lang/String;ILjava/util/Map;)Lsun/security/util/ObjectIdentifier;
@@ -9878,20 +9930,18 @@
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;+]Ljava/lang/ref/SoftReference;Ljava/lang/ref/SoftReference;
HSPLsun/util/locale/BaseLocale$Key;->hashCode()I
+HSPLsun/util/locale/BaseLocale$Key;->hashCode(Lsun/util/locale/BaseLocale;)I
HSPLsun/util/locale/BaseLocale$Key;->normalize(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key;
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lsun/util/locale/BaseLocale-IA;)V
+HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
HSPLsun/util/locale/BaseLocale;->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;
@@ -9925,7 +9975,7 @@
HSPLsun/util/locale/LanguageTag;->isRegion(Ljava/lang/String;)Z
HSPLsun/util/locale/LanguageTag;->isScript(Ljava/lang/String;)Z
HSPLsun/util/locale/LanguageTag;->isVariant(Ljava/lang/String;)Z
-HSPLsun/util/locale/LanguageTag;->parse(Ljava/lang/String;Lsun/util/locale/ParseStatus;)Lsun/util/locale/LanguageTag;
+HSPLsun/util/locale/LanguageTag;->parse(Ljava/lang/String;Lsun/util/locale/ParseStatus;)Lsun/util/locale/LanguageTag;+]Lsun/util/locale/StringTokenIterator;Lsun/util/locale/StringTokenIterator;]Ljava/util/Map;Ljava/util/HashMap;
HSPLsun/util/locale/LanguageTag;->parseExtensions(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z
HSPLsun/util/locale/LanguageTag;->parseExtlangs(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z
HSPLsun/util/locale/LanguageTag;->parseLanguage(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z
@@ -9936,8 +9986,8 @@
HSPLsun/util/locale/LanguageTag;->parseVariants(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z
HSPLsun/util/locale/LocaleObjectCache$CacheEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V
HSPLsun/util/locale/LocaleObjectCache$CacheEntry;->getKey()Ljava/lang/Object;
-HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue;
-HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Ljava/util/Locale$Cache;,Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry;
+HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V
+HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
HSPLsun/util/locale/LocaleObjectCache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object;
HSPLsun/util/locale/LocaleUtils;->caseIgnoreMatch(Ljava/lang/String;Ljava/lang/String;)Z
HSPLsun/util/locale/LocaleUtils;->isAlpha(C)Z
@@ -9967,6 +10017,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 +10095,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;
@@ -10434,7 +10486,6 @@
Ldalvik/annotation/optimization/CriticalNative;
Ldalvik/annotation/optimization/FastNative;
Ldalvik/annotation/optimization/NeverCompile;
-Ldalvik/annotation/optimization/NeverInline;
Ldalvik/system/AppSpecializationHooks;
Ldalvik/system/BaseDexClassLoader$Reporter;
Ldalvik/system/BaseDexClassLoader;
@@ -10469,13 +10520,13 @@
Ldalvik/system/SocketTagger;
Ldalvik/system/VMDebug;
Ldalvik/system/VMRuntime$HiddenApiUsageLogger;
+Ldalvik/system/VMRuntime$SdkVersionContainer;
Ldalvik/system/VMRuntime;
Ldalvik/system/VMStack;
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;
@@ -10653,8 +10704,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;
@@ -10701,8 +10750,13 @@
Ljava/lang/Short$ShortCache;
Ljava/lang/Short;
Ljava/lang/StackOverflowError;
+Ljava/lang/StackStreamFactory;
Ljava/lang/StackTraceElement;
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;
@@ -10819,6 +10873,7 @@
Ljava/lang/invoke/Transformers$ReferenceArrayElementSetter;
Ljava/lang/invoke/Transformers$ReferenceIdentity;
Ljava/lang/invoke/Transformers$Spreader;
+Ljava/lang/invoke/Transformers$TableSwitch;
Ljava/lang/invoke/Transformers$Transformer;
Ljava/lang/invoke/Transformers$TryFinally;
Ljava/lang/invoke/Transformers$VarargsCollector;
@@ -11441,10 +11496,10 @@
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,14 +11514,7 @@
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;
@@ -11619,12 +11667,9 @@
Ljava/util/ImmutableCollections$List12;
Ljava/util/ImmutableCollections$ListItr;
Ljava/util/ImmutableCollections$ListN;
-Ljava/util/ImmutableCollections$Map0;
Ljava/util/ImmutableCollections$Map1;
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;
Ljava/util/InputMismatchException;
@@ -11654,6 +11699,7 @@
Ljava/util/Locale$Cache;
Ljava/util/Locale$Category;
Ljava/util/Locale$FilteringMode;
+Ljava/util/Locale$IsoCountryCode;
Ljava/util/Locale$LanguageRange;
Ljava/util/Locale$LocaleKey;
Ljava/util/Locale$NoImagePreloadHolder;
@@ -12590,7 +12636,6 @@
Lsun/misc/JavaIOFileDescriptorAccess;
Lsun/misc/LRUCache;
Lsun/misc/SharedSecrets;
-Lsun/misc/Unsafe$$ExternalSyntheticBackportWithForwarding0;
Lsun/misc/Unsafe;
Lsun/misc/VM;
Lsun/misc/Version;
@@ -12869,6 +12914,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 +12940,7 @@
Lsun/util/locale/ParseStatus;
Lsun/util/locale/StringTokenIterator;
Lsun/util/locale/UnicodeLocaleExtension;
+Lsun/util/locale/provider/CalendarDataUtility;
Lsun/util/logging/LoggingProxy;
Lsun/util/logging/LoggingSupport$1;
Lsun/util/logging/LoggingSupport$2;
@@ -12954,6 +13001,7 @@
[Ljava/lang/Float;
[Ljava/lang/Integer;
[Ljava/lang/Long;
+[Ljava/lang/Number;
[Ljava/lang/Object;
[Ljava/lang/Package;
[Ljava/lang/Runnable;
diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes
index 4d22e22..d251ff0 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
@@ -466,7 +479,6 @@
dalvik.annotation.optimization.CriticalNative
dalvik.annotation.optimization.FastNative
dalvik.annotation.optimization.NeverCompile
-dalvik.annotation.optimization.NeverInline
dalvik.system.AppSpecializationHooks
dalvik.system.BaseDexClassLoader$Reporter
dalvik.system.BaseDexClassLoader
@@ -507,7 +519,6 @@
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 +527,7 @@
java.io.ByteArrayOutputStream
java.io.CharArrayReader
java.io.CharArrayWriter
+java.io.CharConversionException
java.io.Closeable
java.io.Console
java.io.DataInput
@@ -601,6 +613,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
@@ -682,8 +696,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
@@ -710,6 +722,8 @@
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
@@ -736,6 +750,7 @@
java.lang.Short$ShortCache
java.lang.Short
java.lang.StackOverflowError
+java.lang.StackStreamFactory
java.lang.StackTraceElement
java.lang.StrictMath
java.lang.String$CaseInsensitiveComparator-IA
@@ -854,6 +869,7 @@
java.lang.invoke.Transformers$ReferenceArrayElementSetter
java.lang.invoke.Transformers$ReferenceIdentity
java.lang.invoke.Transformers$Spreader
+java.lang.invoke.Transformers$TableSwitch
java.lang.invoke.Transformers$Transformer
java.lang.invoke.Transformers$TryFinally
java.lang.invoke.Transformers$VarargsCollector
@@ -1111,6 +1127,7 @@
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
@@ -1176,12 +1193,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
@@ -1389,6 +1408,7 @@
java.time.format.DateTimeFormatterBuilder$NumberPrinterParser
java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser
java.time.format.DateTimeFormatterBuilder$PadPrinterParserDecorator
+java.time.format.DateTimeFormatterBuilder$PrefixTree$CI
java.time.format.DateTimeFormatterBuilder$PrefixTree
java.time.format.DateTimeFormatterBuilder$SettingsParser
java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser
@@ -1471,7 +1491,6 @@
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
@@ -1489,14 +1508,7 @@
java.util.Arrays$ArrayList
java.util.Arrays$NaturalOrder
java.util.Arrays
-java.util.ArraysParallelSortHelpers$FJByte$Sorter
-java.util.ArraysParallelSortHelpers$FJChar$Sorter
-java.util.ArraysParallelSortHelpers$FJDouble$Sorter
-java.util.ArraysParallelSortHelpers$FJFloat$Sorter
-java.util.ArraysParallelSortHelpers$FJInt$Sorter
-java.util.ArraysParallelSortHelpers$FJLong$Sorter
java.util.ArraysParallelSortHelpers$FJObject$Sorter
-java.util.ArraysParallelSortHelpers$FJShort$Sorter
java.util.Base64$Decoder
java.util.Base64$Encoder
java.util.Base64
@@ -1648,12 +1660,8 @@
java.util.ImmutableCollections$AbstractImmutableSet
java.util.ImmutableCollections$List12
java.util.ImmutableCollections$ListN
-java.util.ImmutableCollections$Map0
java.util.ImmutableCollections$Map1
java.util.ImmutableCollections$MapN
-java.util.ImmutableCollections$Set0
-java.util.ImmutableCollections$Set1
-java.util.ImmutableCollections$Set2
java.util.ImmutableCollections$SetN
java.util.ImmutableCollections
java.util.InputMismatchException
@@ -1779,6 +1787,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 +1816,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 +1880,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
@@ -1935,6 +1947,7 @@
java.util.concurrent.Semaphore$NonfairSync
java.util.concurrent.Semaphore$Sync
java.util.concurrent.Semaphore
+java.util.concurrent.SynchronousQueue$TransferQueue$QNode
java.util.concurrent.SynchronousQueue$TransferQueue
java.util.concurrent.SynchronousQueue$TransferStack$SNode
java.util.concurrent.SynchronousQueue$TransferStack
@@ -2015,6 +2028,7 @@
java.util.function.IntUnaryOperator
java.util.function.LongBinaryOperator
java.util.function.LongConsumer
+java.util.function.LongPredicate
java.util.function.LongSupplier
java.util.function.LongUnaryOperator
java.util.function.Predicate
@@ -2038,6 +2052,7 @@
java.util.jar.JarVerifier
java.util.jar.Manifest$FastInputStream
java.util.jar.Manifest
+java.util.logging.ConsoleHandler
java.util.logging.ErrorManager
java.util.logging.FileHandler$1
java.util.logging.FileHandler$InitializationErrorManager
@@ -2157,6 +2172,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 +2246,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,6 +2291,7 @@
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
@@ -2309,6 +2328,7 @@
javax.crypto.Cipher$SpiAndProviderUpdater
javax.crypto.Cipher$Transform
javax.crypto.Cipher
+javax.crypto.CipherInputStream
javax.crypto.CipherOutputStream
javax.crypto.CipherSpi
javax.crypto.CryptoPermissions
@@ -2394,6 +2414,9 @@
javax.net.ssl.X509KeyManager
javax.net.ssl.X509TrustManager
javax.security.auth.Destroyable
+javax.security.auth.callback.Callback
+javax.security.auth.callback.CallbackHandler
+javax.security.auth.callback.PasswordCallback
javax.security.auth.callback.UnsupportedCallbackException
javax.security.auth.x500.X500Principal
javax.security.cert.Certificate
@@ -2404,6 +2427,7 @@
javax.xml.datatype.DatatypeConstants$Field
javax.xml.datatype.DatatypeConstants
javax.xml.datatype.Duration
+javax.xml.namespace.QName
javax.xml.parsers.DocumentBuilder
javax.xml.parsers.DocumentBuilderFactory
javax.xml.parsers.ParserConfigurationException
@@ -2418,6 +2442,8 @@
jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer
jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer
jdk.internal.math.FloatingDecimal
+jdk.internal.math.FormattedFloatingDecimal$1
+jdk.internal.math.FormattedFloatingDecimal$2
jdk.internal.math.FormattedFloatingDecimal$Form
jdk.internal.math.FormattedFloatingDecimal
jdk.internal.misc.JavaObjectInputStreamAccess
@@ -2584,7 +2610,6 @@
sun.misc.JavaIOFileDescriptorAccess
sun.misc.LRUCache
sun.misc.SharedSecrets
-sun.misc.Unsafe$$ExternalSyntheticBackportWithForwarding0
sun.misc.Unsafe
sun.misc.VM
sun.misc.Version
@@ -2752,6 +2777,7 @@
sun.security.util.AbstractAlgorithmConstraints$1
sun.security.util.AbstractAlgorithmConstraints
sun.security.util.AlgorithmDecomposer
+sun.security.util.AnchorCertificates$1
sun.security.util.AnchorCertificates
sun.security.util.BitArray
sun.security.util.ByteArrayLexOrder
@@ -2784,6 +2810,7 @@
sun.security.util.MemoryCache$SoftCacheEntry
sun.security.util.MemoryCache
sun.security.util.ObjectIdentifier
+sun.security.util.ResourcesMgr$1
sun.security.util.ResourcesMgr
sun.security.util.SecurityConstants
sun.security.util.SignatureFileVerifier
@@ -2901,13 +2928,16 @@
[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.crypto.params.DHParameters;
@@ -2916,6 +2946,7 @@
[Lcom.android.org.kxml2.io.KXmlParser$ValueContext;
[Ldalvik.system.DexPathList$Element;
[Ldalvik.system.DexPathList$NativeLibraryElement;
+[Ljava.io.Closeable;
[Ljava.io.File$PathStatus;
[Ljava.io.File;
[Ljava.io.FileDescriptor;
@@ -2925,6 +2956,7 @@
[Ljava.io.ObjectStreamClass$MemberSignature;
[Ljava.io.ObjectStreamField;
[Ljava.io.Serializable;
+[Ljava.lang.Boolean;
[Ljava.lang.Byte;
[Ljava.lang.CharSequence;
[Ljava.lang.Character$UnicodeBlock;
@@ -2933,6 +2965,7 @@
[Ljava.lang.ClassLoader;
[Ljava.lang.Comparable;
[Ljava.lang.Daemons$Daemon;
+[Ljava.lang.Double;
[Ljava.lang.Enum;
[Ljava.lang.Float;
[Ljava.lang.Integer;
@@ -2989,7 +3022,9 @@
[Ljava.security.ProtectionDomain;
[Ljava.security.Provider;
[Ljava.security.cert.CRLReason;
+[Ljava.security.cert.CertPathValidatorException$BasicReason;
[Ljava.security.cert.Certificate;
+[Ljava.security.cert.PKIXReason;
[Ljava.security.cert.PKIXRevocationChecker$Option;
[Ljava.security.cert.X509CRL;
[Ljava.security.cert.X509Certificate;
@@ -3056,9 +3091,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 +3124,7 @@
[Z
[[B
[[C
+[[D
[[F
[[I
[[J
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index d2f5aa6..e672367 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -203,6 +203,8 @@
VLOG(jit) << "Compilation of " << method->PrettyMethod() << " took "
<< PrettyDuration(UsToNs(duration_us));
runtime->GetMetrics()->JitMethodCompileCount()->AddOne();
+ runtime->GetMetrics()->JitMethodCompileTotalTimeDelta()->Add(duration_us);
+ runtime->GetMetrics()->JitMethodCompileCountDelta()->AddOne();
}
// Trim maps to reduce memory usage.
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index 6d856da..8432be5 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -36,36 +36,62 @@
#pragma clang diagnostic error "-Wconversion"
// See README.md in this directory for how to define metrics.
-#define ART_METRICS(METRIC) \
- METRIC(ClassLoadingTotalTime, MetricsCounter) \
- METRIC(ClassVerificationTotalTime, MetricsCounter) \
- METRIC(ClassVerificationCount, MetricsCounter) \
- METRIC(WorldStopTimeDuringGCAvg, MetricsAverage) \
- METRIC(YoungGcCount, MetricsCounter) \
- METRIC(FullGcCount, MetricsCounter) \
- METRIC(TotalBytesAllocated, MetricsCounter) \
- METRIC(TotalGcCollectionTime, MetricsCounter) \
- METRIC(YoungGcThroughputAvg, MetricsAverage) \
- METRIC(FullGcThroughputAvg, MetricsAverage) \
- METRIC(YoungGcTracingThroughputAvg, MetricsAverage) \
- METRIC(FullGcTracingThroughputAvg, MetricsAverage) \
- METRIC(JitMethodCompileTotalTime, MetricsCounter) \
- METRIC(JitMethodCompileCount, MetricsCounter) \
- METRIC(YoungGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
- METRIC(FullGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
- METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000) \
- METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000) \
- METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
- METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
- METRIC(GcWorldStopTime, MetricsCounter) \
- METRIC(GcWorldStopCount, MetricsCounter) \
- METRIC(YoungGcScannedBytes, MetricsCounter) \
- METRIC(YoungGcFreedBytes, MetricsCounter) \
- METRIC(YoungGcDuration, MetricsCounter) \
- METRIC(FullGcScannedBytes, MetricsCounter) \
- METRIC(FullGcFreedBytes, MetricsCounter) \
+
+// Metrics reported as Event Metrics.
+#define ART_EVENT_METRICS(METRIC) \
+ METRIC(ClassLoadingTotalTime, MetricsCounter) \
+ METRIC(ClassVerificationTotalTime, MetricsCounter) \
+ METRIC(ClassVerificationCount, MetricsCounter) \
+ METRIC(WorldStopTimeDuringGCAvg, MetricsAverage) \
+ METRIC(YoungGcCount, MetricsCounter) \
+ METRIC(FullGcCount, MetricsCounter) \
+ METRIC(TotalBytesAllocated, MetricsCounter) \
+ METRIC(TotalGcCollectionTime, MetricsCounter) \
+ METRIC(YoungGcThroughputAvg, MetricsAverage) \
+ METRIC(FullGcThroughputAvg, MetricsAverage) \
+ METRIC(YoungGcTracingThroughputAvg, MetricsAverage) \
+ METRIC(FullGcTracingThroughputAvg, MetricsAverage) \
+ METRIC(JitMethodCompileTotalTime, MetricsCounter) \
+ METRIC(JitMethodCompileCount, MetricsCounter) \
+ METRIC(YoungGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
+ METRIC(FullGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
+ METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000) \
+ METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000) \
+ METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
+ METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
+ METRIC(GcWorldStopTime, MetricsCounter) \
+ METRIC(GcWorldStopCount, MetricsCounter) \
+ METRIC(YoungGcScannedBytes, MetricsCounter) \
+ METRIC(YoungGcFreedBytes, MetricsCounter) \
+ METRIC(YoungGcDuration, MetricsCounter) \
+ METRIC(FullGcScannedBytes, MetricsCounter) \
+ METRIC(FullGcFreedBytes, MetricsCounter) \
METRIC(FullGcDuration, MetricsCounter)
+// Increasing counter metrics, reported as Value Metrics in delta increments.
+#define ART_VALUE_METRICS(METRIC) \
+ METRIC(GcWorldStopTimeDelta, MetricsDeltaCounter) \
+ METRIC(GcWorldStopCountDelta, MetricsDeltaCounter) \
+ METRIC(YoungGcScannedBytesDelta, MetricsDeltaCounter) \
+ METRIC(YoungGcFreedBytesDelta, MetricsDeltaCounter) \
+ METRIC(YoungGcDurationDelta, MetricsDeltaCounter) \
+ METRIC(FullGcScannedBytesDelta, MetricsDeltaCounter) \
+ METRIC(FullGcFreedBytesDelta, MetricsDeltaCounter) \
+ METRIC(FullGcDurationDelta, MetricsDeltaCounter) \
+ METRIC(JitMethodCompileTotalTimeDelta, MetricsDeltaCounter) \
+ METRIC(JitMethodCompileCountDelta, MetricsDeltaCounter) \
+ METRIC(ClassVerificationTotalTimeDelta, MetricsDeltaCounter) \
+ METRIC(ClassVerificationCountDelta, MetricsDeltaCounter) \
+ METRIC(ClassLoadingTotalTimeDelta, MetricsDeltaCounter) \
+ METRIC(TotalBytesAllocatedDelta, MetricsDeltaCounter) \
+ METRIC(TotalGcCollectionTimeDelta, MetricsDeltaCounter) \
+ METRIC(YoungGcCountDelta, MetricsDeltaCounter) \
+ METRIC(FullGcCountDelta, MetricsDeltaCounter)
+
+#define ART_METRICS(METRIC) \
+ ART_EVENT_METRICS(METRIC) \
+ ART_VALUE_METRICS(METRIC)
+
// A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS
// and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be
// challenging to read. The alternative was to require a lot of boilerplate code for each new metric
@@ -243,6 +269,8 @@
template <DatumId counter_type, typename T>
friend class MetricsCounter;
+ template <DatumId counter_type, typename T>
+ friend class MetricsDeltaCounter;
template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
friend class MetricsHistogram;
template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
@@ -274,13 +302,14 @@
void AddOne() { Add(1u); }
void Add(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
- void Report(MetricsBackend* backend) const { backend->ReportCounter(counter_type, Value()); }
-
- protected:
- void Reset() {
- value_ = 0;
+ void Report(const std::vector<MetricsBackend*>& backends) const {
+ for (MetricsBackend* backend : backends) {
+ backend->ReportCounter(counter_type, Value());
+ }
}
+ protected:
+ void Reset() { value_ = 0; }
value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
private:
@@ -317,11 +346,14 @@
count_.fetch_add(1, std::memory_order::memory_order_release);
}
- void Report(MetricsBackend* backend) const {
+ void Report(const std::vector<MetricsBackend*>& backends) const {
+ count_t value = MetricsCounter<datum_id, value_t>::Value();
count_t count = count_.load(std::memory_order::memory_order_acquire);
- backend->ReportCounter(datum_id,
- // Avoid divide-by-0.
- count != 0 ? MetricsCounter<datum_id, value_t>::Value() / count : 0);
+ // Avoid divide-by-0.
+ count_t average_value = count != 0 ? value / count : 0;
+ for (MetricsBackend* backend : backends) {
+ backend->ReportCounter(datum_id, average_value);
+ }
}
protected:
@@ -337,6 +369,40 @@
friend class ArtMetrics;
};
+template <DatumId datum_id, typename T = uint64_t>
+class MetricsDeltaCounter : public MetricsBase<T> {
+ public:
+ using value_t = T;
+
+ explicit constexpr MetricsDeltaCounter(uint64_t value = 0) : value_{value} {
+ // Ensure we do not have any unnecessary data in this class.
+ // Adding intptr_t to accommodate vtable, and rounding up to incorporate
+ // padding.
+ static_assert(RoundUp(sizeof(*this), sizeof(uint64_t)) ==
+ RoundUp(sizeof(intptr_t) + sizeof(value_t), sizeof(uint64_t)));
+ }
+
+ void Add(value_t value) override {
+ value_.fetch_add(value, std::memory_order::memory_order_relaxed);
+ }
+ void AddOne() { Add(1u); }
+
+ void ReportAndReset(const std::vector<MetricsBackend*>& backends) {
+ value_t value = value_.exchange(0, std::memory_order::memory_order_relaxed);
+ for (MetricsBackend* backend : backends) {
+ backend->ReportCounter(datum_id, value);
+ }
+ }
+
+ void Reset() { value_ = 0; }
+
+ private:
+ std::atomic<value_t> value_;
+ static_assert(std::atomic<value_t>::is_always_lock_free);
+
+ friend class ArtMetrics;
+};
+
template <DatumId histogram_type_,
size_t num_buckets_,
int64_t minimum_value_,
@@ -361,8 +427,10 @@
buckets_[i].fetch_add(1u, std::memory_order::memory_order_relaxed);
}
- void Report(MetricsBackend* backend) const {
- backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
+ void Report(const std::vector<MetricsBackend*>& backends) const {
+ for (MetricsBackend* backend : backends) {
+ backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
+ }
}
protected:
@@ -639,8 +707,8 @@
public:
ArtMetrics();
- void ReportAllMetrics(MetricsBackend* backend) const;
- void DumpForSigQuit(std::ostream& os) const;
+ void ReportAllMetricsAndResetValueMetrics(const std::vector<MetricsBackend*>& backends);
+ void DumpForSigQuit(std::ostream& os);
// Resets all metrics to their initial value. This is intended to be used after forking from the
// zygote so we don't attribute parent values to the child process.
diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc
index 025f5eb..2732088 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -65,32 +65,40 @@
{
}
-void ArtMetrics::ReportAllMetrics(MetricsBackend* backend) const {
- backend->BeginReport(MilliTime() - beginning_timestamp_);
+void ArtMetrics::ReportAllMetricsAndResetValueMetrics(
+ const std::vector<MetricsBackend*>& backends) {
+ for (auto& backend : backends) {
+ backend->BeginReport(MilliTime() - beginning_timestamp_);
+ }
-#define ART_METRIC(name, Kind, ...) name()->Report(backend);
- ART_METRICS(ART_METRIC)
-#undef ART_METRIC
+#define REPORT_METRIC(name, Kind, ...) name()->Report(backends);
+ ART_EVENT_METRICS(REPORT_METRIC)
+#undef REPORT_METRIC
- backend->EndReport();
+#define REPORT_METRIC(name, Kind, ...) name()->ReportAndReset(backends);
+ ART_VALUE_METRICS(REPORT_METRIC)
+#undef REPORT_METRIC
+
+ for (auto& backend : backends) {
+ backend->EndReport();
+ }
}
-void ArtMetrics::DumpForSigQuit(std::ostream& os) const {
+void ArtMetrics::DumpForSigQuit(std::ostream& os) {
StringBackend backend(std::make_unique<TextFormatter>());
- ReportAllMetrics(&backend);
+ ReportAllMetricsAndResetValueMetrics({&backend});
os << backend.GetAndResetBuffer();
}
void ArtMetrics::Reset() {
beginning_timestamp_ = MilliTime();
-#define ART_METRIC(name, kind, ...) name##_.Reset();
- ART_METRICS(ART_METRIC);
-#undef ART_METRIC
+#define RESET_METRIC(name, ...) name##_.Reset();
+ ART_METRICS(RESET_METRIC)
+#undef RESET_METRIC
}
StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter)
- : formatter_(std::move(formatter))
-{}
+ : formatter_(std::move(formatter)) {}
std::string StringBackend::GetAndResetBuffer() {
return formatter_->GetAndResetBuffer();
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index fd7124b..dff90b1 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -16,6 +16,7 @@
#include "metrics.h"
+#include "base/macros.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "metrics_test.h"
@@ -232,7 +233,7 @@
bool found_histogram_{false};
} backend;
- metrics.ReportAllMetrics(&backend);
+ metrics.ReportAllMetricsAndResetValueMetrics({&backend});
}
TEST_F(MetricsTest, HistogramTimer) {
@@ -251,7 +252,7 @@
ArtMetrics metrics;
StringBackend backend(std::make_unique<TextFormatter>());
- metrics.ReportAllMetrics(&backend);
+ metrics.ReportAllMetricsAndResetValueMetrics({&backend});
// Make sure the resulting string lists all the metrics.
const std::string result = backend.GetAndResetBuffer();
@@ -271,11 +272,14 @@
class NonZeroBackend : public TestBackendBase {
public:
- void ReportCounter(DatumId, uint64_t value) override {
+ void ReportCounter(DatumId counter_type [[gnu::unused]], uint64_t value) override {
EXPECT_NE(value, 0u);
}
- void ReportHistogram(DatumId, int64_t, int64_t, const std::vector<uint32_t>& buckets) override {
+ void ReportHistogram(DatumId histogram_type [[gnu::unused]],
+ int64_t minimum_value [[gnu::unused]],
+ int64_t maximum_value [[gnu::unused]],
+ const std::vector<uint32_t>& buckets) override {
bool nonzero = false;
for (const auto value : buckets) {
nonzero |= (value != 0u);
@@ -285,25 +289,97 @@
} non_zero_backend;
// Make sure the metrics all have a nonzero value.
- metrics.ReportAllMetrics(&non_zero_backend);
+ metrics.ReportAllMetricsAndResetValueMetrics({&non_zero_backend});
// Reset the metrics and make sure they are all zero again
metrics.Reset();
class ZeroBackend : public TestBackendBase {
public:
- void ReportCounter(DatumId, uint64_t value) override {
+ void ReportCounter(DatumId counter_type [[gnu::unused]], uint64_t value) override {
EXPECT_EQ(value, 0u);
}
- void ReportHistogram(DatumId, int64_t, int64_t, const std::vector<uint32_t>& buckets) override {
+ void ReportHistogram(DatumId histogram_type [[gnu::unused]],
+ int64_t minimum_value [[gnu::unused]],
+ int64_t maximum_value [[gnu::unused]],
+ const std::vector<uint32_t>& buckets) override {
for (const auto value : buckets) {
EXPECT_EQ(value, 0u);
}
}
} zero_backend;
- metrics.ReportAllMetrics(&zero_backend);
+ metrics.ReportAllMetricsAndResetValueMetrics({&zero_backend});
+}
+
+TEST_F(MetricsTest, KeepEventMetricsResetValueMetricsAfterReporting) {
+ ArtMetrics metrics;
+
+ // Add something to each of the metrics.
+#define METRIC(name, type, ...) metrics.name()->Add(42);
+ ART_METRICS(METRIC)
+#undef METRIC
+
+ class FirstBackend : public TestBackendBase {
+ public:
+ void ReportCounter(DatumId counter_type [[gnu::unused]], uint64_t value) override {
+ EXPECT_NE(value, 0u);
+ }
+
+ void ReportHistogram(DatumId histogram_type [[gnu::unused]],
+ int64_t minimum_value [[gnu::unused]],
+ int64_t maximum_value [[gnu::unused]],
+ const std::vector<uint32_t>& buckets) override {
+ EXPECT_NE(buckets[0], 0u) << "Bucket 0 should have a non-zero value";
+ for (size_t i = 1; i < buckets.size(); i++) {
+ EXPECT_EQ(buckets[i], 0u) << "Bucket " << i << " should have a zero value";
+ }
+ }
+ } first_backend;
+
+ // Make sure the metrics all have a nonzero value, and they are not reset between backends.
+ metrics.ReportAllMetricsAndResetValueMetrics({&first_backend, &first_backend});
+
+ // After reporting, the Value Metrics should have been reset.
+ class SecondBackend : public TestBackendBase {
+ public:
+ void ReportCounter(DatumId datum_id, uint64_t value) override {
+ switch (datum_id) {
+ // Value metrics - expected to have been reset
+#define CHECK_METRIC(name, ...) case DatumId::k##name:
+ ART_VALUE_METRICS(CHECK_METRIC)
+#undef CHECK_METRIC
+ EXPECT_EQ(value, 0u);
+ return;
+
+ // Event metrics - expected to have retained their previous value
+#define CHECK_METRIC(name, ...) case DatumId::k##name:
+ ART_EVENT_METRICS(CHECK_METRIC)
+#undef CHECK_METRIC
+ EXPECT_NE(value, 0u);
+ return;
+
+ default:
+ // unknown metric - it should not be possible to reach this path
+ FAIL();
+ UNREACHABLE();
+ }
+ }
+
+ // All histograms are event metrics.
+ void ReportHistogram(DatumId histogram_type [[gnu::unused]],
+ int64_t minimum_value [[gnu::unused]],
+ int64_t maximum_value [[gnu::unused]],
+ const std::vector<uint32_t>& buckets) override {
+ EXPECT_NE(buckets[0], 0u) << "Bucket 0 should have a non-zero value";
+ for (size_t i = 1; i < buckets.size(); i++) {
+ EXPECT_EQ(buckets[i], 0u) << "Bucket " << i << " should have a zero value";
+ }
+ }
+ } second_backend;
+
+ metrics.ReportAllMetricsAndResetValueMetrics({&second_backend});
}
TEST(TextFormatterTest, ReportMetrics_WithBuckets) {
diff --git a/libartbase/base/metrics/metrics_test.h b/libartbase/base/metrics/metrics_test.h
index 3e8b42a..07b4e9d 100644
--- a/libartbase/base/metrics/metrics_test.h
+++ b/libartbase/base/metrics/metrics_test.h
@@ -58,7 +58,7 @@
uint64_t* counter_value_;
} backend{&counter_value};
- counter.Report(&backend);
+ counter.Report({&backend});
return counter_value;
}
@@ -75,7 +75,7 @@
std::vector<uint32_t>* buckets_;
} backend{&buckets};
- histogram.Report(&backend);
+ histogram.Report({&backend});
return buckets;
}
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index f311f09..b32dcec 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -186,6 +186,19 @@
// Returns the number of threads running.
int GetTaskCount();
+// Returns a vector holding the raw pointer targets of the elements of "unique_pointers", a vector
+// of smart pointers. This does not affect the target objects e.g. by incrementing ref-counts; it's
+// a raw pointer read.
+template <typename T>
+static std::vector<T*> ToRawPointers(const std::vector<std::unique_ptr<T>>& unique_pointers) {
+ std::vector<T*> raw_pointers;
+ raw_pointers.reserve(unique_pointers.size());
+ for (const std::unique_ptr<T>& p : unique_pointers) {
+ raw_pointers.push_back(p.get());
+ }
+ return raw_pointers;
+}
+
} // namespace art
#endif // ART_LIBARTBASE_BASE_UTILS_H_
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 97f928e..69f42fb 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -66,7 +66,10 @@
// 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: [
+ "framework-system-server-module-defaults",
+ "framework-system-server-module-optimize-defaults",
+ ],
permitted_packages: ["com.android.server.art"],
visibility: [
"//art:__subpackages__",
@@ -81,9 +84,69 @@
srcs: [
"java/**/*.java",
],
+ libs: [
+ "androidx.annotation_annotation",
+ "auto_value_annotations",
+ // TODO(b/256866172): Transitive dependency, for r8 only.
+ "framework-statsd.stubs.module_lib",
+ // TODO(b/256866172): Transitive dependency, for r8 only. This module
+ // always refers to the jar in prebuilts/sdk. We can't use
+ // "framework-connectivity.stubs.module_lib" here because it's not
+ // available on master-art.
+ "sdk_module-lib_current_framework-connectivity",
+ ],
static_libs: [
+ "art-statslog-art-java",
+ "artd-aidl-java",
+ "modules-utils-shell-command-handler",
+ "service-art-proto-java",
+ ],
+ plugins: [
+ "auto_value_plugin",
],
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: [
+ ":art-statslog-art-java-gen",
+ ],
+ libs: [
+ "framework-statsd.stubs.module_lib",
+ ],
+ 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 +189,30 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.runner",
- "mockito-target-minus-junit4",
+ "artd-aidl-java",
+ "framework-annotations-lib",
+ // We need ExtendedMockito to mock static methods.
+ "mockito-target-extended-minus-junit4",
"service-art.impl",
+ // 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..f3dfb38 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -2,7 +2,166 @@
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 addOptimizePackageDoneCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.OptimizePackageDoneCallback);
+ method public void cancelBackgroundDexoptJob();
+ method public void clearOptimizePackagesCallback();
+ method public void clearScheduleBackgroundDexoptJobCallback();
+ method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+ method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, int);
+ method @NonNull public com.android.server.art.model.OptimizationStatus getOptimizationStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
+ method @NonNull public com.android.server.art.model.OptimizationStatus getOptimizationStatus(@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 @NonNull public com.android.server.art.model.OptimizeResult optimizePackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.OptimizeParams);
+ method @NonNull public com.android.server.art.model.OptimizeResult optimizePackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.OptimizeParams, @NonNull android.os.CancellationSignal);
+ method public void removeOptimizePackageDoneCallback(@NonNull com.android.server.art.ArtManagerLocal.OptimizePackageDoneCallback);
+ method public int scheduleBackgroundDexoptJob();
+ method public void setOptimizePackagesCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.OptimizePackagesCallback);
+ 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.OptimizePackageDoneCallback {
+ method public void onOptimizePackageDone(@NonNull com.android.server.art.model.OptimizeResult);
+ }
+
+ public static interface ArtManagerLocal.OptimizePackagesCallback {
+ method public void onOverrideBatchOptimizeParams(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull com.android.server.art.model.BatchOptimizeParams.Builder);
+ }
+
+ public static interface ArtManagerLocal.ScheduleBackgroundDexoptJobCallback {
+ method public void onOverrideJobInfo(@NonNull android.app.job.JobInfo.Builder);
+ }
+
+ public static class ArtManagerLocal.SnapshotProfileException extends java.lang.Exception {
+ ctor public ArtManagerLocal.SnapshotProfileException(@NonNull Throwable);
+ }
+
+ public class ArtModuleServiceInitializer {
+ method public static void setArtModuleServiceManager(@NonNull android.os.ArtModuleServiceManager);
+ }
+
+ public class DexUseManagerLocal {
+ method @NonNull public static com.android.server.art.DexUseManagerLocal createInstance();
+ method public void notifyDexContainersLoaded(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>);
+ }
+
+ public class ReasonMapping {
+ field public static final String REASON_BG_DEXOPT = "bg-dexopt";
+ 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 defaultDeleteFlags();
+ method public static int defaultGetStatusFlags();
+ method public static int defaultOptimizeFlags();
+ 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 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 BatchOptimizeParams {
+ method @NonNull public abstract com.android.server.art.model.OptimizeParams getOptimizeParams();
+ method @NonNull public abstract java.util.List<java.lang.String> getPackages();
+ }
+
+ public static final class BatchOptimizeParams.Builder {
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams build();
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams.Builder setOptimizeParams(@NonNull com.android.server.art.model.OptimizeParams);
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams.Builder setPackages(@NonNull java.util.List<java.lang.String>);
+ }
+
+ public class DeleteResult {
+ method public long getFreedBytes();
+ }
+
+ public abstract class OptimizationStatus {
+ method @NonNull public abstract java.util.List<com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus> getDexContainerFileOptimizationStatuses();
+ }
+
+ public abstract static class OptimizationStatus.DexContainerFileOptimizationStatus {
+ 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();
+ }
+
+ public class OptimizeParams {
+ 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 OptimizeParams.Builder {
+ ctor public OptimizeParams.Builder(@NonNull String);
+ ctor public OptimizeParams.Builder(@NonNull String, int);
+ method @NonNull public com.android.server.art.model.OptimizeParams build();
+ method @NonNull public com.android.server.art.model.OptimizeParams.Builder setCompilerFilter(@NonNull String);
+ method @NonNull public com.android.server.art.model.OptimizeParams.Builder setFlags(int);
+ method @NonNull public com.android.server.art.model.OptimizeParams.Builder setFlags(int, int);
+ method @NonNull public com.android.server.art.model.OptimizeParams.Builder setPriorityClass(int);
+ method @NonNull public com.android.server.art.model.OptimizeParams.Builder setSplitName(@Nullable String);
+ }
+
+ public class OptimizeResult {
+ method public int getFinalStatus();
+ method @NonNull public java.util.List<com.android.server.art.model.OptimizeResult.PackageOptimizeResult> getPackageOptimizeResults();
+ method @NonNull public String getReason();
+ method @NonNull public String getRequestedCompilerFilter();
+ field public static final int OPTIMIZE_CANCELLED = 40; // 0x28
+ field public static final int OPTIMIZE_FAILED = 30; // 0x1e
+ field public static final int OPTIMIZE_PERFORMED = 20; // 0x14
+ field public static final int OPTIMIZE_SKIPPED = 10; // 0xa
+ }
+
+ public static class OptimizeResult.DexContainerFileOptimizeResult {
+ method @NonNull public String getAbi();
+ method @NonNull public String getActualCompilerFilter();
+ method public long getDex2oatCpuTimeMillis();
+ method public long getDex2oatWallTimeMillis();
+ method @NonNull public String getDexContainerFile();
+ method public long getSizeBeforeBytes();
+ method public long getSizeBytes();
+ method public int getStatus();
+ method public boolean isPrimaryAbi();
+ }
+
+ public static class OptimizeResult.PackageOptimizeResult {
+ method @NonNull public java.util.List<com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult> getDexContainerFileOptimizeResults();
+ method @NonNull public String getPackageName();
+ method public int getStatus();
}
}
diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt
index c7d39e6..54ff0a1 100644
--- a/libartservice/service/jarjar-rules.txt
+++ b/libartservice/service/jarjar-rules.txt
@@ -1,2 +1,3 @@
# Repackages static libraries to make them private to ART Services.
rule com.android.modules.utils.** com.android.server.art.jarjar.@0
+rule com.google.protobuf.** com.android.server.art.jarjar.@0
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
new file mode 100644
index 0000000..f38000d
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.ProfilePath.PrebuiltProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryCurProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryRefProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryCurProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryRefProfilePath;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.ProfilePath.WritableProfilePath;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** @hide */
+public final class AidlUtils {
+ private AidlUtils() {}
+
+ @NonNull
+ public static ArtifactsPath buildArtifactsPath(
+ @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) {
+ var artifactsPath = new ArtifactsPath();
+ artifactsPath.dexPath = dexPath;
+ artifactsPath.isa = isa;
+ artifactsPath.isInDalvikCache = isInDalvikCache;
+ return artifactsPath;
+ }
+
+ @NonNull
+ public static FsPermission buildFsPermission(
+ int uid, int gid, boolean isOtherReadable, boolean isOtherExecutable) {
+ var fsPermission = new FsPermission();
+ fsPermission.uid = uid;
+ fsPermission.gid = gid;
+ fsPermission.isOtherReadable = isOtherReadable;
+ fsPermission.isOtherExecutable = isOtherExecutable;
+ return fsPermission;
+ }
+
+ @NonNull
+ public static FsPermission buildFsPermission(int uid, int gid, boolean isOtherReadable) {
+ return buildFsPermission(uid, gid, isOtherReadable, false /* isOtherExecutable */);
+ }
+
+ @NonNull
+ public static DexMetadataPath buildDexMetadataPath(@NonNull String dexPath) {
+ var dexMetadataPath = new DexMetadataPath();
+ dexMetadataPath.dexPath = dexPath;
+ return dexMetadataPath;
+ }
+
+ @NonNull
+ public static PermissionSettings buildPermissionSettings(@NonNull FsPermission dirFsPermission,
+ @NonNull FsPermission fileFsPermission, @Nullable SeContext seContext) {
+ var permissionSettings = new PermissionSettings();
+ permissionSettings.dirFsPermission = dirFsPermission;
+ permissionSettings.fileFsPermission = fileFsPermission;
+ permissionSettings.seContext = seContext;
+ return permissionSettings;
+ }
+
+ @NonNull
+ public static OutputArtifacts buildOutputArtifacts(@NonNull String dexPath, @NonNull String isa,
+ boolean isInDalvikCache, @NonNull PermissionSettings permissionSettings) {
+ var outputArtifacts = new OutputArtifacts();
+ outputArtifacts.artifactsPath = buildArtifactsPath(dexPath, isa, isInDalvikCache);
+ outputArtifacts.permissionSettings = permissionSettings;
+ return outputArtifacts;
+ }
+
+ @NonNull
+ public static PrimaryRefProfilePath buildPrimaryRefProfilePath(
+ @NonNull String packageName, @NonNull String profileName) {
+ var primaryRefProfilePath = new PrimaryRefProfilePath();
+ primaryRefProfilePath.packageName = packageName;
+ primaryRefProfilePath.profileName = profileName;
+ return primaryRefProfilePath;
+ }
+
+ @NonNull
+ public static SecondaryRefProfilePath buildSecondaryRefProfilePath(@NonNull String dexPath) {
+ var secondaryRefProfilePath = new SecondaryRefProfilePath();
+ secondaryRefProfilePath.dexPath = dexPath;
+ return secondaryRefProfilePath;
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForPrimaryRef(
+ @NonNull String packageName, @NonNull String profileName) {
+ return ProfilePath.primaryRefProfilePath(
+ buildPrimaryRefProfilePath(packageName, profileName));
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForPrebuilt(@NonNull String dexPath) {
+ var prebuiltProfilePath = new PrebuiltProfilePath();
+ prebuiltProfilePath.dexPath = dexPath;
+ return ProfilePath.prebuiltProfilePath(prebuiltProfilePath);
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForDm(@NonNull String dexPath) {
+ return ProfilePath.dexMetadataPath(buildDexMetadataPath(dexPath));
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForPrimaryCur(
+ int userId, @NonNull String packageName, @NonNull String profileName) {
+ var primaryCurProfilePath = new PrimaryCurProfilePath();
+ primaryCurProfilePath.userId = userId;
+ primaryCurProfilePath.packageName = packageName;
+ primaryCurProfilePath.profileName = profileName;
+ return ProfilePath.primaryCurProfilePath(primaryCurProfilePath);
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForSecondaryRef(@NonNull String dexPath) {
+ return ProfilePath.secondaryRefProfilePath(buildSecondaryRefProfilePath(dexPath));
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForSecondaryCur(@NonNull String dexPath) {
+ var secondaryCurProfilePath = new SecondaryCurProfilePath();
+ secondaryCurProfilePath.dexPath = dexPath;
+ return ProfilePath.secondaryCurProfilePath(secondaryCurProfilePath);
+ }
+
+ @NonNull
+ private static OutputProfile buildOutputProfile(
+ @NonNull WritableProfilePath finalPath, int uid, int gid, boolean isPublic) {
+ var outputProfile = new OutputProfile();
+ outputProfile.profilePath = new TmpProfilePath();
+ outputProfile.profilePath.finalPath = finalPath;
+ outputProfile.profilePath.id = ""; // Will be filled by artd.
+ outputProfile.profilePath.tmpPath = ""; // Will be filled by artd.
+ outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic);
+ return outputProfile;
+ }
+
+ @NonNull
+ public static OutputProfile buildOutputProfileForPrimary(@NonNull String packageName,
+ @NonNull String profileName, int uid, int gid, boolean isPublic) {
+ return buildOutputProfile(WritableProfilePath.forPrimary(
+ buildPrimaryRefProfilePath(packageName, profileName)),
+ uid, gid, isPublic);
+ }
+
+ @NonNull
+ public static OutputProfile buildOutputProfileForSecondary(
+ @NonNull String dexPath, int uid, int gid, boolean isPublic) {
+ return buildOutputProfile(
+ WritableProfilePath.forSecondary(buildSecondaryRefProfilePath(dexPath)), uid, gid,
+ isPublic);
+ }
+
+ @NonNull
+ public static SeContext buildSeContext(@NonNull String seInfo, int uid) {
+ var seContext = new SeContext();
+ seContext.seInfo = seInfo;
+ seContext.uid = uid;
+ return seContext;
+ }
+
+ @NonNull
+ public static String toString(@NonNull PrimaryRefProfilePath profile) {
+ return String.format(
+ "[packageName = %s, profileName = %s]", profile.packageName, profile.profileName);
+ }
+
+ @NonNull
+ public static String toString(@NonNull SecondaryRefProfilePath profile) {
+ return String.format("[dexPath = %s]", profile.dexPath);
+ }
+
+ @NonNull
+ public static String toString(@NonNull WritableProfilePath profile) {
+ switch (profile.getTag()) {
+ case WritableProfilePath.forPrimary:
+ return toString(profile.getForPrimary());
+ case WritableProfilePath.forSecondary:
+ return toString(profile.getForSecondary());
+ default:
+ throw new IllegalStateException(
+ "Unknown WritableProfilePath tag " + profile.getTag());
+ }
+ }
+
+ @NonNull
+ public static String toString(@NonNull ProfilePath profile) {
+ switch (profile.getTag()) {
+ case ProfilePath.primaryRefProfilePath:
+ return toString(profile.getPrimaryRefProfilePath());
+ case ProfilePath.secondaryRefProfilePath:
+ return toString(profile.getSecondaryRefProfilePath());
+ default:
+ throw new UnsupportedOperationException(
+ "Only reference profile paths are supported to be converted to string, got "
+ + profile.getTag());
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 64aec7b..7514543 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,16 +16,744 @@
package com.android.server.art;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.ReasonMapping.BatchOptimizeReason;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.DeleteFlags;
+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.OptimizationStatus.DexContainerFileOptimizationStatus;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.job.JobInfo;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.BatchOptimizeParams;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.OptimizationStatus;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
/**
* This class provides a system API for functionality provided by the ART module.
*
+ * Note: Although this class is the entry point of ART services, this class is not a {@link
+ * SystemService}, and it does not publish a binder. Instead, it is a module loaded by the
+ * system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry}
+ * specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal}
+ * for consistency.
+ *
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public final class ArtManagerLocal {
private static final String TAG = "ArtService";
+ private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
+ "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
- public ArtManagerLocal() {}
+ @NonNull private final Injector mInjector;
+
+ @Deprecated
+ public ArtManagerLocal() {
+ mInjector = new Injector(this, null /* context */);
+ }
+
+ public ArtManagerLocal(@NonNull Context context) {
+ mInjector = new Injector(this, context);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public ArtManagerLocal(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
+ * Handles `cmd package art` sub-command.
+ *
+ * For debugging purposes only. Intentionally enforces root access to limit the usage.
+ *
+ * Note: This method is not an override of {@link Binder#handleShellCommand} because ART
+ * services does not publish a binder. Instead, it handles the `art` sub-command 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#onHelp()
+ */
+ public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return new ArtShellCommand(
+ this, mInjector.getPackageManagerLocal(), mInjector.getDexUseManager())
+ .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
+ err.getFileDescriptor(), args);
+ }
+
+ /**
+ * Deletes optimized artifacts of a package.
+ *
+ * Uses the default flags ({@link ArtFlags#defaultDeleteFlags()}).
+ *
+ * @throws IllegalArgumentException if the package is not found or the flags are illegal
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error).
+ */
+ @NonNull
+ public DeleteResult deleteOptimizedArtifacts(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+ return deleteOptimizedArtifacts(snapshot, packageName, ArtFlags.defaultDeleteFlags());
+ }
+
+ /**
+ * Same as above, but allows to specify flags.
+ *
+ * @see #deleteOptimizedArtifacts(PackageManagerLocal.FilteredSnapshot, String)
+ */
+ @NonNull
+ public DeleteResult deleteOptimizedArtifacts(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+ @DeleteFlags int flags) {
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+ && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
+ throw new IllegalArgumentException("Nothing to delete");
+ }
+
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+ try {
+ long freedBytes = 0;
+
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
+ for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+ if (!dexInfo.hasCode()) {
+ continue;
+ }
+ for (Abi abi : Utils.getAllAbis(pkgState)) {
+ freedBytes +=
+ mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
+ dexInfo.dexPath(), abi.isa(), isInDalvikCache));
+ }
+ }
+ }
+
+ if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ // TODO(jiakaiz): Implement this.
+ throw new UnsupportedOperationException(
+ "Deleting artifacts of secondary dex'es is not implemented yet");
+ }
+
+ return new DeleteResult(freedBytes);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ /**
+ * Returns the optimization status of a package.
+ *
+ * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}).
+ *
+ * @throws IllegalArgumentException if the package is not found or the flags are illegal
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error).
+ */
+ @NonNull
+ public OptimizationStatus getOptimizationStatus(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+ return getOptimizationStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
+ }
+
+ /**
+ * Same as above, but allows to specify flags.
+ *
+ * @see #getOptimizationStatus(PackageManagerLocal.FilteredSnapshot, String)
+ */
+ @NonNull
+ public OptimizationStatus getOptimizationStatus(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+ @GetStatusFlags int flags) {
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+ && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
+ throw new IllegalArgumentException("Nothing to check");
+ }
+
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+ try {
+ List<DexContainerFileOptimizationStatus> statuses = new ArrayList<>();
+
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ for (DetailedPrimaryDexInfo dexInfo :
+ PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+ if (!dexInfo.hasCode()) {
+ continue;
+ }
+ for (Abi abi : Utils.getAllAbis(pkgState)) {
+ try {
+ GetOptimizationStatusResult result =
+ mInjector.getArtd().getOptimizationStatus(dexInfo.dexPath(),
+ abi.isa(), dexInfo.classLoaderContext());
+ statuses.add(
+ DexContainerFileOptimizationStatus.create(dexInfo.dexPath(),
+ abi.isPrimaryAbi(), abi.name(), result.compilerFilter,
+ result.compilationReason, result.locationDebugString));
+ } catch (ServiceSpecificException e) {
+ statuses.add(DexContainerFileOptimizationStatus.create(
+ dexInfo.dexPath(), abi.isPrimaryAbi(), abi.name(), "error",
+ "error", e.getMessage()));
+ }
+ }
+ }
+ }
+
+ if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ // TODO(jiakaiz): Implement this.
+ throw new UnsupportedOperationException(
+ "Getting optimization status of secondary dex'es is not implemented yet");
+ }
+
+ return OptimizationStatus.create(statuses);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ /**
+ * Optimizes 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
+ * #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} are called.
+ *
+ * @throws IllegalArgumentException if the package is not found or the params are illegal
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error).
+ */
+ @NonNull
+ public OptimizeResult optimizePackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull String packageName, @NonNull OptimizeParams params) {
+ var cancellationSignal = new CancellationSignal();
+ return optimizePackage(snapshot, packageName, params, cancellationSignal);
+ }
+
+ /**
+ * Same as above, but supports cancellation.
+ *
+ * @see #optimizePackage(PackageManagerLocal.FilteredSnapshot, String, OptimizeParams)
+ */
+ @NonNull
+ public OptimizeResult optimizePackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull String packageName, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ return mInjector.getDexOptHelper().dexopt(
+ snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
+ }
+
+ /**
+ * Runs batch optimization 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 #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)}.
+ *
+ * The optimization is done in a thread pool. The number of packages being optimized
+ * 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
+ * #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} are called.
+ *
+ * @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
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error), or the callback set by {@link
+ * #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)} provides invalid
+ * params.
+ *
+ * @hide
+ */
+ @NonNull
+ public OptimizeResult optimizePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason,
+ @NonNull CancellationSignal cancellationSignal) {
+ List<String> defaultPackages =
+ Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
+ OptimizeParams defaultOptimizeParams = new OptimizeParams.Builder(reason).build();
+ var builder = new BatchOptimizeParams.Builder(defaultPackages, defaultOptimizeParams);
+ Callback<OptimizePackagesCallback> callback =
+ mInjector.getConfig().getOptimizePackagesCallback();
+ if (callback != null) {
+ Utils.executeAndWait(callback.executor(), () -> {
+ callback.get().onOverrideBatchOptimizeParams(
+ snapshot, reason, defaultPackages, builder);
+ });
+ }
+ BatchOptimizeParams params = builder.build();
+ Utils.check(params.getOptimizeParams().getReason().equals(reason));
+
+ return mInjector.getDexOptHelper().dexopt(snapshot, params.getPackages(),
+ params.getOptimizeParams(), cancellationSignal,
+ Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason)));
+ }
+
+ /**
+ * Overrides the default params for {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). This method is thread-safe.
+ *
+ * This method gives users the opportunity to change the behavior of {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}, which is called by ART
+ * Service automatically during boot / background dexopt.
+ *
+ * If this method is not called, the default list of packages and options determined by {@code
+ * reason} will be used.
+ */
+ public void setOptimizePackagesCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OptimizePackagesCallback callback) {
+ mInjector.getConfig().setOptimizePackagesCallback(executor, callback);
+ }
+
+ /**
+ * Clears the callback set by {@link #setOptimizePackagesCallback(Executor,
+ * OptimizePackagesCallback)}. This method is thread-safe.
+ */
+ public void clearOptimizePackagesCallback() {
+ mInjector.getConfig().clearOptimizePackagesCallback();
+ }
+
+ /**
+ * 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)})
+ * <li>The free storage space is not low.
+ * (see {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)})
+ * </ul>
+ *
+ * When the job is running, the job scheduler cancels the job immediately whenever one of the
+ * constraints above is no longer met, and 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 #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
+ * CancellationSignal)} 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 #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} with the
+ * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+ */
+ public @ScheduleStatus int scheduleBackgroundDexoptJob() {
+ return mInjector.getBackgroundDexOptJob().schedule();
+ }
+
+ /**
+ * Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}.
+ * Does nothing if the job is not scheduled.
+ *
+ * Use this method if you no longer want the system to automatically run dexopt.
+ *
+ * If the job is already started by the job scheduler and is running, it will be cancelled
+ * immediately, and the result sent to the callbacks added by {@link
+ * #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} will contain {@link
+ * OptimizeResult#OPTIMIZE_CANCELLED}. Note that a job started by {@link
+ * #startBackgroundDexoptJob()} will not be cancelled by this method.
+ */
+ public void unscheduleBackgroundDexoptJob() {
+ mInjector.getBackgroundDexOptJob().unschedule();
+ }
+
+ /**
+ * Overrides the configuration of the background dexopt job. This method is thread-safe.
+ */
+ public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+ mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback);
+ }
+
+ /**
+ * Clears the callback set by {@link #setScheduleBackgroundDexoptJobCallback(Executor,
+ * ScheduleBackgroundDexoptJobCallback)}. This method is thread-safe.
+ */
+ public void clearScheduleBackgroundDexoptJobCallback() {
+ mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback();
+ }
+
+ /**
+ * Manually starts a background dexopt job. Does nothing if a job is already started by this
+ * method or by the job scheduler. This method is not blocking.
+ *
+ * Unlike the job started by job scheduler, the job started by this method does not respect
+ * constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be
+ * cancelled when they aren't met.
+ *
+ * See {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
+ * CancellationSignal)} 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 #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} with the
+ * reason {@link ReasonMapping#REASON_BG_DEXOPT}.
+ */
+ public void startBackgroundDexoptJob() {
+ mInjector.getBackgroundDexOptJob().start();
+ }
+
+ /**
+ * Cancels the running background dexopt job started by the job scheduler or by {@link
+ * #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not
+ * blocking.
+ *
+ * The result sent to the callbacks added by {@link #addOptimizePackageDoneCallback(Executor,
+ * OptimizePackageDoneCallback)} will contain {@link OptimizeResult#OPTIMIZE_CANCELLED}.
+ */
+ public void cancelBackgroundDexoptJob() {
+ mInjector.getBackgroundDexOptJob().cancel();
+ }
+
+ /**
+ * Adds a global listener that listens to any result of optimizing 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.
+ *
+ * @throws IllegalStateException if the same callback instance is already added
+ */
+ public void addOptimizePackageDoneCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OptimizePackageDoneCallback callback) {
+ mInjector.getConfig().addOptimizePackageDoneCallback(executor, callback);
+ }
+
+ /**
+ * Removes the listener added by {@link #addOptimizePackageDoneCallback(Executor,
+ * OptimizePackageDoneCallback)}. Does nothing if the callback was not added. This method is
+ * thread-safe.
+ */
+ public void removeOptimizePackageDoneCallback(@NonNull OptimizePackageDoneCallback callback) {
+ mInjector.getConfig().removeOptimizePackageDoneCallback(callback);
+ }
+
+ /**
+ * Snapshots the profile of the given app split. The profile snapshot is the aggregation of all
+ * existing profiles of the app split (all current user profiles and the reference profile).
+ *
+ * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+ * @param packageName the name of the app that owns the profile
+ * @param splitName see {@link AndroidPackageSplit#getName()}
+ * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+ * caller is responsible for closing it. Note that the content may be empty.
+ * @throws IllegalArgumentException if the package or the split is not found
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error).
+ * @throws SnapshotProfileException if the operation encounters an error that the caller should
+ * handle (e.g., an I/O error, a sub-process crash).
+ */
+ @NonNull
+ public ParcelFileDescriptor snapshotAppProfile(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+ @Nullable String splitName) throws SnapshotProfileException {
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
+
+ List<ProfilePath> profiles = new ArrayList<>();
+ profiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+ profiles.addAll(
+ PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
+
+ OutputProfile output = PrimaryDexUtils.buildOutputProfile(
+ pkgState, dexInfo, Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
+
+ return mergeProfilesAndGetFd(
+ profiles, output, List.of(dexInfo.dexPath()), false /* forBootImage */);
+ }
+
+ /**
+ * Snapshots the boot image profile
+ * (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot
+ * is the aggregation of all existing profiles on the device (all current user profiles and
+ * reference profiles) of all apps and the system server filtered by applicable classpaths.
+ *
+ * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+ * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
+ * caller is responsible for closing it. Note that the content may be empty.
+ * @throws IllegalStateException if the operation encounters an error that should never happen
+ * (e.g., an internal logic error).
+ * @throws SnapshotProfileException if the operation encounters an error that the caller should
+ * handle (e.g., an I/O error, a sub-process crash).
+ */
+ @NonNull
+ public ParcelFileDescriptor snapshotBootImageProfile(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot)
+ throws SnapshotProfileException {
+ List<ProfilePath> profiles = new ArrayList<>();
+
+ // System server profiles.
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, Utils.PLATFORM_PACKAGE_NAME);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfo(pkg).get(0);
+ profiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
+ profiles.addAll(
+ PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
+
+ // App profiles.
+ snapshot.forAllPackageStates((appPkgState) -> {
+ // Hibernating apps can still provide useful profile contents, so skip the hibernation
+ // check.
+ if (Utils.canOptimizePackage(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,
+ "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());
+
+ return mergeProfilesAndGetFd(profiles, output, dexPaths, true /* forBootImage */);
+ }
+
+ /**
+ * Should be used by {@link BackgroundDexOptJobService} ONLY.
+ *
+ * @hide
+ */
+ @NonNull
+ BackgroundDexOptJob getBackgroundDexOptJob() {
+ return mInjector.getBackgroundDexOptJob();
+ }
+
+ @NonNull
+ private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason) {
+ var packages = new ArrayList<String>();
+ snapshot.forAllPackageStates((pkgState) -> {
+ if (Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager())) {
+ packages.add(pkgState.getPackageName());
+ }
+ });
+ return packages;
+ }
+
+ @NonNull
+ private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles,
+ @NonNull OutputProfile output, @NonNull List<String> dexPaths, boolean forBootImage)
+ throws SnapshotProfileException {
+ try {
+ var options = new MergeProfileOptions();
+ options.forceMerge = true;
+ options.forBootImage = forBootImage;
+
+ boolean hasContent = false;
+ try {
+ hasContent = mInjector.getArtd().mergeProfiles(
+ profiles, null /* referenceProfile */, output, dexPaths, options);
+ } catch (ServiceSpecificException e) {
+ throw new SnapshotProfileException(e);
+ }
+
+ String path = hasContent ? output.profilePath.tmpPath : "/dev/null";
+ ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(
+ String.format("Failed to open profile snapshot '%s'", path), e);
+ }
+
+ if (hasContent) {
+ // This is done on the open file so that only the FD keeps a reference to its
+ // contents.
+ mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
+ }
+
+ return fd;
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ public interface OptimizePackagesCallback {
+ /**
+ * Mutates {@code builder} to override the default params for {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). It must ignore unknown
+ * reasons because more reasons may be added in the future.
+ *
+ * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
+ * list of packages to optimize.
+ *
+ * If {@code builder.setOptimizeParams} is not called, the default params built from {@code
+ * new OptimizeParams.Builder(reason)} will to used as the params for optimizing each
+ * package.
+ *
+ * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
+ * when {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
+ * CancellationSignal)} is called.
+ */
+ void onOverrideBatchOptimizeParams(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason, @NonNull List<String> defaultPackages,
+ @NonNull BatchOptimizeParams.Builder builder);
+ }
+
+ 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.
+ */
+ void onOverrideJobInfo(@NonNull JobInfo.Builder builder);
+ }
+
+ public interface OptimizePackageDoneCallback {
+ void onOptimizePackageDone(@NonNull OptimizeResult result);
+ }
+
+ /** Represents an error that happens when snapshotting profiles. */
+ public static class SnapshotProfileException extends Exception {
+ public SnapshotProfileException(@NonNull Throwable cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @Nullable private final Context mContext;
+ @Nullable private final PackageManagerLocal mPackageManagerLocal;
+ @Nullable private final Config mConfig;
+ @Nullable private final BackgroundDexOptJob mBgDexOptJob;
+
+ Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
+ mContext = context;
+ if (context != null) {
+ // We only need them on Android U and above, where a context is passed.
+ mPackageManagerLocal = LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ mConfig = new Config();
+ mBgDexOptJob = new BackgroundDexOptJob(context, artManagerLocal, mConfig);
+ } else {
+ mPackageManagerLocal = null;
+ mConfig = null;
+ mBgDexOptJob = null;
+ }
+ }
+
+ @NonNull
+ public Context getContext() {
+ return Objects.requireNonNull(mContext);
+ }
+
+ @NonNull
+ public PackageManagerLocal getPackageManagerLocal() {
+ return Objects.requireNonNull(mPackageManagerLocal);
+ }
+
+ @NonNull
+ public IArtd getArtd() {
+ return Utils.getArtd();
+ }
+
+ @NonNull
+ public DexOptHelper getDexOptHelper() {
+ return new DexOptHelper(getContext(), getConfig());
+ }
+
+ @NonNull
+ public Config getConfig() {
+ return mConfig;
+ }
+
+ @NonNull
+ public AppHibernationManager getAppHibernationManager() {
+ return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
+ }
+
+ @NonNull
+ public BackgroundDexOptJob getBackgroundDexOptJob() {
+ return Objects.requireNonNull(mBgDexOptJob);
+ }
+
+ @NonNull
+ public UserManager getUserManager() {
+ return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+ }
+
+ @NonNull
+ public DexUseManagerLocal getDexUseManager() {
+ return Objects.requireNonNull(
+ LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+ }
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
new file mode 100644
index 0000000..e6d789a
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.ArtModuleServiceManager;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * Class for performing registration for the ART mainline module.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtModuleServiceInitializer {
+ private ArtModuleServiceInitializer() {}
+
+ @NonNull private static Object sLock = new Object();
+ @GuardedBy("sLock") @Nullable private static ArtModuleServiceManager sArtModuleServiceManager;
+
+ /**
+ * Sets an instance of {@link ArtModuleServiceManager} that allows the ART mainline module to
+ * obtain ART binder services. This is called by the platform during the system server
+ * initialization.
+ */
+ public static void setArtModuleServiceManager(
+ @NonNull ArtModuleServiceManager artModuleServiceManager) {
+ synchronized (sLock) {
+ if (sArtModuleServiceManager != null) {
+ throw new IllegalStateException("ArtModuleServiceManager is already set");
+ }
+ sArtModuleServiceManager = artModuleServiceManager;
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ public static ArtModuleServiceManager getArtModuleServiceManager() {
+ synchronized (sLock) {
+ return Objects.requireNonNull(sArtModuleServiceManager);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
new file mode 100644
index 0000000..eccc7c3
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -0,0 +1,429 @@
+/*
+ * 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.model.ArtFlags.OptimizeFlags;
+import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.OptimizeStatus;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+
+import 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.OptimizationStatus;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageManagerLocal;
+
+import libcore.io.Streams;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * This class handles ART shell commands.
+ *
+ * @hide
+ */
+public final class ArtShellCommand extends BasicShellCommandHandler {
+ private static final String TAG = "ArtShellCommand";
+
+ private final ArtManagerLocal mArtManagerLocal;
+ private final PackageManagerLocal mPackageManagerLocal;
+ private final DexUseManagerLocal mDexUseManager;
+
+ @GuardedBy("sCancellationSignalMap")
+ @NonNull
+ private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
+
+ public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
+ @NonNull PackageManagerLocal packageManagerLocal,
+ @NonNull DexUseManagerLocal dexUseManager) {
+ mArtManagerLocal = artManagerLocal;
+ mPackageManagerLocal = packageManagerLocal;
+ mDexUseManager = dexUseManager;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Override
+ public int onCommand(String cmd) {
+ enforceRoot();
+ PrintWriter pw = getOutPrintWriter();
+ try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) {
+ switch (cmd) {
+ case "delete-optimized-artifacts": {
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(
+ snapshot, getNextArgRequired(), ArtFlags.defaultDeleteFlags());
+ pw.printf("Freed %d bytes\n", result.getFreedBytes());
+ return 0;
+ }
+ case "get-optimization-status": {
+ OptimizationStatus optimizationStatus = mArtManagerLocal.getOptimizationStatus(
+ snapshot, getNextArgRequired(), ArtFlags.defaultGetStatusFlags());
+ pw.println(optimizationStatus);
+ return 0;
+ }
+ case "optimize-package": {
+ var paramsBuilder = new OptimizeParams.Builder("cmdline");
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-m":
+ paramsBuilder.setCompilerFilter(getNextArgRequired());
+ break;
+ case "-f":
+ paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
+ break;
+ case "--secondary-dex":
+ paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+ ArtFlags.FLAG_FOR_PRIMARY_DEX
+ | ArtFlags.FLAG_FOR_SECONDARY_DEX);
+ break;
+ case "--include-dependencies":
+ paramsBuilder.setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+ ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+ break;
+ case "--split":
+ String splitName = getNextArgRequired();
+ paramsBuilder
+ .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT,
+ ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+ .setSplitName(!splitName.isEmpty() ? splitName : null);
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ OptimizeResult result;
+ try (var signal = new WithCancellationSignal(pw)) {
+ result = mArtManagerLocal.optimizePackage(snapshot, getNextArgRequired(),
+ paramsBuilder.build(), signal.get());
+ }
+ printOptimizeResult(pw, result);
+ return 0;
+ }
+ case "optimize-packages": {
+ OptimizeResult result;
+ try (var signal = new WithCancellationSignal(pw)) {
+ result = mArtManagerLocal.optimizePackages(
+ snapshot, getNextArgRequired(), signal.get());
+ }
+ printOptimizeResult(pw, result);
+ return 0;
+ }
+ case "cancel": {
+ String jobId = getNextArgRequired();
+ CancellationSignal signal;
+ synchronized (sCancellationSignalMap) {
+ signal = sCancellationSignalMap.getOrDefault(jobId, null);
+ }
+ if (signal == null) {
+ pw.println("Job not found");
+ return 1;
+ }
+ signal.cancel();
+ pw.println("Job cancelled");
+ return 0;
+ }
+ case "dex-use-notify": {
+ mDexUseManager.notifyDexContainersLoaded(snapshot, getNextArgRequired(),
+ Map.of(getNextArgRequired(), getNextArgRequired()));
+ return 0;
+ }
+ case "dex-use-get-primary": {
+ String packageName = getNextArgRequired();
+ String dexPath = getNextArgRequired();
+ pw.println("Loaders: "
+ + mDexUseManager.getPrimaryDexLoaders(packageName, dexPath)
+ .stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", ")));
+ pw.println("Is used by other apps: "
+ + mDexUseManager.isPrimaryDexUsedByOtherApps(packageName, dexPath));
+ return 0;
+ }
+ case "dex-use-get-secondary": {
+ for (DexUseManagerLocal.SecondaryDexInfo info :
+ mDexUseManager.getSecondaryDexInfo(getNextArgRequired())) {
+ pw.println(info);
+ }
+ return 0;
+ }
+ case "dex-use-dump": {
+ pw.println(mDexUseManager.dump());
+ return 0;
+ }
+ case "dex-use-save": {
+ try {
+ mDexUseManager.save(getNextArgRequired());
+ return 0;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ case "dex-use-load": {
+ try {
+ mDexUseManager.load(getNextArgRequired());
+ return 0;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ case "bg-dexopt-job": {
+ String opt = getNextOption();
+ if (opt == null) {
+ mArtManagerLocal.startBackgroundDexoptJob();
+ return 0;
+ }
+ switch (opt) {
+ case "--cancel": {
+ mArtManagerLocal.cancelBackgroundDexoptJob();
+ return 0;
+ }
+ case "--enable": {
+ // This operation requires the uid to be "system" (1000).
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ mArtManagerLocal.scheduleBackgroundDexoptJob();
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ return 0;
+ }
+ case "--disable": {
+ // This operation requires the uid to be "system" (1000).
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ mArtManagerLocal.unscheduleBackgroundDexoptJob();
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ return 0;
+ }
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+ case "snapshot-app-profile": {
+ String outputPath = getNextArgRequired();
+ ParcelFileDescriptor fd;
+ try {
+ fd = mArtManagerLocal.snapshotAppProfile(
+ snapshot, getNextArgRequired(), getNextOption());
+ } catch (SnapshotProfileException e) {
+ throw new RuntimeException(e);
+ }
+ writeFdContentsToFile(fd, outputPath);
+ return 0;
+ }
+ case "snapshot-boot-image-profile": {
+ String outputPath = getNextArgRequired();
+ ParcelFileDescriptor fd;
+ try {
+ fd = mArtManagerLocal.snapshotBootImageProfile(snapshot);
+ } catch (SnapshotProfileException e) {
+ throw new RuntimeException(e);
+ }
+ writeFdContentsToFile(fd, outputPath);
+ return 0;
+ }
+ default:
+ // Handles empty, help, and invalid commands.
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("ART service commands.");
+ pw.println("Note: The commands are used for internal debugging purposes only. There are no "
+ + "stability guarantees for them.");
+ pw.println("");
+ pw.println("Usage: cmd package art [ARGS]...");
+ pw.println("");
+ pw.println("Supported commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ // TODO(jiakaiz): Also do operations for secondary dex'es by default.
+ pw.println(" delete-optimized-artifacts PACKAGE_NAME");
+ pw.println(" Delete the optimized artifacts of a package.");
+ pw.println(" By default, the command only deletes the optimized artifacts of primary "
+ + "dex'es.");
+ pw.println(" get-optimization-status PACKAGE_NAME");
+ pw.println(" Print the optimization status of a package.");
+ pw.println(" By default, the command only prints the optimization status of primary "
+ + "dex'es.");
+ pw.println(" optimize-package [-m COMPILER_FILTER] [-f] [--secondary-dex] ");
+ pw.println(" [--include-dependencies] [--split SPLIT_NAME] PACKAGE_NAME");
+ pw.println(" Optimize a package.");
+ pw.println(" By default, the command only optimizes primary dex'es.");
+ pw.println(" The command prints a job ID, which can be used to cancel the job using the"
+ + "'cancel' command.");
+ pw.println(" Options:");
+ pw.println(" -m Set the compiler filter.");
+ pw.println(" -f Force compilation.");
+ pw.println(" --secondary-dex Only optimize secondary dex.");
+ pw.println(" --include-dependencies Include dependencies.");
+ pw.println(" --split SPLIT_NAME Only optimize the given split. If SPLIT_NAME is an");
+ pw.println(" empty string, only optimize the base APK.");
+ pw.println(" optimize-packages REASON");
+ pw.println(" Run batch optimization for the given reason.");
+ pw.println(" The command prints a job ID, which can be used to cancel the job using the"
+ + "'cancel' command.");
+ pw.println(" cancel JOB_ID");
+ pw.println(" Cancel a job.");
+ pw.println(" dex-use-notify PACKAGE_NAME DEX_PATH CLASS_LOADER_CONTEXT");
+ pw.println(" Notify that a dex file is loaded with the given class loader context by");
+ pw.println(" the given package.");
+ pw.println(" dex-use-get-primary PACKAGE_NAME DEX_PATH");
+ pw.println(" Print the dex use information about a primary dex file owned by the given");
+ pw.println(" package.");
+ pw.println(" dex-use-get-secondary PACKAGE_NAME");
+ pw.println(" Print the dex use information about all secondary dex files owned by the");
+ pw.println(" given package.");
+ pw.println(" dex-use-dump");
+ pw.println(" Print all dex use information in textproto format.");
+ pw.println(" dex-use-save PATH");
+ pw.println(" Save dex use information to a file in binary proto format.");
+ pw.println(" dex-use-load PATH");
+ pw.println(" Load dex use information from a file in binary proto format.");
+ pw.println(" bg-dexopt-job [--cancel | --disable | --enable]");
+ pw.println(" Control the background dexopt job.");
+ pw.println(" Without flags, it starts a background dexopt job immediately. It does");
+ pw.println(" nothing if a job is already started either automatically by the system");
+ pw.println(" or through this command. This command is not blocking.");
+ pw.println(" Options:");
+ pw.println(" --cancel Cancel any currently running background dexopt job");
+ pw.println(" immediately. This cancels jobs started either automatically by the");
+ pw.println(" system or through this command. This command is not blocking.");
+ pw.println(" --disable: Disable the background dexopt job from being started by the");
+ pw.println(" job scheduler. If a job is already started by the job scheduler and");
+ pw.println(" is running, it will be cancelled immediately. Does not affect");
+ pw.println(" jobs started through this command or by the system in other ways.");
+ pw.println(" This state will be lost when the system_server process exits.");
+ pw.println(" --enable: Enable the background dexopt job to be started by the job");
+ pw.println(" scheduler again, if previously disabled by --disable.");
+ pw.println(" snapshot-app-profile OUTPUT_PATH PACKAGE_NAME [SPLIT_NAME]");
+ pw.println(" Snapshot the profile of the given app and save it to the output path.");
+ pw.println(" If SPLIT_NAME is empty, the command snapshots the base APK.");
+ pw.println(" snapshot-boot-image-profile OUTPUT_PATH");
+ pw.println(" Snapshot the boot image profile and save it to the output path.");
+ }
+
+ private void enforceRoot() {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.ROOT_UID) {
+ throw new SecurityException("ART service shell commands need root access");
+ }
+ }
+
+ @NonNull
+ private String optimizeStatusToString(@OptimizeStatus int status) {
+ switch (status) {
+ case OptimizeResult.OPTIMIZE_SKIPPED:
+ return "SKIPPED";
+ case OptimizeResult.OPTIMIZE_PERFORMED:
+ return "PERFORMED";
+ case OptimizeResult.OPTIMIZE_FAILED:
+ return "FAILED";
+ case OptimizeResult.OPTIMIZE_CANCELLED:
+ return "CANCELLED";
+ }
+ throw new IllegalArgumentException("Unknown optimize status " + status);
+ }
+
+ private void printOptimizeResult(@NonNull PrintWriter pw, @NonNull OptimizeResult result) {
+ pw.println(optimizeStatusToString(result.getFinalStatus()));
+ for (PackageOptimizeResult packageResult : result.getPackageOptimizeResults()) {
+ pw.printf("[%s]\n", packageResult.getPackageName());
+ for (DexContainerFileOptimizeResult fileResult :
+ packageResult.getDexContainerFileOptimizeResults()) {
+ pw.printf("dexContainerFile = %s, isPrimaryAbi = %b, abi = %s, "
+ + "compilerFilter = %s, status = %s, "
+ + "dex2oatWallTimeMillis = %d, dex2oatCpuTimeMillis = %d, "
+ + "sizeBytes = %d, sizeBeforeBytes = %d\n",
+ fileResult.getDexContainerFile(), fileResult.isPrimaryAbi(),
+ fileResult.getAbi(), fileResult.getActualCompilerFilter(),
+ optimizeStatusToString(fileResult.getStatus()),
+ fileResult.getDex2oatWallTimeMillis(), fileResult.getDex2oatCpuTimeMillis(),
+ fileResult.getSizeBytes(), fileResult.getSizeBeforeBytes());
+ }
+ }
+ }
+
+ private void writeFdContentsToFile(
+ @NonNull ParcelFileDescriptor fd, @NonNull String outputPath) {
+ try (InputStream inputStream = new AutoCloseInputStream(fd);
+ OutputStream outputStream = new FileOutputStream(outputPath)) {
+ Streams.copy(inputStream, outputStream);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class WithCancellationSignal implements AutoCloseable {
+ @NonNull private final CancellationSignal mSignal = new CancellationSignal();
+ @NonNull private final String mJobId;
+
+ public WithCancellationSignal(@NonNull PrintWriter pw) {
+ mJobId = UUID.randomUUID().toString();
+ pw.printf("Job ID: %s\n", mJobId);
+ pw.flush();
+
+ synchronized (sCancellationSignalMap) {
+ sCancellationSignalMap.put(mJobId, mSignal);
+ }
+ }
+
+ @NonNull
+ public CancellationSignal get() {
+ return mSignal;
+ }
+
+ public void close() {
+ synchronized (sCancellationSignalMap) {
+ sCancellationSignalMap.remove(mJobId);
+ }
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java
new file mode 100644
index 0000000..b93bdda
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexOptJob.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback;
+import static com.android.server.art.model.ArtFlags.ScheduleStatus;
+import static com.android.server.art.model.Config.Callback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/** @hide */
+public class BackgroundDexOptJob {
+ private static final String TAG = "BackgroundDexOptJob";
+
+ /**
+ * "android" is the package name for a <service> declared in
+ * frameworks/base/core/res/AndroidManifest.xml
+ */
+ private static final String JOB_PKG_NAME = Utils.PLATFORM_PACKAGE_NAME;
+ /** An arbitrary number. Must be unique among all jobs owned by the system uid. */
+ private static final int JOB_ID = 27873780;
+
+ @VisibleForTesting public static final long JOB_INTERVAL_MS = TimeUnit.DAYS.toMillis(1);
+
+ @NonNull private final Injector mInjector;
+
+ @GuardedBy("this") @Nullable private CompletableFuture<Result> mRunningJob = null;
+ @GuardedBy("this") @Nullable private CancellationSignal mCancellationSignal = null;
+
+ 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(params, result);
+ // This is a periodic job, where the interval is specified in the `JobInfo`. "false"
+ // means not to execute again during a future idle maintenance window in the same
+ // interval. This job will be executed again in the next interval.
+ // This call will be ignored if `onStopJob` is called.
+ jobService.jobFinished(params, false /* wantReschedule */);
+ });
+ // "true" means the job will continue running until `jobFinished` is called.
+ return true;
+ }
+
+ /** Handles {@link BackgroundDexOptJobService#onStopJob(JobParameters)}. */
+ public boolean onStopJob(@NonNull JobParameters params) {
+ 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)
+ .setRequiresStorageNotLow(true);
+
+ Callback<ScheduleBackgroundDexoptJobCallback> callback =
+ mInjector.getConfig().getScheduleBackgroundDexoptJobCallback();
+ if (callback != null) {
+ Utils.executeAndWait(
+ callback.executor(), () -> { callback.get().onOverrideJobInfo(builder); });
+ }
+
+ return mInjector.getJobScheduler().schedule(builder.build()) == 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();
+ mRunningJob = new CompletableFuture().supplyAsync(() -> {
+ Log.i(TAG, "Job started");
+ try {
+ return run(mCancellationSignal);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fatal error", e);
+ return new FatalErrorResult();
+ } finally {
+ Log.i(TAG, "Job finished");
+ synchronized (this) {
+ mRunningJob = null;
+ mCancellationSignal = null;
+ }
+ }
+ });
+ return mRunningJob;
+ }
+
+ public synchronized void cancel() {
+ if (mRunningJob == null) {
+ Log.i(TAG, "Job is not running");
+ return;
+ }
+
+ mCancellationSignal.cancel();
+ Log.i(TAG, "Job cancelled");
+ }
+
+ @NonNull
+ private CompletedResult run(@NonNull CancellationSignal cancellationSignal) {
+ // TODO(b/254013427): Cleanup dex use info.
+ // TODO(b/254013425): Cleanup unused secondary dex file artifacts.
+ // TODO(b/255565888): Downgrade inactive apps.
+ long startTimeMs = SystemClock.uptimeMillis();
+ OptimizeResult dexoptResult;
+ try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
+ dexoptResult = mInjector.getArtManagerLocal().optimizePackages(
+ snapshot, ReasonMapping.REASON_BG_DEXOPT, cancellationSignal);
+ }
+ return CompletedResult.create(dexoptResult, SystemClock.uptimeMillis() - startTimeMs);
+ }
+
+ private void writeStats(@NonNull JobParameters params, @NonNull Result result) {
+ if (result instanceof CompletedResult) {
+ var completedResult = (CompletedResult) result;
+ int status = completedResult.dexoptResult().getFinalStatus()
+ == OptimizeResult.OPTIMIZE_CANCELLED
+ ? ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION
+ : ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED;
+ ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED, status,
+ params.getStopReason(), completedResult.durationMs(), 0 /* deprecated */);
+ } else if (result instanceof FatalErrorResult) {
+ ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+ ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN,
+ JobParameters.STOP_REASON_UNDEFINED, 0 /* durationMs */, 0 /* deprecated */);
+ }
+ }
+
+ static abstract class Result {}
+ static class FatalErrorResult extends Result {}
+
+ @AutoValue
+ static abstract class CompletedResult extends Result {
+ abstract @NonNull OptimizeResult dexoptResult();
+ abstract long durationMs();
+
+ @NonNull
+ static CompletedResult create(@NonNull OptimizeResult 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;
+ }
+
+ @NonNull
+ public ArtManagerLocal getArtManagerLocal() {
+ return mArtManagerLocal;
+ }
+
+ @NonNull
+ public PackageManagerLocal getPackageManagerLocal() {
+ return LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ }
+
+ @NonNull
+ public Config getConfig() {
+ return mConfig;
+ }
+
+ @NonNull
+ public JobScheduler getJobScheduler() {
+ return mContext.getSystemService(JobScheduler.class);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexOptJobService.java b/libartservice/service/java/com/android/server/art/BackgroundDexOptJobService.java
new file mode 100644
index 0000000..5ab35d5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexOptJobService.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * Entry point for the callback from the job scheduler. This class is instantiated by the system
+ * automatically.
+ *
+ * @hide
+ */
+public class BackgroundDexOptJobService extends JobService {
+ @Override
+ public boolean onStartJob(@NonNull JobParameters params) {
+ return getJob().onStartJob(this, params);
+ }
+
+ @Override
+ public boolean onStopJob(@NonNull JobParameters params) {
+ return getJob().onStopJob(params);
+ }
+
+ @NonNull
+ static BackgroundDexOptJob getJob() {
+ return LocalManagerRegistry.getManager(ArtManagerLocal.class).getBackgroundDexOptJob();
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/Constants.java b/libartservice/service/java/com/android/server/art/Constants.java
new file mode 100644
index 0000000..2d2d757
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Constants.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.system.Os;
+
+/**
+ * A mockable wrapper class for device-specific constants.
+ *
+ * @hide
+ */
+public class Constants {
+ private Constants() {}
+
+ /** Returns the ABI that the device prefers. */
+ @NonNull
+ public static String getPreferredAbi() {
+ return Build.SUPPORTED_ABIS[0];
+ }
+
+ /** Returns the 64 bit ABI that is native to the device. */
+ @Nullable
+ public static String getNative64BitAbi() {
+ // The value comes from "ro.product.cpu.abilist64" and we assume that the first element is
+ // the native one.
+ return Build.SUPPORTED_64_BIT_ABIS.length > 0 ? Build.SUPPORTED_64_BIT_ABIS[0] : null;
+ }
+
+ /** Returns the 32 bit ABI that is native to the device. */
+ @Nullable
+ public static String getNative32BitAbi() {
+ // The value comes from "ro.product.cpu.abilist32" and we assume that the first element is
+ // the native one.
+ return Build.SUPPORTED_32_BIT_ABIS.length > 0 ? Build.SUPPORTED_32_BIT_ABIS[0] : null;
+ }
+
+ @Nullable
+ public static String getenv(@NonNull String name) {
+ return Os.getenv(name);
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
new file mode 100644
index 0000000..df45ab2
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -0,0 +1,298 @@
+/*
+ * 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.OptimizePackageDoneCallback;
+import static com.android.server.art.model.Config.Callback;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
+
+import android.annotation.NonNull;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to handle dexopt.
+ *
+ * It talks to other components (e.g., PowerManager) and dispatches tasks to dex optimizers.
+ *
+ * @hide
+ */
+public class DexOptHelper {
+ private static final String TAG = "DexoptHelper";
+
+ /**
+ * Timeout of the wake lock. This is required by AndroidLint, but we set it to a very large
+ * value so that it should normally never triggered.
+ */
+ private static final long WAKE_LOCK_TIMEOUT_MS = TimeUnit.DAYS.toMillis(1);
+
+ @NonNull private final Injector mInjector;
+
+ public DexOptHelper(@NonNull Context context, @NonNull Config config) {
+ this(new Injector(context, config));
+ }
+
+ @VisibleForTesting
+ public DexOptHelper(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
+ * DO NOT use this method directly. Use {@link
+ * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ public OptimizeResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull List<String> packageNames, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal, @NonNull Executor executor) {
+ return dexoptPackages(
+ getPackageStates(snapshot, packageNames,
+ (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0),
+ params, cancellationSignal, executor);
+ }
+
+ /**
+ * DO NOT use this method directly. Use {@link
+ * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ private OptimizeResult dexoptPackages(@NonNull List<PackageState> pkgStates,
+ @NonNull OptimizeParams params, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Executor executor) {
+ 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<Future<PackageOptimizeResult>> futures = new ArrayList<>();
+ for (PackageState pkgState : pkgStates) {
+ futures.add(Utils.execute(
+ executor, () -> dexoptPackage(pkgState, params, cancellationSignal)));
+ }
+
+ List<PackageOptimizeResult> results =
+ futures.stream().map(Utils::getFuture).collect(Collectors.toList());
+
+ var result =
+ new OptimizeResult(params.getCompilerFilter(), params.getReason(), results);
+
+ for (Callback<OptimizePackageDoneCallback> callback :
+ mInjector.getConfig().getOptimizePackageDoneCallbacks()) {
+ // TODO(b/257027956): Consider filtering the packages before calling the callback.
+ Utils.executeAndWait(callback.executor(),
+ () -> { callback.get().onOptimizePackageDone(result); });
+ }
+
+ return result;
+ } finally {
+ if (wakeLock != null) {
+ wakeLock.release();
+ }
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * DO NOT use this method directly. Use {@link
+ * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ private PackageOptimizeResult dexoptPackage(@NonNull PackageState pkgState,
+ @NonNull OptimizeParams params, @NonNull CancellationSignal cancellationSignal) {
+ List<DexContainerFileOptimizeResult> results = new ArrayList<>();
+ Supplier<PackageOptimizeResult> createResult = ()
+ -> new PackageOptimizeResult(
+ pkgState.getPackageName(), results, cancellationSignal.isCanceled());
+
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+ if (!canOptimizePackage(pkgState)) {
+ return createResult.get();
+ }
+
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+ // Throws if the split is not found.
+ PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
+ }
+
+ try {
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ if (cancellationSignal.isCanceled()) {
+ return createResult.get();
+ }
+
+ results.addAll(
+ mInjector.getPrimaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
+ }
+
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ if (cancellationSignal.isCanceled()) {
+ return createResult.get();
+ }
+
+ results.addAll(
+ mInjector
+ .getSecondaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+
+ return createResult.get();
+ }
+
+ private boolean canOptimizePackage(@NonNull PackageState pkgState) {
+ return Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager());
+ }
+
+ @NonNull
+ private List<PackageState> getPackageStates(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull List<String> packageNames, boolean includeDependencies) {
+ var pkgStates = new LinkedHashMap<String, PackageState>();
+ Set<String> visitedLibraries = new HashSet<>();
+ Queue<SharedLibrary> queue = new LinkedList<>();
+
+ Consumer<SharedLibrary> maybeEnqueue = library -> {
+ // The package name is not null if the library is an APK.
+ // TODO(jiakaiz): Support JAR libraries.
+ if (library.getPackageName() != null && !visitedLibraries.contains(library.getName())) {
+ visitedLibraries.add(library.getName());
+ queue.add(library);
+ }
+ };
+
+ for (String packageName : packageNames) {
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ Utils.getPackageOrThrow(pkgState);
+ pkgStates.put(packageName, pkgState);
+ if (includeDependencies && canOptimizePackage(pkgState)) {
+ for (SharedLibrary library : pkgState.getUsesLibraries()) {
+ maybeEnqueue.accept(library);
+ }
+ }
+ }
+
+ SharedLibrary library;
+ while ((library = queue.poll()) != null) {
+ String packageName = library.getPackageName();
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ if (canOptimizePackage(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;
+ }
+
+ @NonNull
+ PrimaryDexOptimizer getPrimaryDexOptimizer(@NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ return new PrimaryDexOptimizer(mContext, pkgState, pkg, params, cancellationSignal);
+ }
+
+ @NonNull
+ SecondaryDexOptimizer getSecondaryDexOptimizer(@NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ return new SecondaryDexOptimizer(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/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
new file mode 100644
index 0000000..21333a5
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -0,0 +1,657 @@
+/*
+ * 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.OptimizeFlags;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public abstract class DexOptimizer<DexInfoType extends DetailedDexInfo> {
+ private static final String TAG = "DexOptimizer";
+
+ @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 OptimizeParams mParams;
+ @NonNull protected final CancellationSignal mCancellationSignal;
+
+ protected DexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams 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#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ public final List<DexContainerFileOptimizeResult> dexopt() throws RemoteException {
+ List<DexContainerFileOptimizeResult> results = new ArrayList<>();
+
+ for (DexInfoType dexInfo : getDexInfoList()) {
+ ProfilePath profile = null;
+ boolean succeeded = true;
+ try {
+ if (!isOptimizable(dexInfo)) {
+ continue;
+ }
+
+ String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
+ if (compilerFilter.equals(OptimizeParams.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 optimization 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)) {
+ @OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
+ long wallTimeMs = 0;
+ long cpuTimeMs = 0;
+ long sizeBytes = 0;
+ long sizeBeforeBytes = 0;
+ 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;
+ }
+
+ 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);
+ }
+ });
+
+ DexoptResult dexoptResult = dexoptFile(target, profile,
+ getDexoptNeededResult, permissionSettings,
+ mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
+ status = dexoptResult.cancelled ? OptimizeResult.OPTIMIZE_CANCELLED
+ : OptimizeResult.OPTIMIZE_PERFORMED;
+ wallTimeMs = dexoptResult.wallTimeMs;
+ cpuTimeMs = dexoptResult.cpuTimeMs;
+ sizeBytes = dexoptResult.sizeBytes;
+ sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
+
+ if (status == OptimizeResult.OPTIMIZE_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 = OptimizeResult.OPTIMIZE_FAILED;
+ } finally {
+ results.add(new DexContainerFileOptimizeResult(dexInfo.dexPath(),
+ abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+ cpuTimeMs, sizeBytes, sizeBeforeBytes));
+ if (status != OptimizeResult.OPTIMIZE_SKIPPED
+ && status != OptimizeResult.OPTIMIZE_PERFORMED) {
+ succeeded = false;
+ }
+ // Make sure artd does not leak even if the caller holds
+ // `mCancellationSignal` forever.
+ mCancellationSignal.setOnCancelListener(null);
+ }
+ }
+
+ if (profile != null && succeeded) {
+ if (profile.getTag() == ProfilePath.tmpProfilePath) {
+ // Commit the profile only if dexopt succeeds.
+ if (commitProfileChanges(profile.getTmpProfilePath())) {
+ profile = null;
+ }
+ }
+ if (profileMerged) {
+ // Note that this is just an optimization, to reduce the amount of data that
+ // the runtime writes on every profile save. The profile merge result on the
+ // next run won't change regardless of whether the cleanup is done or not
+ // because profman only looks at the diff.
+ // A caveat is that it may delete more than what has been merged, if the
+ // runtime writes additional entries between the merge and the cleanup, but
+ // this is fine because the runtime writes all JITed classes and methods on
+ // every save and the additional entries will likely be written back on the
+ // next save.
+ cleanupCurProfiles(dexInfo);
+ }
+ }
+ } finally {
+ if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+ mInjector.getArtd().deleteProfile(profile);
+ }
+ }
+ }
+
+ return results;
+ }
+
+ @NonNull
+ private String adjustCompilerFilter(
+ @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
+ if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
+ String systemUiCompilerFilter = getSystemUiCompilerFilter();
+ if (!systemUiCompilerFilter.isEmpty()) {
+ return systemUiCompilerFilter;
+ }
+ }
+
+ // We force vmSafeMode on debuggable apps as well:
+ // - the runtime ignores their compiled code
+ // - they generally have lots of methods that could make the compiler used run out of
+ // memory (b/130828957)
+ // Note that forcing the compiler filter here applies to all compilations (even if they
+ // are done via adb shell commands). This is okay because the runtime will ignore the
+ // compiled code anyway.
+ if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
+ return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
+ }
+
+ // We cannot do AOT compilation if we don't have a valid class loader context.
+ if (dexInfo.classLoaderContext() == null) {
+ return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+ : targetCompilerFilter;
+ }
+
+ // This application wants to use the embedded dex in the APK, rather than extracted or
+ // locally compiled variants, so we only verify it.
+ // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
+ // won't extract the dex code because the APK is uncompressed, and the assumption is that
+ // such applications always use uncompressed APKs.
+ if (mPkg.isUseEmbeddedDex()) {
+ return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+ : targetCompilerFilter;
+ }
+
+ return targetCompilerFilter;
+ }
+
+ @NonNull
+ private String getSystemUiCompilerFilter() {
+ String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
+ if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
+ throw new IllegalStateException(
+ "Got invalid compiler filter '" + compilerFilter + "' for System UI");
+ }
+ return compilerFilter;
+ }
+
+ /**
+ * Gets the existing reference profile if exists, or initializes a reference profile from an
+ * external profile.
+ *
+ * @return A pair where the first element is the found or initialized profile, and the second
+ * element is true if the profile is readable by others. Or null if there is no
+ * reference profile or external profile to use.
+ */
+ @Nullable
+ private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+ throws RemoteException {
+ ProfilePath refProfile = buildRefProfilePath(dexInfo);
+ try {
+ if (mInjector.getArtd().isProfileUsable(refProfile, dexInfo.dexPath())) {
+ boolean isOtherReadable = mInjector.getArtd().getProfileVisibility(refProfile)
+ == FileVisibility.OTHER_READABLE;
+ return Pair.create(refProfile, isOtherReadable);
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ "Failed to use the existing reference profile "
+ + AidlUtils.toString(refProfile),
+ e);
+ }
+
+ ProfilePath initializedProfile = initReferenceProfile(dexInfo);
+ return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+ }
+
+ @NonNull
+ private DexoptOptions getDexoptOptions(
+ @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
+ DexoptOptions dexoptOptions = new DexoptOptions();
+ dexoptOptions.compilationReason = mParams.getReason();
+ dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
+ dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
+ // Generating a meaningful app image needs a profile to determine what to include in the
+ // image. Otherwise, the app image will be nearly empty.
+ dexoptOptions.generateAppImage =
+ isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled();
+ dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
+ return dexoptOptions;
+ }
+
+ private boolean isAlwaysDebuggable() {
+ return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
+ }
+
+ private boolean isAppImageEnabled() {
+ return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
+ }
+
+ private boolean isHiddenApiPolicyEnabled() {
+ if (mPkg.isSignedWithPlatformKey()) {
+ return false;
+ }
+ if (mPkgState.isSystem() || mPkgState.isUpdatedSystemApp()) {
+ // TODO(b/236389629): Check whether the app is in hidden api whitelist.
+ return !mPkg.isUsesNonSdkApi();
+ }
+ return true;
+ }
+
+ @NonNull
+ GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
+ @NonNull GetDexoptNeededOptions options) throws RemoteException {
+ int dexoptTrigger = getDexoptTrigger(target, options);
+
+ // The result should come from artd even if all the bits of `dexoptTrigger` are set
+ // because the result also contains information about the usable VDEX file.
+ // Note that the class loader context can be null. In that case, we intentionally pass the
+ // null value down to lower levels to indicate that the class loader context check should be
+ // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
+ GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+ target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+ target.compilerFilter(), dexoptTrigger);
+
+ return result;
+ }
+
+ int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
+ @NonNull GetDexoptNeededOptions options) throws RemoteException {
+ if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
+ return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
+ | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ }
+
+ if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
+ return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+ }
+
+ int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ if (options.profileMerged()) {
+ dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
+ }
+
+ ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath(
+ target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
+
+ if (options.needsToBePublic()
+ && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
+ == FileVisibility.NOT_OTHER_READABLE) {
+ // Typically, this happens after an app starts being used by other apps.
+ // This case should be the same as force as we have no choice but to trigger a new
+ // dexopt.
+ dexoptTrigger |=
+ DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+ }
+
+ return dexoptTrigger;
+ }
+
+ private DexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
+ @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
+ @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
+ @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
+ throws RemoteException {
+ OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
+ target.isa(), target.isInDalvikCache(), permissionSettings);
+
+ VdexPath inputVdex =
+ getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
+
+ DexMetadataPath dmFile = getDmFile(target.dexInfo());
+ if (dmFile != null
+ && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
+ // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
+ // "install-dm").
+ // Note that this only applies to reasons for app install because the goal is to give
+ // Play a signal that a DM file is downloaded at install time. We actually pass the DM
+ // file regardless of the compilation reason, but we don't append a suffix when the
+ // compilation reason is not a reason for app install.
+ // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
+ // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
+ // compilation reason can still be "install-dm" even if dex2oat left all contents of the
+ // DM file unused or an empty DM file is passed to dex2oat.
+ dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
+ }
+
+ return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+ target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+ dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+ }
+
+ @Nullable
+ private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
+ @NonNull String dexPath, @NonNull String isa) {
+ if (!getDexoptNeededResult.isVdexUsable) {
+ return null;
+ }
+ switch (getDexoptNeededResult.artifactsLocation) {
+ case ArtifactsLocation.DALVIK_CACHE:
+ return VdexPath.artifactsPath(
+ AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */));
+ case ArtifactsLocation.NEXT_TO_DEX:
+ return VdexPath.artifactsPath(
+ AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
+ case ArtifactsLocation.DM:
+ // The DM file is passed to dex2oat as a separate flag whenever it exists.
+ return null;
+ default:
+ // This should never happen as the value is got from artd.
+ throw new IllegalStateException(
+ "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
+ }
+ }
+
+ @Nullable
+ private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
+ DexMetadataPath path = buildDmPath(dexInfo);
+ if (path == null) {
+ return null;
+ }
+ try {
+ if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
+ return path;
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
+ }
+ return null;
+ }
+
+ private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
+ try {
+ mInjector.getArtd().commitTmpProfile(profile);
+ return true;
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
+ e);
+ return false;
+ }
+ }
+
+ @Nullable
+ private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
+ @Nullable ProfilePath referenceProfile) throws RemoteException {
+ OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
+
+ try {
+ if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
+ List.of(dexInfo.dexPath()), new MergeProfileOptions())) {
+ return ProfilePath.tmpProfilePath(output.profilePath);
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
+ e);
+ }
+
+ return null;
+ }
+
+ private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
+ for (ProfilePath profile : getCurProfiles(dexInfo)) {
+ mInjector.getArtd().deleteProfile(profile);
+ }
+ }
+
+ // Methods to be implemented by child classes.
+
+ /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
+ protected abstract boolean isInDalvikCache();
+
+ /** Returns information about all dex files. */
+ @NonNull protected abstract List<DexInfoType> getDexInfoList();
+
+ /** Returns true if the given dex file should be optimized. */
+ protected abstract boolean isOptimizable(@NonNull DexInfoType dexInfo);
+
+ /**
+ * Returns true if the artifacts should be shared with other apps. Note that this must imply
+ * {@link #isDexFilePublic(DexInfoType)}.
+ */
+ protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
+
+ /**
+ * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+ * (S_IROTH).
+ */
+ protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
+
+ /**
+ * Returns a reference profile initialized from an external profile (e.g., a DM profile) if
+ * one exists, or null otherwise.
+ */
+ @Nullable
+ protected abstract ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo)
+ throws RemoteException;
+
+ /** Returns the permission settings to use for the artifacts of the given dex file. */
+ @NonNull
+ protected abstract PermissionSettings getPermissionSettings(
+ @NonNull DexInfoType dexInfo, boolean canBePublic);
+
+ /** Returns all ABIs that the given dex file should be compiled for. */
+ @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
+
+ /** Returns the path to the reference profile of the given dex file. */
+ @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo);
+
+ /** Returns true if app image (--app-image-fd) is allowed. */
+ protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo);
+
+ /**
+ * Returns the data structure that represents the temporary profile to use during processing.
+ */
+ @NonNull
+ protected abstract OutputProfile buildOutputProfile(
+ @NonNull DexInfoType dexInfo, boolean isPublic);
+
+ /** Returns the paths to the current profiles of the given dex file. */
+ @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
+
+ /**
+ * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
+ * should be passed.
+ */
+ @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
+
+ @AutoValue
+ abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
+ abstract @NonNull DexInfoType dexInfo();
+ abstract @NonNull String isa();
+ abstract boolean isInDalvikCache();
+ abstract @NonNull String compilerFilter();
+
+ static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
+ return new AutoValue_DexOptimizer_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 @OptimizeFlags int flags();
+ abstract boolean profileMerged();
+ abstract boolean needsToBePublic();
+
+ static Builder builder() {
+ return new AutoValue_DexOptimizer_GetDexoptNeededOptions.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setFlags(@OptimizeFlags 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;
+ }
+
+ public boolean isSystemUiPackage(@NonNull String packageName) {
+ return packageName.equals(mContext.getString(R.string.config_systemUi));
+ }
+
+ @NonNull
+ public UserManager getUserManager() {
+ return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+ }
+
+ @NonNull
+ public DexUseManagerLocal getDexUseManager() {
+ return Objects.requireNonNull(
+ LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+ }
+
+ @NonNull
+ public IArtd getArtd() {
+ return Utils.getArtd();
+ }
+ }
+}
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..f6e987a
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -0,0 +1,718 @@
+/*
+ * 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.Binder;
+import android.os.Build;
+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.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.art.wrapper.Environment;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+/**
+ * A singleton class that maintains the information about dex uses. This class is thread-safe.
+ *
+ * This class collects data sent directly by apps, and hence the data should be trusted as little as
+ * possible.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class DexUseManagerLocal {
+ private static final String TAG = "DexUseManagerLocal";
+
+ private static final Object sLock = new Object();
+ @GuardedBy("sLock") @Nullable private static DexUseManagerLocal sInstance = null;
+
+ @NonNull private final Injector mInjector;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock") @NonNull private DexUse mDexUse = new DexUse();
+
+ /**
+ * 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.
+ *
+ * @throws IllegalStateException if the instance is already created
+ */
+ @NonNull
+ public static DexUseManagerLocal createInstance() {
+ synchronized (sLock) {
+ if (sInstance != null) {
+ throw new IllegalStateException("DexUseManagerLocal is already created");
+ }
+ sInstance = new DexUseManagerLocal();
+ return sInstance;
+ }
+ }
+
+ private DexUseManagerLocal() {
+ this(new Injector());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public DexUseManagerLocal(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
+ * Returns all entities that load the given primary dex file owned by the given package.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ @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.mLoaders);
+ }
+ }
+
+ /**
+ * 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 */);
+ }
+
+ /**
+ * @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 optimize 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 optimize and how to optimize them
+ * </ul>
+ *
+ * @param loadingPackageName the name of the package who performs the load. ART Service assumes
+ * that this argument has been validated that it exists in the snapshot and matches the
+ * calling UID
+ * @param classLoaderContextByDexContainerFile a map from dex container files' absolute paths to
+ * the string representations of the class loader contexts used to load them
+ * @throws IllegalArgumentException if {@code classLoaderContextByDexContainerFile} contains
+ * invalid entries
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void notifyDexContainersLoaded(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull String loadingPackageName,
+ @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+ // "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle
+ // this case because it doesn't compile system server and system server isn't allowed to
+ // load artifacts produced by ART Services.
+ if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
+ return;
+ }
+
+ validateInputs(snapshot, loadingPackageName, classLoaderContextByDexContainerFile);
+
+ // TODO(jiakaiz): Investigate whether it should also be considered as isolated process if
+ // `Process.isSdkSandboxUid` returns true.
+ boolean isolatedProcess = Process.isIsolatedUid(Binder.getCallingUid());
+
+ for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+ String dexPath = Utils.assertNonEmpty(entry.getKey());
+ String classLoaderContext = Utils.assertNonEmpty(entry.getValue());
+ String owningPackageName = findOwningPackage(snapshot, loadingPackageName, dexPath,
+ DexUseManagerLocal::isOwningPackageForPrimaryDex);
+ if (owningPackageName != null) {
+ addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess);
+ continue;
+ }
+ owningPackageName = findOwningPackage(snapshot, loadingPackageName, dexPath,
+ DexUseManagerLocal::isOwningPackageForSecondaryDex);
+ if (owningPackageName != null) {
+ PackageState loadingPkgState =
+ Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+ // An app is always launched with its primary ABI.
+ Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState);
+ addSecondaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess,
+ classLoaderContext, abi.name());
+ continue;
+ }
+ // It is expected that a dex file isn't owned by any package. For example, the dex file
+ // could be a shared library jar.
+ }
+ }
+
+ @Nullable
+ private static String findOwningPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull String loadingPackageName, @NonNull String dexPath,
+ @NonNull BiFunction<PackageState, String, Boolean> predicate) {
+ // Most likely, the package is loading its own dex file, so we check this first as an
+ // optimization.
+ PackageState loadingPkgState = Utils.getPackageStateOrThrow(snapshot, loadingPackageName);
+ if (predicate.apply(loadingPkgState, dexPath)) {
+ return loadingPkgState.getPackageName();
+ }
+
+ // TODO(b/246609797): The API can be improved.
+ var packageStates = new ArrayList<PackageState>();
+ snapshot.forAllPackageStates((pkgState) -> { packageStates.add(pkgState); });
+
+ for (PackageState pkgState : packageStates) {
+ if (predicate.apply(pkgState, dexPath)) {
+ return pkgState.getPackageName();
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean isOwningPackageForPrimaryDex(
+ @NonNull PackageState pkgState, @NonNull String dexPath) {
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ return PrimaryDexUtils.getDexInfo(pkg).stream().anyMatch(
+ dexInfo -> dexInfo.dexPath().equals(dexPath));
+ }
+
+ private static boolean isOwningPackageForSecondaryDex(
+ @NonNull PackageState pkgState, @NonNull String dexPath) {
+ String volumeUuid =
+ new com.android.server.art.wrapper.PackageState(pkgState).getVolumeUuid();
+ UserHandle handle = Binder.getCallingUserHandle();
+
+ File ceDir = Environment.getDataUserCePackageDirectory(
+ volumeUuid, handle.getIdentifier(), pkgState.getPackageName());
+ if (Paths.get(dexPath).startsWith(ceDir.toPath())) {
+ return true;
+ }
+
+ File deDir = Environment.getDataUserDePackageDirectory(
+ volumeUuid, handle.getIdentifier(), pkgState.getPackageName());
+ if (Paths.get(dexPath).startsWith(deDir.toPath())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void addPrimaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+ @NonNull String loadingPackageName, boolean isolatedProcess) {
+ synchronized (mLock) {
+ mDexUse.mPackageDexUseByOwningPackageName
+ .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
+ .mPrimaryDexUseByDexFile.computeIfAbsent(dexPath, k -> new PrimaryDexUse())
+ .mLoaders.add(DexLoader.create(loadingPackageName, isolatedProcess));
+ }
+ }
+
+ private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
+ @NonNull String loadingPackageName, boolean isolatedProcess,
+ @NonNull String classLoaderContext, @NonNull String abiName) {
+ 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;
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public void clear() {
+ synchronized (mLock) {
+ mDexUse = new DexUse();
+ }
+ }
+
+ /** @hide */
+ public @NonNull String dump() {
+ var builder = DexUseProto.newBuilder();
+ synchronized (mLock) {
+ mDexUse.toProto(builder);
+ }
+ return builder.build().toString();
+ }
+
+ /** @hide */
+ public void save(@NonNull String filename) throws IOException {
+ try (OutputStream out = new FileOutputStream(filename)) {
+ var builder = DexUseProto.newBuilder();
+ synchronized (mLock) {
+ mDexUse.toProto(builder);
+ }
+ builder.build().writeTo(out);
+ }
+ }
+
+ /** @hide */
+ public void load(@NonNull String filename) throws IOException {
+ try (InputStream in = new FileInputStream(filename)) {
+ var proto = DexUseProto.parseFrom(in);
+ var dexUse = new DexUse();
+ dexUse.fromProto(proto);
+ synchronized (mLock) {
+ mDexUse = dexUse;
+ }
+ }
+ }
+
+ private static boolean isUsedByOtherApps(
+ @NonNull Set<DexLoader> loaders, @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 optimized artifacts to be world readable. An
+ // example of such a package is webview.
+ return loaders.stream().anyMatch(loader
+ -> !loader.loadingPackageName().equals(owningPackageName)
+ || loader.isolatedProcess());
+ }
+
+ private static void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull String loadingPackageName,
+ @NonNull Map<String, String> classLoaderContextByDexContainerFile) {
+ if (classLoaderContextByDexContainerFile.isEmpty()) {
+ throw new IllegalArgumentException("Nothing to record");
+ }
+
+ for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
+ Utils.assertNonEmpty(entry.getKey());
+ if (!Paths.get(entry.getKey()).isAbsolute()) {
+ throw new IllegalArgumentException(String.format(
+ "Dex container file path must be absolute, got '%s'", entry.getKey()));
+ }
+ Utils.assertNonEmpty(entry.getValue());
+ }
+
+ // TODO(b/253570365): Make the validation more strict.
+ }
+
+ private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
+ try {
+ return mInjector.getArtd().getDexFileVisibility(dexPath);
+ } catch (ServiceSpecificException | RemoteException e) {
+ Log.e(TAG, "Failed to get visibility of " + dexPath, e);
+ return FileVisibility.NOT_FOUND;
+ }
+ }
+
+ /**
+ * Basic information about a secondary dex file (an APK or JAR file that an app adds to its
+ * own data directory and loads dynamically).
+ *
+ * @hide
+ */
+ @Immutable
+ public abstract static class SecondaryDexInfo {
+ // Special encoding used to denote a foreign ClassLoader was found when trying to encode
+ // class loader contexts for each classpath element in a ClassLoader.
+ // Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
+ // `art/runtime/class_loader_context.h`.
+ public static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+ "=UnsupportedClassLoaderContext=";
+
+ // Special encoding used to denote that a dex file is loaded by different packages with
+ // different ClassLoader's. Only for display purpose (e.g., in dumpsys). This value is not
+ // written to the file, and so far only used here.
+ @VisibleForTesting
+ public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts=";
+
+ /** The absolute path to the dex file within the user's app data directory. */
+ public abstract @NonNull String dexPath();
+
+ /**
+ * The {@link UserHandle} that represents the human user who owns and loads the dex file. A
+ * secondary dex file belongs to a specific human user, and only that user can load it.
+ */
+ public abstract @NonNull UserHandle userHandle();
+
+ /**
+ * A string describing the structure of the class loader that the dex file is loaded with,
+ * or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}.
+ */
+ public abstract @NonNull String displayClassLoaderContext();
+
+ /**
+ * A string describing the structure of the class loader that the dex file is loaded with,
+ * or null if the class loader context is invalid.
+ */
+ public @Nullable String classLoaderContext() {
+ return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
+ && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS)
+ ? displayClassLoaderContext()
+ : null;
+ }
+
+ /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */
+ public abstract @NonNull Set<String> abiNames();
+
+ /** The set of entities that load the dex file. Guaranteed to be non-empty. */
+ public abstract @NonNull Set<DexLoader> loaders();
+
+ /** Returns whether the dex file is used by apps other than the app that owns it. */
+ public abstract boolean isUsedByOtherApps();
+ }
+
+ /**
+ * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
+ * own data directory and loads dynamically). It contains the visibility of the dex file in
+ * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO.
+ *
+ * @hide
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class DetailedSecondaryDexInfo
+ extends SecondaryDexInfo implements DetailedDexInfo {
+ static DetailedSecondaryDexInfo create(@NonNull String dexPath,
+ @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext,
+ @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders,
+ boolean isUsedByOtherApps, boolean isDexFilePublic) {
+ return new AutoValue_DexUseManagerLocal_DetailedSecondaryDexInfo(dexPath, userHandle,
+ displayClassLoaderContext, Collections.unmodifiableSet(abiNames),
+ Collections.unmodifiableSet(loaders), isUsedByOtherApps, isDexFilePublic);
+ }
+
+ /**
+ * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+ * (S_IROTH).
+ */
+ public abstract boolean isDexFilePublic();
+ }
+
+ private static class DexUse {
+ @NonNull Map<String, PackageDexUse> mPackageDexUseByOwningPackageName = new HashMap<>();
+
+ void toProto(@NonNull DexUseProto.Builder builder) {
+ for (var entry : mPackageDexUseByOwningPackageName.entrySet()) {
+ var packageBuilder =
+ PackageDexUseProto.newBuilder().setOwningPackageName(entry.getKey());
+ entry.getValue().toProto(packageBuilder);
+ builder.addPackageDexUse(packageBuilder);
+ }
+ }
+
+ void fromProto(@NonNull DexUseProto proto) {
+ for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) {
+ var packageDexUse = new PackageDexUse();
+ packageDexUse.fromProto(packageProto);
+ mPackageDexUseByOwningPackageName.put(
+ Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse);
+ }
+ }
+ }
+
+ private static class PackageDexUse {
+ /**
+ * The keys are absolute paths to primary dex files of the owning package (the base APK and
+ * split APKs).
+ */
+ @NonNull Map<String, PrimaryDexUse> mPrimaryDexUseByDexFile = new HashMap<>();
+
+ /**
+ * The keys are absolute paths to secondary dex files of the owning package (the APKs and
+ * JARs in CE and DE directories).
+ */
+ @NonNull Map<String, SecondaryDexUse> mSecondaryDexUseByDexFile = new HashMap<>();
+
+ void toProto(@NonNull PackageDexUseProto.Builder builder) {
+ for (var entry : mPrimaryDexUseByDexFile.entrySet()) {
+ var primaryBuilder = PrimaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+ entry.getValue().toProto(primaryBuilder);
+ builder.addPrimaryDexUse(primaryBuilder);
+ }
+ for (var entry : mSecondaryDexUseByDexFile.entrySet()) {
+ var secondaryBuilder = SecondaryDexUseProto.newBuilder().setDexFile(entry.getKey());
+ entry.getValue().toProto(secondaryBuilder);
+ builder.addSecondaryDexUse(secondaryBuilder);
+ }
+ }
+
+ void fromProto(@NonNull PackageDexUseProto proto) {
+ for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) {
+ var primaryDexUse = new PrimaryDexUse();
+ primaryDexUse.fromProto(primaryProto);
+ mPrimaryDexUseByDexFile.put(
+ Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse);
+ }
+ for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) {
+ var secondaryDexUse = new SecondaryDexUse();
+ secondaryDexUse.fromProto(secondaryProto);
+ mSecondaryDexUseByDexFile.put(
+ Utils.assertNonEmpty(secondaryProto.getDexFile()), secondaryDexUse);
+ }
+ }
+ }
+
+ private static class PrimaryDexUse {
+ @NonNull Set<DexLoader> mLoaders = new HashSet<>();
+
+ void toProto(@NonNull PrimaryDexUseProto.Builder builder) {
+ for (DexLoader loader : mLoaders) {
+ builder.addRecord(PrimaryDexUseRecordProto.newBuilder()
+ .setLoadingPackageName(loader.loadingPackageName())
+ .setIsolatedProcess(loader.isolatedProcess()));
+ }
+ }
+
+ void fromProto(@NonNull PrimaryDexUseProto proto) {
+ for (PrimaryDexUseRecordProto recordProto : proto.getRecordList()) {
+ mLoaders.add(
+ DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+ recordProto.getIsolatedProcess()));
+ }
+ }
+ }
+
+ private static class SecondaryDexUse {
+ @Nullable UserHandle mUserHandle = null;
+ @NonNull Map<DexLoader, SecondaryDexUseRecord> mRecordByLoader = new HashMap<>();
+
+ void toProto(@NonNull SecondaryDexUseProto.Builder builder) {
+ builder.setUserId(Int32Value.newBuilder().setValue(mUserHandle.getIdentifier()));
+ for (var entry : mRecordByLoader.entrySet()) {
+ var recordBuilder =
+ SecondaryDexUseRecordProto.newBuilder()
+ .setLoadingPackageName(entry.getKey().loadingPackageName())
+ .setIsolatedProcess(entry.getKey().isolatedProcess());
+ entry.getValue().toProto(recordBuilder);
+ builder.addRecord(recordBuilder);
+ }
+ }
+
+ void fromProto(@NonNull SecondaryDexUseProto proto) {
+ Utils.check(proto.hasUserId());
+ mUserHandle = UserHandle.of(proto.getUserId().getValue());
+ for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) {
+ var record = new SecondaryDexUseRecord();
+ record.fromProto(recordProto);
+ mRecordByLoader.put(
+ DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()),
+ recordProto.getIsolatedProcess()),
+ record);
+ }
+ }
+ }
+
+ /**
+ * Represents an entity that loads a dex file.
+ *
+ * @hide
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class DexLoader {
+ static DexLoader create(@NonNull String loadingPackageName, boolean isolatedProcess) {
+ return new AutoValue_DexUseManagerLocal_DexLoader(loadingPackageName, isolatedProcess);
+ }
+
+ abstract @NonNull String loadingPackageName();
+
+ /** @see Process#isIsolatedUid(int) */
+ abstract boolean isolatedProcess();
+ }
+
+ private static class 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;
+
+ void toProto(@NonNull SecondaryDexUseRecordProto.Builder builder) {
+ builder.setClassLoaderContext(mClassLoaderContext).setAbiName(mAbiName);
+ }
+
+ void fromProto(@NonNull SecondaryDexUseRecordProto proto) {
+ mClassLoaderContext = Utils.assertNonEmpty(proto.getClassLoaderContext());
+ mAbiName = Utils.assertNonEmpty(proto.getAbiName());
+ }
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull
+ public IArtd getArtd() {
+ return Utils.getArtd();
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
new file mode 100644
index 0000000..55d92c6
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public class PrimaryDexOptimizer extends DexOptimizer<DetailedPrimaryDexInfo> {
+ private static final String TAG = "PrimaryDexOptimizer";
+
+ private final int mSharedGid;
+
+ public PrimaryDexOptimizer(@NonNull Context context, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+ }
+
+ @VisibleForTesting
+ public PrimaryDexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ super(injector, pkgState, pkg, params, cancellationSignal);
+
+ mSharedGid = UserHandle.getSharedAppGid(pkgState.getAppId());
+ if (mSharedGid < 0) {
+ throw new IllegalStateException(
+ String.format("Unable to get shared gid for package '%s' (app ID: %d)",
+ pkgState.getPackageName(), pkgState.getAppId()));
+ }
+ }
+
+ @Override
+ protected boolean isInDalvikCache() {
+ return Utils.isInDalvikCache(mPkgState);
+ }
+
+ @Override
+ @NonNull
+ protected List<DetailedPrimaryDexInfo> getDexInfoList() {
+ return PrimaryDexUtils.getDetailedDexInfo(mPkgState, mPkg);
+ }
+
+ @Override
+ protected boolean isOptimizable(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ if (!dexInfo.hasCode()) {
+ return false;
+ }
+ if ((mParams.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+ return Objects.equals(mParams.getSplitName(), dexInfo.splitName());
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean needsToBeShared(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return isSharedLibrary()
+ || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps(
+ mPkgState.getPackageName(), dexInfo.dexPath());
+ }
+
+ @Override
+ protected boolean isDexFilePublic(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ // The filesystem permission of a primary dex file always has the S_IROTH bit. In practice,
+ // the accessibility is enforced by Application Sandbox, not filesystem permission.
+ return true;
+ }
+
+ @Override
+ @Nullable
+ protected ProfilePath initReferenceProfile(@NonNull DetailedPrimaryDexInfo dexInfo)
+ throws RemoteException {
+ OutputProfile output = buildOutputProfile(dexInfo, true /* isPublic */);
+
+ ProfilePath prebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath());
+ try {
+ // If the APK is really a prebuilt one, rewriting the profile is unnecessary because the
+ // dex location is known at build time and is correctly set in the profile header.
+ // However, the APK can also be an installed one, in which case partners may place a
+ // profile file next to the APK at install time. Rewriting the profile in the latter
+ // case is necessary.
+ if (mInjector.getArtd().copyAndRewriteProfile(
+ prebuiltProfile, output, dexInfo.dexPath())) {
+ return ProfilePath.tmpProfilePath(output.profilePath);
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ "Failed to use prebuilt profile "
+ + AidlUtils.toString(output.profilePath.finalPath),
+ e);
+ }
+
+ ProfilePath dmProfile = AidlUtils.buildProfilePathForDm(dexInfo.dexPath());
+ try {
+ if (mInjector.getArtd().copyAndRewriteProfile(dmProfile, output, dexInfo.dexPath())) {
+ return ProfilePath.tmpProfilePath(output.profilePath);
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ "Failed to use profile in dex metadata file "
+ + AidlUtils.toString(output.profilePath.finalPath),
+ e);
+ }
+
+ return null;
+ }
+
+ @Override
+ @NonNull
+ protected PermissionSettings getPermissionSettings(
+ @NonNull DetailedPrimaryDexInfo dexInfo, boolean canBePublic) {
+ // The files and directories should belong to the system so that Package Manager can manage
+ // them (e.g., move them around).
+ // We don't need the "read" bit for "others" on the directories because others only need to
+ // access the files in the directories, but they don't need to "ls" the directories.
+ FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID /* uid */,
+ Process.SYSTEM_UID /* gid */, false /* isOtherReadable */,
+ true /* isOtherExecutable */);
+ FsPermission fileFsPermission = AidlUtils.buildFsPermission(
+ Process.SYSTEM_UID /* uid */, mSharedGid /* gid */, canBePublic);
+ // For primary dex, we can use the default SELinux context.
+ SeContext seContext = null;
+ return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+ }
+
+ @Override
+ @NonNull
+ protected List<Abi> getAllAbis(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return Utils.getAllAbis(mPkgState);
+ }
+
+ @Override
+ @NonNull
+ protected ProfilePath buildRefProfilePath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return PrimaryDexUtils.buildRefProfilePath(mPkgState, dexInfo);
+ }
+
+ @Override
+ protected boolean isAppImageAllowed(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ // Only allow app image for the base APK because having multiple app images is not
+ // supported.
+ // Additionally, disable app images if the app requests for the splits to be loaded in
+ // isolation because app images are unsupported for multiple class loaders (b/72696798).
+ // TODO(jiakaiz): Investigate whether this is still the best choice today.
+ return dexInfo.splitName() == null && !PrimaryDexUtils.isIsolatedSplitLoading(mPkg);
+ }
+
+ @Override
+ @NonNull
+ protected OutputProfile buildOutputProfile(
+ @NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) {
+ return PrimaryDexUtils.buildOutputProfile(
+ mPkgState, dexInfo, Process.SYSTEM_UID, mSharedGid, isPublic);
+ }
+
+ @Override
+ @NonNull
+ protected List<ProfilePath> getCurProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), mPkgState, dexInfo);
+ }
+
+ @Override
+ @Nullable
+ protected DexMetadataPath buildDmPath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return AidlUtils.buildDexMetadataPath(dexInfo.dexPath());
+ }
+
+ private boolean isSharedLibrary() {
+ // TODO(b/242688548): Package manager should provide a better API for this.
+ return !TextUtils.isEmpty(mPkg.getSdkLibraryName())
+ || !TextUtils.isEmpty(mPkg.getStaticSharedLibraryName())
+ || !mPkg.getLibraryNames().isEmpty();
+ }
+}
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..5af9f0a
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.SharedLibrary;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** @hide */
+public class PrimaryDexUtils {
+ 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.getUsesLibraries());
+
+ boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg);
+
+ for (int i = 1; i < dexInfos.size(); i++) {
+ var dexInfoBuilder = dexInfos.get(i);
+ File splitDexFile = new File(dexInfoBuilder.mSplit.getPath());
+ if (!splitDexFile.getParent().equals(baseDexFile.getParent())) {
+ throw new IllegalStateException(
+ "Split APK and base APK are in different directories: "
+ + splitDexFile.getParent() + " != " + baseDexFile.getParent());
+ }
+ dexInfoBuilder.mRelativeDexPath = splitDexFile.getName();
+ if (isIsolatedSplitLoading && dexInfoBuilder.mSplit.isHasCode()) {
+ dexInfoBuilder.mClassLoaderName = dexInfoBuilder.mSplit.getClassLoaderName();
+
+ List<AndroidPackageSplit> dependencies = dexInfoBuilder.mSplit.getDependencies();
+ if (!Utils.isEmpty(dependencies)) {
+ // We only care about the first dependency because it is the parent split. The
+ // rest are configuration splits, which we don't care.
+ AndroidPackageSplit dependency = dependencies.get(0);
+ for (var dexInfo : dexInfos) {
+ if (Objects.equals(dexInfo.mSplit, dependency)) {
+ dexInfoBuilder.mSplitDependency = dexInfo;
+ break;
+ }
+ }
+
+ if (dexInfoBuilder.mSplitDependency == null) {
+ throw new IllegalStateException(
+ "Split dependency not found for " + splitDexFile);
+ }
+ }
+ }
+ }
+
+ if (isIsolatedSplitLoading) {
+ computeClassLoaderContextsIsolated(dexInfos);
+ } else {
+ computeClassLoaderContexts(dexInfos);
+ }
+
+ return dexInfos;
+ }
+
+ /**
+ * Computes class loader context for an app that didn't request isolated split loading. Stores
+ * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+ *
+ * In this case, all the splits will be loaded in the base apk class loader (in the order of
+ * their definition).
+ *
+ * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK is
+ * `CLN[base.apk, split_0.apk, ..., split_n-1.apk]{shared-libraries}`; where `CLN` is the
+ * class loader name for the base APK.
+ */
+ private static void computeClassLoaderContexts(@NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+ String baseClassLoaderName = dexInfos.get(0).mClassLoaderName;
+ String sharedLibrariesContext = dexInfos.get(0).mSharedLibrariesContext;
+ List<String> classpath = new ArrayList<>();
+ for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+ if (dexInfo.mSplit.isHasCode()) {
+ dexInfo.mClassLoaderContext = encodeClassLoader(baseClassLoaderName, classpath,
+ null /* parentContext */, sharedLibrariesContext);
+ }
+ // Note that the splits with no code are not removed from the classpath computation.
+ // I.e., split_n might get the split_n-1 in its classpath dependency even if split_n-1
+ // has no code.
+ // The splits with no code do not matter for the runtime which ignores APKs without code
+ // when doing the classpath checks. As such we could actually filter them but we don't
+ // do it in order to keep consistency with how the apps are loaded.
+ classpath.add(dexInfo.mRelativeDexPath);
+ }
+ }
+
+ /**
+ * Computes class loader context for an app that requested for isolated split loading. Stores
+ * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}.
+ *
+ * In this case, each split will be loaded with a separate class loader, whose context is a
+ * chain formed from inter-split dependencies.
+ *
+ * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK that
+ * depends on the base APK is `CLN_n[];CLN[base.apk]{shared-libraries}`; the CLC for the n-th
+ * split APK that depends on the m-th split APK is
+ * `CLN_n[];CLN_m[split_m.apk];...;CLN[base.apk]{shared-libraries}`; where `CLN` is the base
+ * class loader name for the base APK, `CLN_i` is the class loader name for the i-th split APK,
+ * and `...` represents the ancestors along the dependency chain.
+ *
+ * Specially, if a split does not have any dependency, the CLC for it is `CLN_n[]`.
+ */
+ private static void computeClassLoaderContextsIsolated(
+ @NonNull List<PrimaryDexInfoBuilder> dexInfos) {
+ for (PrimaryDexInfoBuilder dexInfo : dexInfos) {
+ if (dexInfo.mSplit.isHasCode()) {
+ dexInfo.mClassLoaderContext = encodeClassLoader(dexInfo.mClassLoaderName,
+ null /* classpath */, getParentContextRecursive(dexInfo),
+ dexInfo.mSharedLibrariesContext);
+ }
+ }
+ }
+
+ /**
+ * Computes the parent class loader context, recursively. Caches results in {@link
+ * PrimaryDexInfoBuilder#mContextForChildren}.
+ */
+ @Nullable
+ private static String getParentContextRecursive(@NonNull PrimaryDexInfoBuilder dexInfo) {
+ if (dexInfo.mSplitDependency == null) {
+ return null;
+ }
+ PrimaryDexInfoBuilder parent = dexInfo.mSplitDependency;
+ if (parent.mContextForChildren == null) {
+ parent.mContextForChildren =
+ encodeClassLoader(parent.mClassLoaderName, List.of(parent.mRelativeDexPath),
+ getParentContextRecursive(parent), parent.mSharedLibrariesContext);
+ }
+ return parent.mContextForChildren;
+ }
+
+ /**
+ * Returns class loader context in the format of
+ * `CLN[classpath...]{share-libraries};parent-context`, where `CLN` is the class loader name.
+ */
+ @NonNull
+ private static String encodeClassLoader(@Nullable String classLoaderName,
+ @Nullable List<String> classpath, @Nullable String parentContext,
+ @Nullable String sharedLibrariesContext) {
+ StringBuilder classLoaderContext = new StringBuilder();
+
+ classLoaderContext.append(encodeClassLoaderName(classLoaderName));
+
+ classLoaderContext.append(
+ "[" + (classpath != null ? String.join(":", classpath) : "") + "]");
+
+ if (!TextUtils.isEmpty(sharedLibrariesContext)) {
+ classLoaderContext.append(sharedLibrariesContext);
+ }
+
+ if (!TextUtils.isEmpty(parentContext)) {
+ classLoaderContext.append(";" + parentContext);
+ }
+
+ return classLoaderContext.toString();
+ }
+
+ @NonNull
+ private static String encodeClassLoaderName(@Nullable String classLoaderName) {
+ // `PathClassLoader` and `DexClassLoader` are grouped together because they have the same
+ // behavior. For null values we default to "PCL". This covers the case where a package does
+ // not specify any value for its class loader.
+ if (classLoaderName == null || PathClassLoader.class.getName().equals(classLoaderName)
+ || DexClassLoader.class.getName().equals(classLoaderName)) {
+ return "PCL";
+ } else if (DelegateLastClassLoader.class.getName().equals(classLoaderName)) {
+ return "DLC";
+ } else {
+ throw new IllegalStateException("Unsupported classLoaderName: " + classLoaderName);
+ }
+ }
+
+ /**
+ * Returns shared libraries context in the format of
+ * `{PCL[library_1_dex_1.jar:library_1_dex_2.jar:...]{library_1-dependencies}#PCL[
+ * library_1_dex_2.jar:library_2_dex_2.jar:...]{library_2-dependencies}#...}`.
+ */
+ @Nullable
+ private static String encodeSharedLibraries(@Nullable List<SharedLibrary> sharedLibraries) {
+ if (Utils.isEmpty(sharedLibraries)) {
+ return null;
+ }
+ return sharedLibraries.stream()
+ .map(library
+ -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(),
+ null /* parentContext */,
+ encodeSharedLibraries(library.getDependencies())))
+ .collect(Collectors.joining("#", "{", "}"));
+ }
+
+ public static boolean isIsolatedSplitLoading(@NonNull AndroidPackage pkg) {
+ return pkg.isIsolatedSplitLoading() && pkg.getSplits().size() > 1;
+ }
+
+ @NonNull
+ public static ProfilePath buildRefProfilePath(
+ @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+ String profileName = getProfileName(dexInfo.splitName());
+ return AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName);
+ }
+
+ @NonNull
+ public static OutputProfile buildOutputProfile(@NonNull PackageState pkgState,
+ @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic) {
+ String profileName = getProfileName(dexInfo.splitName());
+ return AidlUtils.buildOutputProfileForPrimary(
+ pkgState.getPackageName(), profileName, uid, gid, isPublic);
+ }
+
+ @NonNull
+ public static List<ProfilePath> getCurProfiles(@NonNull UserManager userManager,
+ @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) {
+ List<ProfilePath> profiles = new ArrayList<>();
+ for (UserHandle handle : userManager.getUserHandles(true /* excludeDying */)) {
+ int userId = handle.getIdentifier();
+ PackageUserState userState = pkgState.getStateForUser(handle);
+ if (userState.isInstalled()) {
+ profiles.add(AidlUtils.buildProfilePathForPrimaryCur(
+ userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName())));
+ }
+ }
+ return profiles;
+ }
+
+ @NonNull
+ private static String getProfileName(@Nullable String splitName) {
+ return splitName == null ? "primary" : splitName + ".split";
+ }
+
+ /** Basic information about a primary dex file (either the base APK or a split APK). */
+ @Immutable
+ public static class PrimaryDexInfo {
+ private final @NonNull AndroidPackageSplit mSplit;
+
+ PrimaryDexInfo(@NonNull AndroidPackageSplit split) {
+ mSplit = split;
+ }
+
+ /** The path to the dex file. */
+ public @NonNull String dexPath() {
+ return mSplit.getPath();
+ }
+
+ /** True if the dex file has code. */
+ public boolean hasCode() {
+ return mSplit.isHasCode();
+ }
+
+ /** The name of the split, or null for base APK. */
+ public @Nullable String splitName() {
+ return mSplit.getName();
+ }
+ }
+
+ /**
+ * Detailed information about a primary dex file (either the base APK or a split APK). It
+ * contains the class loader context in addition to what is in {@link PrimaryDexInfo}, but
+ * producing it requires {@link PackageState}.
+ */
+ @Immutable
+ public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo {
+ private final @Nullable String mClassLoaderContext;
+
+ DetailedPrimaryDexInfo(
+ @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) {
+ super(split);
+ mClassLoaderContext = classLoaderContext;
+ }
+
+ /**
+ * A string describing the structure of the class loader that the dex file is loaded with.
+ */
+ public @Nullable String classLoaderContext() {
+ return mClassLoaderContext;
+ }
+ }
+
+ private static class PrimaryDexInfoBuilder {
+ @NonNull AndroidPackageSplit mSplit;
+ @Nullable String mRelativeDexPath = null;
+ @Nullable String mClassLoaderContext = null;
+ @Nullable String mClassLoaderName = null;
+ @Nullable PrimaryDexInfoBuilder mSplitDependency = null;
+ /** The class loader context of the shared libraries. Only applicable for the base APK. */
+ @Nullable String mSharedLibrariesContext = null;
+ /** The class loader context for children to use when this dex file is used as a parent. */
+ @Nullable String mContextForChildren = null;
+
+ PrimaryDexInfoBuilder(@NonNull AndroidPackageSplit split) {
+ mSplit = split;
+ }
+
+ PrimaryDexInfo build() {
+ return new PrimaryDexInfo(mSplit);
+ }
+
+ DetailedPrimaryDexInfo buildDetailed() {
+ return new DetailedPrimaryDexInfo(mSplit, mClassLoaderContext);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
new file mode 100644
index 0000000..98c82d8
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.pm.PackageManagerLocal;
+
+import dalvik.system.DexFile;
+
+import java.util.Set;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Maps a compilation reason to a compiler filter and a priority class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ReasonMapping {
+ private ReasonMapping() {}
+
+ /** Optimizing apps on the first boot. */
+ public static final String REASON_FIRST_BOOT = "first-boot";
+ /** Optimizing apps on the next boot after an OTA. */
+ public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota";
+ /** Installing an app after user presses the "install"/"update" button. */
+ public static final String REASON_INSTALL = "install";
+ /** Optimizing apps in the background. */
+ public static final String REASON_BG_DEXOPT = "bg-dexopt";
+ /** Invoked by cmdline. */
+ public static final String REASON_CMDLINE = "cmdline";
+ /** Downgrading the compiler filter when an app is not used for a long time. */
+ public static final String REASON_INACTIVE = "inactive";
+
+ // Reasons for Play Install Hints (go/install-hints).
+ public static final String REASON_INSTALL_FAST = "install-fast";
+ public static final String REASON_INSTALL_BULK = "install-bulk";
+ public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary";
+ public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded";
+ public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
+ "install-bulk-secondary-downgraded";
+
+ /** @hide */
+ public static final Set<String> REASONS_FOR_INSTALL = Set.of(REASON_INSTALL,
+ REASON_INSTALL_FAST, REASON_INSTALL_BULK, REASON_INSTALL_BULK_SECONDARY,
+ REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+
+ /**
+ * Reasons for
+ * {@link ArtManagerLocal#optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}.
+ *
+ * @hide
+ */
+ // clang-format off
+ @StringDef(prefix = "REASON_", value = {
+ REASON_FIRST_BOOT,
+ REASON_BOOT_AFTER_OTA,
+ REASON_BG_DEXOPT,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BatchOptimizeReason {}
+
+ /**
+ * 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:
+ 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 optimization ({@link
+ * ArtManagerLocal#optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}), or 1 if the
+ * system property is not found or cannot be parsed.
+ *
+ * @hide
+ */
+ public static int getConcurrencyForReason(@NonNull @BatchOptimizeReason String reason) {
+ return SystemProperties.getInt("pm.dexopt." + reason + ".concurrency", 1 /* def */);
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
new file mode 100644
index 0000000..3f8d966
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.List;
+
+/** @hide */
+public class SecondaryDexOptimizer extends DexOptimizer<DetailedSecondaryDexInfo> {
+ private static final String TAG = "SecondaryDexOptimizer";
+
+ public SecondaryDexOptimizer(@NonNull Context context, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+ }
+
+ @VisibleForTesting
+ public SecondaryDexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams 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 isOptimizable(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return true;
+ }
+
+ @Override
+ protected boolean needsToBeShared(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return dexInfo.isUsedByOtherApps();
+ }
+
+ @Override
+ protected boolean isDexFilePublic(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return dexInfo.isDexFilePublic();
+ }
+
+ @Override
+ @Nullable
+ protected ProfilePath initReferenceProfile(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ // A secondary dex file doesn't have any external profile to use.
+ return null;
+ }
+
+ @Override
+ @NonNull
+ protected PermissionSettings getPermissionSettings(
+ @NonNull DetailedSecondaryDexInfo dexInfo, boolean canBePublic) {
+ int uid = getUid(dexInfo);
+ // We need the "execute" bit for "others" even though `canBePublic` is false because the
+ // directory can contain other artifacts that needs to be public.
+ // We don't need the "read" bit for "others" on the directories because others only need to
+ // access the files in the directories, but they don't need to "ls" the directories.
+ FsPermission dirFsPermission = AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */,
+ false /* isOtherReadable */, true /* isOtherExecutable */);
+ FsPermission fileFsPermission =
+ AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */, canBePublic);
+ SeContext seContext = AidlUtils.buildSeContext(
+ new com.android.server.art.wrapper.PackageState(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..5083428
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -0,0 +1,294 @@
+/*
+ * 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.apphibernation.AppHibernationManager;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.server.art.model.OptimizeParams;
+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.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.stream.Collectors;
+
+/** @hide */
+public final class Utils {
+ public static final String PLATFORM_PACKAGE_NAME = "android";
+
+ private Utils() {}
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static <T> boolean isEmpty(@Nullable Collection<T> array) {
+ return array == null || array.isEmpty();
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static <T> boolean isEmpty(@Nullable SparseArray<T> array) {
+ return array == null || array.size() == 0;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /** Returns the ABI information for the package. */
+ @NonNull
+ public static List<Abi> getAllAbis(@NonNull PackageState pkgState) {
+ List<Abi> abis = new ArrayList<>();
+ abis.add(getPrimaryAbi(pkgState));
+ String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
+ String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+ if (pkgSecondaryCpuAbi != null) {
+ Utils.check(pkgState.getPrimaryCpuAbi() != null);
+ String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi));
+ abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */));
+ }
+ // Primary and secondary ABIs should be guaranteed to have different ISAs.
+ if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) {
+ throw new IllegalStateException(String.format(
+ "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')",
+ pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name()));
+ }
+ return abis;
+ }
+
+ /** Returns the ABI information for the ABIs with the given names. */
+ @NonNull
+ public static List<Abi> getAllAbisForNames(
+ @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
+ Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
+ return abiNames.stream()
+ .map(name
+ -> Abi.create(name, VMRuntime.getInstructionSet(name),
+ name.equals(pkgPrimaryAbi.name())))
+ .collect(Collectors.toList());
+ }
+
+ @NonNull
+ public static Abi getPrimaryAbi(@NonNull PackageState pkgState) {
+ String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
+ if (primaryCpuAbi != null) {
+ String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi));
+ return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */);
+ }
+ // This is the most common case. The package manager can't infer the ABIs, probably because
+ // the package doesn't contain any native library. The app is launched with the device's
+ // preferred ABI.
+ String preferredAbi = Constants.getPreferredAbi();
+ return Abi.create(
+ preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */);
+ }
+
+ /**
+ * If the given ISA isn't native to the device, returns the ISA that the native bridge
+ * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually
+ * launched with and therefore the ISA that should be used to compile the app.
+ */
+ @NonNull
+ private static String getTranslatedIsa(@NonNull String isa) {
+ String abi64 = Constants.getNative64BitAbi();
+ String abi32 = Constants.getNative32BitAbi();
+ if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64)))
+ || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) {
+ return isa;
+ }
+ String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa);
+ if (TextUtils.isEmpty(translatedIsa)) {
+ throw new IllegalStateException(String.format("Unsupported isa '%s'", isa));
+ }
+ return translatedIsa;
+ }
+
+ @NonNull
+ private static String nativeIsaToAbi(@NonNull String isa) {
+ String abi64 = Constants.getNative64BitAbi();
+ if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) {
+ return abi64;
+ }
+ String abi32 = Constants.getNative32BitAbi();
+ if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) {
+ return abi32;
+ }
+ throw new IllegalStateException(String.format("Non-native isa '%s'", isa));
+ }
+
+ @NonNull
+ public static boolean isInDalvikCache(@NonNull PackageState pkg) {
+ return pkg.isSystem() && !pkg.isUpdatedSystemApp();
+ }
+
+ /** Returns true if the given string is a valid compiler filter. */
+ public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) {
+ if (compilerFilter.equals(OptimizeParams.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(execute(executor, Executors.callable(runnable)));
+ }
+
+ public static <T> Future<T> execute(@NonNull Executor executor, @NonNull Callable<T> callable) {
+ var future = new FutureTask<T>(callable);
+ executor.execute(future);
+ return future;
+ }
+
+ 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 optimizable.
+ *
+ * @param appHibernationManager the {@link AppHibernationManager} instance for checking
+ * hibernation status, or null to skip the check
+ */
+ public static boolean canOptimizePackage(
+ @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) {
+ // An APEX has a uid of -1.
+ // TODO(b/256637152): Consider using `isApex` instead.
+ if (pkgState.getAppId() <= 0) {
+ return false;
+ }
+
+ // "android" is a special package that represents the platform, not an app.
+ if (pkgState.getPackageName().equals(Utils.PLATFORM_PACKAGE_NAME)) {
+ return false;
+ }
+
+ AndroidPackage pkg = pkgState.getAndroidPackage();
+ if (pkg == null || !pkg.getSplits().get(0).isHasCode()) {
+ return false;
+ }
+
+ // We do not dexopt unused packages.
+ // If `appHibernationManager` is null, the caller's intention is to skip the check.
+ if (appHibernationManager != null
+ && appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
+ && appHibernationManager.isOatArtifactDeletionEnabled()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @AutoValue
+ public abstract static class Abi {
+ static @NonNull Abi create(
+ @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) {
+ return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi);
+ }
+
+ // The ABI name. E.g., "arm64-v8a".
+ abstract @NonNull String name();
+
+ // The instruction set name. E.g., "arm64".
+ abstract @NonNull String isa();
+
+ abstract boolean isPrimaryAbi();
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/ArtFlags.java b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
new file mode 100644
index 0000000..c35b2c2
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
@@ -0,0 +1,209 @@
+/*
+ * 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.SystemApi;
+import android.app.job.JobScheduler;
+
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.PriorityClass;
+import com.android.server.pm.PackageManagerLocal;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtFlags {
+ // Common flags.
+
+ /** Whether the operation is applied for primary dex'es. */
+ public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0;
+ /** Whether the operation is applied for secondary dex'es. */
+ public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1;
+
+ // Flags specific to `optimizePackage`.
+
+ /** Whether to optimize dependency libraries as well. */
+ public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 1 << 2;
+ /**
+ * Whether the intention is to downgrade the compiler filter. If true, the optimization will
+ * be skipped if the target compiler filter is better than or equal to the compiler filter
+ * of the existing optimized artifacts, or optimized artifacts do not exist.
+ */
+ public static final int FLAG_SHOULD_DOWNGRADE = 1 << 3;
+ /**
+ * Whether to force optimization. If true, the optimization will be performed regardless of
+ * any existing optimized artifacts.
+ */
+ public static final int FLAG_FORCE = 1 << 4;
+ /**
+ * If set, the optimization will be performed for a single split. Otherwise, the optimization
+ * will be performed for all splits. {@link OptimizeParams.Builder#setSplitName()} can be used
+ * to specify the split to optimize.
+ *
+ * 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;
+
+ /**
+ * Flags for
+ * {@link ArtManagerLocal#deleteOptimizedArtifacts(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 DeleteFlags {}
+
+ /**
+ * Default flags that are used when
+ * {@link ArtManagerLocal#deleteOptimizedArtifacts(PackageManagerLocal.FilteredSnapshot,
+ * String)}
+ * is called.
+ * Value: {@link #FLAG_FOR_PRIMARY_DEX}.
+ */
+ public static @DeleteFlags int defaultDeleteFlags() {
+ return FLAG_FOR_PRIMARY_DEX;
+ }
+
+ /**
+ * Flags for
+ * {@link ArtManagerLocal#getOptimizationStatus(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#getOptimizationStatus(PackageManagerLocal.FilteredSnapshot, String)}
+ * is called.
+ * Value: {@link #FLAG_FOR_PRIMARY_DEX}.
+ */
+ public static @GetStatusFlags int defaultGetStatusFlags() {
+ return FLAG_FOR_PRIMARY_DEX;
+ }
+
+ /**
+ * Flags for {@link OptimizeParams}.
+ *
+ * @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,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OptimizeFlags {}
+
+ /**
+ * Default flags that are used when
+ * {@link OptimizeParams.Builder#Builder(String)} is called.
+ * Value: {@link #FLAG_FOR_PRIMARY_DEX}.
+ */
+ public static @OptimizeFlags int defaultOptimizeFlags() {
+ return FLAG_FOR_PRIMARY_DEX;
+ }
+
+ // Keep in sync with `PriorityClass` except for `PRIORITY_NONE`.
+
+ /**
+ * Initial value. Not expected.
+ *
+ * @hide
+ */
+ public static final int PRIORITY_NONE = -1;
+ /** Indicates that the operation blocks boot. */
+ public static final int PRIORITY_BOOT = PriorityClass.BOOT;
+ /**
+ * Indicates that a human is waiting on the result and the operation is more latency sensitive
+ * than usual.
+ */
+ public static final int PRIORITY_INTERACTIVE_FAST = PriorityClass.INTERACTIVE_FAST;
+ /** Indicates that a human is waiting on the result. */
+ public static final int PRIORITY_INTERACTIVE = PriorityClass.INTERACTIVE;
+ /** Indicates that the operation runs in background. */
+ public static final int PRIORITY_BACKGROUND = PriorityClass.BACKGROUND;
+
+ /**
+ * Indicates the priority of an operation. The value affects the resource usage and the process
+ * priority. A higher value may result in faster execution but may consume more resources and
+ * compete for resources with other processes.
+ *
+ * @hide
+ */
+ // clang-format off
+ @IntDef(prefix = "PRIORITY_", value = {
+ PRIORITY_NONE,
+ PRIORITY_BOOT,
+ PRIORITY_INTERACTIVE_FAST,
+ PRIORITY_INTERACTIVE,
+ PRIORITY_BACKGROUND,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PriorityClassApi {}
+
+ /** The job has been successfully scheduled. */
+ public static final int SCHEDULE_SUCCESS = 0;
+
+ /** @see JobScheduler#RESULT_FAILURE */
+ public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1;
+
+ /** The job is disabled by the system property {@code pm.dexopt.disable_bg_dexopt}. */
+ public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2;
+
+ /**
+ * Indicates the result of scheduling a background dexopt job.
+ *
+ * @hide
+ */
+ // clang-format off
+ @IntDef(prefix = "SCHEDULE_", value = {
+ SCHEDULE_SUCCESS,
+ SCHEDULE_JOB_SCHEDULER_FAILURE,
+ SCHEDULE_DISABLED_BY_SYSPROP,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScheduleStatus {}
+
+ private ArtFlags() {}
+}
diff --git a/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java b/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java
new file mode 100644
index 0000000..f0025e4
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java
@@ -0,0 +1,85 @@
+/*
+ * 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 BatchOptimizeParams {
+ public static final class Builder {
+ private @NonNull List<String> mPackageNames; // This is assumed immutable.
+ private @NonNull OptimizeParams mOptimizeParams;
+
+ /** @hide */
+ public Builder(@NonNull List<String> defaultPackages,
+ @NonNull OptimizeParams defaultOptimizeParams) {
+ mPackageNames = defaultPackages; // The argument is assumed immutable.
+ mOptimizeParams = defaultOptimizeParams;
+ }
+
+ /**
+ * Sets the list of packages to optimize. The optimization 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 optimizing each package.
+ *
+ * If not called, the default params built from {@link OptimizeParams#Builder(String)} will
+ * be used.
+ */
+ @NonNull
+ public Builder setOptimizeParams(@NonNull OptimizeParams optimizeParams) {
+ mOptimizeParams = optimizeParams;
+ return this;
+ }
+
+ /** Returns the built object. */
+ @NonNull
+ public BatchOptimizeParams build() {
+ return new AutoValue_BatchOptimizeParams(mPackageNames, mOptimizeParams);
+ }
+ }
+
+ /** @hide */
+ protected BatchOptimizeParams() {}
+
+ /** The ordered list of packages to optimize. */
+ public abstract @NonNull List<String> getPackages();
+
+ /** The params for optimizing each package. */
+ public abstract @NonNull OptimizeParams getOptimizeParams();
+}
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..8b84bcc
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/Config.java
@@ -0,0 +1,123 @@
+/*
+ * 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.OptimizePackageDoneCallback;
+import static com.android.server.art.ArtManagerLocal.OptimizePackagesCallback;
+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#setOptimizePackagesCallback(Executor, OptimizePackagesCallback) */
+ @GuardedBy("this")
+ @Nullable
+ private Callback<OptimizePackagesCallback> mOptimizePackagesCallback = null;
+
+ /**
+ * @see ArtManagerLocal#setScheduleBackgroundDexoptJobCallback(Executor,
+ * ScheduleBackgroundDexoptJobCallback)
+ */
+ @GuardedBy("this")
+ @Nullable
+ private Callback<ScheduleBackgroundDexoptJobCallback> mScheduleBackgroundDexoptJobCallback =
+ null;
+
+ /**
+ * @see ArtManagerLocal#addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)
+ */
+ @GuardedBy("this")
+ @NonNull
+ private LinkedHashMap<OptimizePackageDoneCallback, Callback<OptimizePackageDoneCallback>>
+ mOptimizePackageDoneCallbacks = new LinkedHashMap<>();
+
+ public synchronized void setOptimizePackagesCallback(
+ @NonNull Executor executor, @NonNull OptimizePackagesCallback callback) {
+ mOptimizePackagesCallback = Callback.<OptimizePackagesCallback>create(callback, executor);
+ }
+
+ public synchronized void clearOptimizePackagesCallback() {
+ mOptimizePackagesCallback = null;
+ }
+
+ @Nullable
+ public synchronized Callback<OptimizePackagesCallback> getOptimizePackagesCallback() {
+ return mOptimizePackagesCallback;
+ }
+
+ public synchronized void setScheduleBackgroundDexoptJobCallback(
+ @NonNull Executor executor, @NonNull ScheduleBackgroundDexoptJobCallback callback) {
+ mScheduleBackgroundDexoptJobCallback =
+ Callback.<ScheduleBackgroundDexoptJobCallback>create(callback, executor);
+ }
+
+ public synchronized void clearScheduleBackgroundDexoptJobCallback() {
+ mScheduleBackgroundDexoptJobCallback = null;
+ }
+
+ @Nullable
+ public synchronized Callback<ScheduleBackgroundDexoptJobCallback>
+ getScheduleBackgroundDexoptJobCallback() {
+ return mScheduleBackgroundDexoptJobCallback;
+ }
+
+ public synchronized void addOptimizePackageDoneCallback(
+ @NonNull Executor executor, @NonNull OptimizePackageDoneCallback callback) {
+ if (mOptimizePackageDoneCallbacks.putIfAbsent(
+ callback, Callback.<OptimizePackageDoneCallback>create(callback, executor))
+ != null) {
+ throw new IllegalStateException("callback already added");
+ }
+ }
+
+ public synchronized void removeOptimizePackageDoneCallback(
+ @NonNull OptimizePackageDoneCallback callback) {
+ mOptimizePackageDoneCallbacks.remove(callback);
+ }
+
+ @NonNull
+ public synchronized List<Callback<OptimizePackageDoneCallback>>
+ getOptimizePackageDoneCallbacks() {
+ return new ArrayList<>(mOptimizePackageDoneCallbacks.values());
+ }
+
+ @AutoValue
+ public static abstract class Callback<T> {
+ public abstract @NonNull T get();
+ public abstract @NonNull Executor executor();
+ static <T> @NonNull Callback<T> create(@NonNull T callback, @NonNull Executor executor) {
+ return new AutoValue_Config_Callback<T>(callback, executor);
+ }
+ }
+}
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..fc40cbc
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
@@ -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.model;
+
+import android.annotation.SystemApi;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class DeleteResult {
+ private long mFreedBytes;
+
+ /** @hide */
+ public DeleteResult(long freedBytes) {
+ mFreedBytes = freedBytes;
+ }
+
+ /** The amount of the disk space freed by the deletion, in bytes. */
+ public long getFreedBytes() {
+ return mFreedBytes;
+ }
+}
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/OptimizationStatus.java b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
new file mode 100644
index 0000000..4abf4c3
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
@@ -0,0 +1,134 @@
+/*
+ * 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 optimization status of a package.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class OptimizationStatus {
+ /** @hide */
+ protected OptimizationStatus() {}
+
+ /** @hide */
+ public static @NonNull OptimizationStatus
+ create(@NonNull List<DexContainerFileOptimizationStatus> dexContainerFileOptimizationStatuses) {
+ return new AutoValue_OptimizationStatus(dexContainerFileOptimizationStatuses);
+ }
+
+ /**
+ * The statuses of the dex container file optimizations. Note that there can be multiple entries
+ * for the same dex container file, but for different ABIs.
+ */
+ @NonNull
+ public abstract List<DexContainerFileOptimizationStatus>
+ getDexContainerFileOptimizationStatuses();
+
+ /** Describes the optimization status of a dex container file. */
+ @Immutable
+ @AutoValue
+ public abstract static class DexContainerFileOptimizationStatus {
+ /** @hide */
+ protected DexContainerFileOptimizationStatus() {}
+
+ /** @hide */
+ public static @NonNull DexContainerFileOptimizationStatus create(
+ @NonNull String dexContainerFile, boolean isPrimaryAbi, @NonNull String abi,
+ @NonNull String compilerFilter, @NonNull String compilationReason,
+ @NonNull String locationDebugString) {
+ return new AutoValue_OptimizationStatus_DexContainerFileOptimizationStatus(
+ dexContainerFile, isPrimaryAbi, abi, compilerFilter, compilationReason,
+ locationDebugString);
+ }
+
+ /** The absolute path to the dex container file. */
+ public abstract @NonNull String getDexContainerFile();
+
+ /**
+ * If true, the optimization is for the primary ABI of the package (the ABI that the
+ * application is launched with). Otherwise, the optimization 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 optimization 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 optimized artifacts are valid. See
+ * https://source.android.com/docs/core/dalvik/configure#compilation_options.
+ * <li>{@code "run-from-apk"}, if the optimized artifacts do not exist.
+ * <li>{@code "run-from-apk-fallback"}, if the optimized 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 optimized 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
+ * OptimizeParams.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 optimized 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/OptimizeParams.java b/libartservice/service/java/com/android/server/art/model/OptimizeParams.java
new file mode 100644
index 0000000..175d900
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeParams.java
@@ -0,0 +1,215 @@
+/*
+ * 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.OptimizeFlags;
+import static com.android.server.art.model.ArtFlags.PriorityClassApi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public class OptimizeParams {
+ public static final class Builder {
+ private OptimizeParams mParams = new OptimizeParams();
+
+ /**
+ * Creates a builder.
+ *
+ * Uses default flags ({@link ArtFlags#defaultOptimizeFlags()}).
+ *
+ * @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.defaultOptimizeFlags());
+ }
+
+ /**
+ * Same as above, but allows to specify flags.
+ */
+ public Builder(@NonNull String reason, @OptimizeFlags int flags) {
+ mParams.mReason = reason;
+ setFlags(flags);
+ }
+
+ /** Replaces all flags with the given value. */
+ @NonNull
+ public Builder setFlags(@OptimizeFlags int value) {
+ mParams.mFlags = value;
+ return this;
+ }
+
+ /** Replaces the flags specified by the mask with the given value. */
+ @NonNull
+ public Builder setFlags(@OptimizeFlags int value, @OptimizeFlags 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 optimize, 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 OptimizeParams build() {
+ if (mParams.mReason.isEmpty()) {
+ throw new IllegalArgumentException("Reason must not be empty");
+ }
+
+ if (mParams.mCompilerFilter.isEmpty()) {
+ mParams.mCompilerFilter = ReasonMapping.getCompilerFilterForReason(mParams.mReason);
+ } else if (!Utils.isValidArtServiceCompilerFilter(mParams.mCompilerFilter)) {
+ throw new IllegalArgumentException(
+ "Invalid compiler filter '" + mParams.mCompilerFilter + "'");
+ }
+
+ if (mParams.mPriorityClass == ArtFlags.PRIORITY_NONE) {
+ mParams.mPriorityClass = ReasonMapping.getPriorityClassForReason(mParams.mReason);
+ } else if (mParams.mPriorityClass < 0 || mParams.mPriorityClass > 100) {
+ throw new IllegalArgumentException("Invalid priority class "
+ + mParams.mPriorityClass + ". Must be between 0 and 100");
+ }
+
+ if ((mParams.mFlags & (ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX))
+ == 0) {
+ throw new IllegalArgumentException("Nothing to optimize");
+ }
+
+ 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 @OptimizeFlags int mFlags = 0;
+ private @NonNull String mCompilerFilter = "";
+ private @PriorityClassApi int mPriorityClass = ArtFlags.PRIORITY_NONE;
+ private @NonNull String mReason = "";
+ private @Nullable String mSplitName = null;
+
+ private OptimizeParams() {}
+
+ /** Returns all flags. */
+ public @OptimizeFlags 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 optimize, or null for the base split. */
+ public @Nullable String getSplitName() {
+ return mSplitName;
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
new file mode 100644
index 0000000..b777e47
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public class OptimizeResult {
+ // Possible values of {@link #OptimizeStatus}.
+ // 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.
+ public static final int OPTIMIZE_SKIPPED = 10;
+ public static final int OPTIMIZE_PERFORMED = 20;
+ public static final int OPTIMIZE_FAILED = 30;
+ public static final int OPTIMIZE_CANCELLED = 40;
+
+ /** @hide */
+ // clang-format off
+ @IntDef(prefix = {"OPTIMIZE_"}, value = {
+ OPTIMIZE_SKIPPED,
+ OPTIMIZE_FAILED,
+ OPTIMIZE_PERFORMED,
+ OPTIMIZE_CANCELLED,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OptimizeStatus {}
+
+ private final @NonNull String mRequestedCompilerFilter;
+ private final @NonNull String mReason;
+ private final @NonNull List<PackageOptimizeResult> mPackageOptimizeResult;
+
+ /** @hide */
+ public OptimizeResult(@NonNull String requestedCompilerFilter, @NonNull String reason,
+ @NonNull List<PackageOptimizeResult> packageOptimizeResult) {
+ mRequestedCompilerFilter = requestedCompilerFilter;
+ mReason = reason;
+ mPackageOptimizeResult = packageOptimizeResult;
+ }
+
+ /**
+ * 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 OptimizeParams.Builder#setCompilerFilter(String)
+ * @see DexContainerFileOptimizeResult#getActualCompilerFilter()
+ */
+ public @NonNull String getRequestedCompilerFilter() {
+ return mRequestedCompilerFilter;
+ }
+
+ /** The compilation reason. */
+ public @NonNull String getReason() {
+ return mReason;
+ }
+
+ /**
+ * The result of each individual package.
+ *
+ * If the request is to optimize a single package without optimizing dependencies, the only
+ * element is the result of the requested package.
+ *
+ * If the request is to optimize 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 optimize 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.
+ */
+ public @NonNull List<PackageOptimizeResult> getPackageOptimizeResults() {
+ return mPackageOptimizeResult;
+ }
+
+ /** The final status. */
+ public @OptimizeStatus int getFinalStatus() {
+ return mPackageOptimizeResult.stream()
+ .mapToInt(result -> result.getStatus())
+ .max()
+ .orElse(OPTIMIZE_SKIPPED);
+ }
+
+ /** Describes the result of a package. */
+ @Immutable
+ public static class PackageOptimizeResult {
+ private final @NonNull String mPackageName;
+ private final
+ @NonNull List<DexContainerFileOptimizeResult> mDexContainerFileOptimizeResults;
+ private final boolean mIsCanceled;
+
+ /** @hide */
+ public PackageOptimizeResult(@NonNull String packageName,
+ @NonNull List<DexContainerFileOptimizeResult> dexContainerFileOptimizeResults,
+ boolean isCanceled) {
+ mPackageName = packageName;
+ mDexContainerFileOptimizeResults = dexContainerFileOptimizeResults;
+ mIsCanceled = isCanceled;
+ }
+
+ /** The package name. */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The results of optimizing dex container files. Note that there can be multiple entries
+ * for the same dex container file, but for different ABIs.
+ */
+ @NonNull
+ public List<DexContainerFileOptimizeResult> getDexContainerFileOptimizeResults() {
+ return mDexContainerFileOptimizeResults;
+ }
+
+ /** The overall status of the package. */
+ public @OptimizeStatus int getStatus() {
+ return mIsCanceled ? OPTIMIZE_CANCELLED
+ : mDexContainerFileOptimizeResults.stream()
+ .mapToInt(result -> result.getStatus())
+ .max()
+ .orElse(OPTIMIZE_SKIPPED);
+ }
+ }
+
+ /** Describes the result of optimizing a dex container file. */
+ @Immutable
+ public static class DexContainerFileOptimizeResult {
+ private final @NonNull String mDexContainerFile;
+ private final boolean mIsPrimaryAbi;
+ private final @NonNull String mAbi;
+ private final @NonNull String mActualCompilerFilter;
+ private final @OptimizeStatus int mStatus;
+ private final long mDex2oatWallTimeMillis;
+ private final long mDex2oatCpuTimeMillis;
+ private final long mSizeBytes;
+ private final long mSizeBeforeBytes;
+
+ /** @hide */
+ public DexContainerFileOptimizeResult(@NonNull String dexContainerFile,
+ boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
+ @OptimizeStatus int status, long dex2oatWallTimeMillis, long dex2oatCpuTimeMillis,
+ long sizeBytes, long sizeBeforeBytes) {
+ mDexContainerFile = dexContainerFile;
+ mIsPrimaryAbi = isPrimaryAbi;
+ mAbi = abi;
+ mActualCompilerFilter = compilerFilter;
+ mStatus = status;
+ mDex2oatWallTimeMillis = dex2oatWallTimeMillis;
+ mDex2oatCpuTimeMillis = dex2oatCpuTimeMillis;
+ mSizeBytes = sizeBytes;
+ mSizeBeforeBytes = sizeBeforeBytes;
+ }
+
+ /** The absolute path to the dex container file. */
+ public @NonNull String getDexContainerFile() {
+ return mDexContainerFile;
+ }
+
+ /**
+ * If true, the optimization is for the primary ABI of the package (the ABI that the
+ * application is launched with). Otherwise, the optimization is for an ABI that other
+ * applications might be launched with when using this application's code.
+ */
+ public boolean isPrimaryAbi() {
+ return mIsPrimaryAbi;
+ }
+
+ /**
+ * Returns the ABI that the optimization is for. Possible values are documented at
+ * https://developer.android.com/ndk/guides/abis#sa.
+ */
+ public @NonNull String getAbi() {
+ return mAbi;
+ }
+
+ /**
+ * The actual compiler filter.
+ *
+ * @see OptimizeParams.Builder#setCompilerFilter(String)
+ */
+ public @NonNull String getActualCompilerFilter() {
+ return mActualCompilerFilter;
+ }
+
+ /** The status of optimizing this dex container file. */
+ public @OptimizeStatus int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * 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 @DurationMillisLong long getDex2oatWallTimeMillis() {
+ return mDex2oatWallTimeMillis;
+ }
+
+ /**
+ * 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 @DurationMillisLong long getDex2oatCpuTimeMillis() {
+ return mDex2oatCpuTimeMillis;
+ }
+
+ /**
+ * The total size, in bytes, of the optimized artifacts. Returns 0 if {@link #getStatus()}
+ * is not {@link #OPTIMIZE_PERFORMED}.
+ */
+ public long getSizeBytes() {
+ return mSizeBytes;
+ }
+
+ /**
+ * The total size, in bytes, of the previous optimized artifacts that has been replaced.
+ * Returns 0 if there were no previous optimized artifacts or {@link #getStatus()} is not
+ * {@link #OPTIMIZE_PERFORMED}.
+ */
+ public long getSizeBeforeBytes() {
+ return mSizeBeforeBytes;
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/Environment.java b/libartservice/service/java/com/android/server/art/wrapper/Environment.java
new file mode 100644
index 0000000..ef024fd
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/Environment.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.wrapper;
+
+import java.io.File;
+
+/** @hide */
+public class Environment {
+ public static File getDataUserCePackageDirectory(
+ String volumeUuid, int userId, String packageName) {
+ try {
+ return (File) Class.forName("android.os.Environment")
+ .getMethod(
+ "getDataUserCePackageDirectory", String.class, int.class, String.class)
+ .invoke(null, volumeUuid, userId, packageName);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static File getDataUserDePackageDirectory(
+ String volumeUuid, int userId, String packageName) {
+ try {
+ return (File) Class.forName("android.os.Environment")
+ .getMethod(
+ "getDataUserDePackageDirectory", String.class, int.class, String.class)
+ .invoke(null, volumeUuid, userId, packageName);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/PackageState.java b/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
new file mode 100644
index 0000000..3f1e871
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
@@ -0,0 +1,70 @@
+
+/*
+ * 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.wrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.server.pm.pkg.AndroidPackage;
+
+/** @hide */
+public class PackageState {
+ @NonNull private final com.android.server.pm.pkg.PackageState mPkgState;
+
+ public PackageState(@NonNull com.android.server.pm.pkg.PackageState pkgState) {
+ mPkgState = pkgState;
+ }
+
+ @Nullable
+ public String getVolumeUuid() {
+ try {
+ return (String) mPkgState.getClass().getMethod("getVolumeUuid").invoke(mPkgState);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public String getSeInfo() {
+ try {
+ Object pkgStateUnserialized =
+ mPkgState.getClass().getMethod("getTransientState").invoke(mPkgState);
+ String seInfo = (String) pkgStateUnserialized.getClass()
+ .getMethod("getOverrideSeInfo")
+ .invoke(pkgStateUnserialized);
+ if (!TextUtils.isEmpty(seInfo)) {
+ return seInfo;
+ }
+
+ // Default to the information in `AndroidPackage`. The defaulting behavior will
+ // eventually be done by `PackageState` internally.
+ AndroidPackage pkg = mPkgState.getAndroidPackage();
+ if (pkg == null) {
+ // This should never happen because we check the existence of the package at the
+ // beginning of each ART Services method.
+ throw new IllegalStateException("Unable to get package "
+ + mPkgState.getPackageName() + ". This should never happen.");
+ }
+
+ return (String) pkg.getClass().getMethod("getSeInfo").invoke(pkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/README.md b/libartservice/service/java/com/android/server/art/wrapper/README.md
new file mode 100644
index 0000000..a18a08a
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/README.md
@@ -0,0 +1,8 @@
+This folder contains temporary wrappers that access system server internal
+classes using reflection. Having the wrappers is the workaround for the current
+time being where required system APIs are not finalized. The classes and methods
+correspond to system APIs planned to be exposed.
+
+The mappings are:
+- `Environment`: `android.os.Environment`
+- `PackageState`: `com.android.server.pm.pkg.PackageState`
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index a27dfa5..85a7b87 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,29 +16,631 @@
package com.android.server.art;
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
+
+import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
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.argThat;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.apphibernation.AppHibernationManager;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+
import androidx.test.filters.SmallTest;
-import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.OptimizationStatus;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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.PackageUserState;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
@SmallTest
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(Parameterized.class)
public class ArtManagerLocalTest {
+ private static final String PKG_NAME = "com.example.foo";
+ private static final String PKG_NAME_SYS_UI = "com.android.systemui";
+ private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
+
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, Constants.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;
+ private PackageState mPkgState;
+ private AndroidPackage mPkg;
+ private Config mConfig;
+
+ // True if the primary dex'es are in a readonly partition.
+ @Parameter(0) public boolean mIsInReadonlyPartition;
+
private ArtManagerLocal mArtManagerLocal;
+ @Parameters(name = "isInReadonlyPartition={0}")
+ public static Iterable<? extends Object> data() {
+ return List.of(false, true);
+ }
+
@Before
- public void setUp() {
- mArtManagerLocal = new ArtManagerLocal();
+ public void setUp() throws Exception {
+ mConfig = new Config();
+
+ // Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
+ // These are the default test setups. They may or may not be used depending on the code path
+ // that each test case examines.
+ lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+ lenient().when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
+ lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+ lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
+ lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+
+ lenient().when(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.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+ .thenReturn(3);
+
+ // No ISA translation.
+ lenient()
+ .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+ .thenReturn("");
+
+ lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+ lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
+ lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+ lenient()
+ .when(mUserManager.getUserHandles(anyBoolean()))
+ .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1)));
+
+ lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+ List<PackageState> pkgStates = createPackageStates();
+ for (PackageState pkgState : pkgStates) {
+ lenient()
+ .when(mSnapshot.getPackageState(pkgState.getPackageName()))
+ .thenReturn(pkgState);
+ }
+ lenient()
+ .doAnswer(invocation -> {
+ var consumer = invocation.<Consumer<PackageState>>getArgument(0);
+ for (PackageState pkgState : pkgStates) {
+ consumer.accept(pkgState);
+ }
+ return null;
+ })
+ .when(mSnapshot)
+ .forAllPackageStates(any());
+ mPkgState = mSnapshot.getPackageState(PKG_NAME);
+ mPkg = mPkgState.getAndroidPackage();
+
+ mArtManagerLocal = new ArtManagerLocal(mInjector);
}
@Test
- public void testScaffolding() {
- assertThat(true).isTrue();
+ public void testDeleteOptimizedArtifacts() throws Exception {
+ when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
+ assertThat(result.getFreedBytes()).isEqualTo(4);
+
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("arm64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("arm")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("arm64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("arm")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verifyNoMoreInteractions(mArtd);
+ }
+
+ @Test
+ public void testDeleteOptimizedArtifactsTranslatedIsas() throws Exception {
+ lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
+ lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
+ lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
+
+ when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
+ assertThat(result.getFreedBytes()).isEqualTo(4);
+
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("x86_64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("x86")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("x86_64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("x86")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verifyNoMoreInteractions(mArtd);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteOptimizedArtifactsPackageNotFound() throws Exception {
+ when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+ mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteOptimizedArtifactsNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
+ }
+
+ @Test
+ public void testGetOptimizationStatus() throws Exception {
+ when(mArtd.getOptimizationStatus(any(), any(), any()))
+ .thenReturn(createGetOptimizationStatusResult(
+ "speed", "compilation-reason-0", "location-debug-string-0"),
+ createGetOptimizationStatusResult(
+ "speed-profile", "compilation-reason-1", "location-debug-string-1"),
+ createGetOptimizationStatusResult(
+ "verify", "compilation-reason-2", "location-debug-string-2"),
+ createGetOptimizationStatusResult(
+ "extract", "compilation-reason-3", "location-debug-string-3"));
+
+ OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+
+ List<DexContainerFileOptimizationStatus> statuses =
+ result.getDexContainerFileOptimizationStatuses();
+ assertThat(statuses.size()).isEqualTo(4);
+
+ assertThat(statuses.get(0).getDexContainerFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(0).isPrimaryAbi()).isEqualTo(true);
+ assertThat(statuses.get(0).getAbi()).isEqualTo("arm64-v8a");
+ assertThat(statuses.get(0).getCompilerFilter()).isEqualTo("speed");
+ assertThat(statuses.get(0).getCompilationReason()).isEqualTo("compilation-reason-0");
+ assertThat(statuses.get(0).getLocationDebugString()).isEqualTo("location-debug-string-0");
+
+ assertThat(statuses.get(1).getDexContainerFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(1).isPrimaryAbi()).isEqualTo(false);
+ assertThat(statuses.get(1).getAbi()).isEqualTo("armeabi-v7a");
+ assertThat(statuses.get(1).getCompilerFilter()).isEqualTo("speed-profile");
+ assertThat(statuses.get(1).getCompilationReason()).isEqualTo("compilation-reason-1");
+ assertThat(statuses.get(1).getLocationDebugString()).isEqualTo("location-debug-string-1");
+
+ assertThat(statuses.get(2).getDexContainerFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(2).isPrimaryAbi()).isEqualTo(true);
+ assertThat(statuses.get(2).getAbi()).isEqualTo("arm64-v8a");
+ assertThat(statuses.get(2).getCompilerFilter()).isEqualTo("verify");
+ assertThat(statuses.get(2).getCompilationReason()).isEqualTo("compilation-reason-2");
+ assertThat(statuses.get(2).getLocationDebugString()).isEqualTo("location-debug-string-2");
+
+ assertThat(statuses.get(3).getDexContainerFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(3).isPrimaryAbi()).isEqualTo(false);
+ assertThat(statuses.get(3).getAbi()).isEqualTo("armeabi-v7a");
+ assertThat(statuses.get(3).getCompilerFilter()).isEqualTo("extract");
+ assertThat(statuses.get(3).getCompilationReason()).isEqualTo("compilation-reason-3");
+ assertThat(statuses.get(3).getLocationDebugString()).isEqualTo("location-debug-string-3");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetOptimizationStatusPackageNotFound() throws Exception {
+ when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetOptimizationStatusNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+ }
+
+ @Test
+ public void testGetOptimizationStatusNonFatalError() throws Exception {
+ when(mArtd.getOptimizationStatus(any(), any(), any()))
+ .thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
+
+ OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+
+ List<DexContainerFileOptimizationStatus> statuses =
+ result.getDexContainerFileOptimizationStatuses();
+ assertThat(statuses.size()).isEqualTo(4);
+
+ for (DexContainerFileOptimizationStatus status : statuses) {
+ assertThat(status.getCompilerFilter()).isEqualTo("error");
+ assertThat(status.getCompilationReason()).isEqualTo("error");
+ assertThat(status.getLocationDebugString()).isEqualTo("some error message");
+ }
+ }
+
+ @Test
+ public void testOptimizePackage() throws Exception {
+ var params = new OptimizeParams.Builder("install").build();
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
+ same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(
+ mArtManagerLocal.optimizePackage(mSnapshot, PKG_NAME, params, cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testOptimizePackages() throws Exception {
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ // It should use the default package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)), any(),
+ same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testOptimizePackagesOverride() throws Exception {
+ var params = new OptimizeParams.Builder("bg-dexopt").build();
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ assertThat(reason).isEqualTo("bg-dexopt");
+ assertThat(defaultPackages).containsExactly(PKG_NAME, PKG_NAME_SYS_UI);
+ builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
+ });
+
+ // It should use the overridden package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
+ same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testOptimizePackagesOverrideCleared() throws Exception {
+ var params = new OptimizeParams.Builder("bg-dexopt").build();
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
+ });
+ mArtManagerLocal.clearOptimizePackagesCallback();
+
+ // It should use the default package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)),
+ not(same(params)), same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOptimizePackagesOverrideReasonChanged() throws Exception {
+ var params = new OptimizeParams.Builder("first-boot").build();
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ builder.setOptimizeParams(params);
+ });
+
+ mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal);
+ }
+
+ @Test
+ public void testSnapshotAppProfile() throws Exception {
+ var options = new MergeProfileOptions();
+ options.forceMerge = true;
+ options.forBootImage = false;
+
+ File tempFile = File.createTempFile("primary", ".prof");
+ tempFile.deleteOnExit();
+
+ when(mArtd.mergeProfiles(
+ deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME, "primary"))),
+ isNull(),
+ deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "primary",
+ Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+ deepEq(List.of("/data/app/foo/base.apk")), deepEq(options)))
+ .thenAnswer(invocation -> {
+ try (var writer = new FileWriter(tempFile)) {
+ writer.write("snapshot");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ var output = invocation.<OutputProfile>getArgument(2);
+ output.profilePath.tmpPath = tempFile.getPath();
+ return true;
+ });
+
+ ParcelFileDescriptor fd =
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+
+ verify(mArtd).deleteProfile(
+ argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath())));
+
+ try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+ String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+ assertThat(contents).isEqualTo("snapshot");
+ }
+ }
+
+ @Test
+ public void testSnapshotAppProfileSplit() throws Exception {
+ when(mArtd.mergeProfiles(deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef(
+ PKG_NAME, "split_0.split"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME, "split_0.split"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME, "split_0.split"))),
+ isNull(),
+ deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "split_0.split",
+ Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+ deepEq(List.of("/data/app/foo/split_0.apk")), any()))
+ .thenReturn(false);
+
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "split_0");
+ }
+
+ @Test
+ public void testSnapshotAppProfileEmpty() throws Exception {
+ when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+ ParcelFileDescriptor fd =
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+
+ verify(mArtd, never()).deleteProfile(any());
+
+ try (InputStream inputStream = new AutoCloseInputStream(fd)) {
+ assertThat(inputStream.readAllBytes()).isEmpty();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSnapshotAppProfilePackageNotFound() throws Exception {
+ when(mSnapshot.getPackageState(anyString())).thenReturn(null);
+
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSnapshotAppProfileNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, null /* splitName */);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSnapshotAppProfileSplitNotFound() throws Exception {
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME, "non-existent-split");
+ }
+
+ @Test
+ public void 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(
+ deepEq(List.of(AidlUtils.buildProfilePathForPrimaryRef("android", "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, "android", "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, "android", "primary"),
+ AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME, "primary"),
+ AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME, "split_0.split"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME, "split_0.split"),
+ AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_SYS_UI, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME_SYS_UI, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME_SYS_UI, "primary"),
+ AidlUtils.buildProfilePathForPrimaryRef(
+ PKG_NAME_HIBERNATING, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 0 /* userId */, PKG_NAME_HIBERNATING, "primary"),
+ AidlUtils.buildProfilePathForPrimaryCur(
+ 1 /* userId */, PKG_NAME_HIBERNATING, "primary"))),
+ isNull(),
+ deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary",
+ Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)),
+ deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")),
+ deepEq(options)))
+ .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`.
+
+ mArtManagerLocal.snapshotBootImageProfile(mSnapshot);
+ }
+
+ private AndroidPackage createPackage(boolean multiSplit) {
+ AndroidPackage pkg = mock(AndroidPackage.class);
+
+ var baseSplit = mock(AndroidPackageSplit.class);
+ lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+ lenient().when(baseSplit.isHasCode()).thenReturn(true);
+
+ if (multiSplit) {
+ // split_0 has code while split_1 doesn't.
+ var split0 = mock(AndroidPackageSplit.class);
+ lenient().when(split0.getName()).thenReturn("split_0");
+ lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+ lenient().when(split0.isHasCode()).thenReturn(true);
+ var split1 = mock(AndroidPackageSplit.class);
+ lenient().when(split1.getName()).thenReturn("split_1");
+ lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+ lenient().when(split1.isHasCode()).thenReturn(false);
+
+ lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
+ } else {
+ lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+ }
+
+ return pkg;
+ }
+
+ private PackageUserState createPackageUserState() {
+ PackageUserState pkgUserState = mock(PackageUserState.class);
+ lenient().when(pkgUserState.isInstalled()).thenReturn(true);
+ return pkgUserState;
+ }
+
+ private PackageState createPackageState(
+ String packageName, int appId, boolean hasPackage, boolean multiSplit) {
+ PackageState pkgState = mock(PackageState.class);
+
+ lenient().when(pkgState.getPackageName()).thenReturn(packageName);
+ lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+ lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+ lenient().when(pkgState.isSystem()).thenReturn(mIsInReadonlyPartition);
+ lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+ lenient().when(pkgState.getAppId()).thenReturn(appId);
+
+ if (hasPackage) {
+ AndroidPackage pkg = createPackage(multiSplit);
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+ } else {
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(null);
+ }
+
+ PackageUserState pkgUserState = createPackageUserState();
+ lenient().when(pkgState.getStateForUser(any())).thenReturn(pkgUserState);
+
+ return pkgState;
+ }
+
+ private List<PackageState> createPackageStates() {
+ PackageState pkgState = createPackageState(
+ PKG_NAME, 10001 /* appId */, true /* hasPackage */, true /* multiSplit */);
+
+ PackageState sysUiPkgState = createPackageState(
+ PKG_NAME_SYS_UI, 1234 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it's hibernating. However, it should be included
+ // when snapshotting boot image profile.
+ PackageState pkgHibernatingState = createPackageState(PKG_NAME_HIBERNATING,
+ 10002 /* appId */, true /* hasPackage */, false /* multiSplit */);
+ lenient()
+ .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
+ .thenReturn(true);
+
+ // This should not be optimized because it does't have AndroidPackage.
+ PackageState nullPkgState = createPackageState("com.example.null", 10003 /* appId */,
+ false /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it has a negative app id.
+ PackageState apexPkgState = createPackageState(
+ "com.android.art", -1 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it's "android".
+ PackageState platformPkgState = createPackageState(Utils.PLATFORM_PACKAGE_NAME,
+ 1000 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ return List.of(pkgState, sysUiPkgState, pkgHibernatingState, nullPkgState, apexPkgState,
+ platformPkgState);
+ }
+
+ private GetOptimizationStatusResult createGetOptimizationStatusResult(
+ String compilerFilter, String compilationReason, String locationDebugString) {
+ var getOptimizationStatusResult = new GetOptimizationStatusResult();
+ getOptimizationStatusResult.compilerFilter = compilerFilter;
+ getOptimizationStatusResult.compilationReason = compilationReason;
+ getOptimizationStatusResult.locationDebugString = locationDebugString;
+ return getOptimizationStatusResult;
}
}
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..c235f65
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/BackgroundDexOptJobTest.java
@@ -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
+ */
+
+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.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.OptimizeResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.PackageManagerLocal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundDexOptJobTest {
+ private static final long TIMEOUT_SEC = 1;
+
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, BackgroundDexOptJobService.class);
+
+ @Mock private BackgroundDexOptJob.Injector mInjector;
+ @Mock private ArtManagerLocal mArtManagerLocal;
+ @Mock private PackageManagerLocal mPackageManagerLocal;
+ @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+ @Mock private JobScheduler mJobScheduler;
+ @Mock private OptimizeResult mOptimizeResult;
+ private Config mConfig;
+ private BackgroundDexOptJob mBackgroundDexOptJob;
+
+ @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);
+ }
+
+ @Test
+ public void testStart() {
+ when(mArtManagerLocal.optimizePackages(
+ same(mSnapshot), eq(ReasonMapping.REASON_BG_DEXOPT), any()))
+ .thenReturn(mOptimizeResult);
+
+ Result result = Utils.getFuture(mBackgroundDexOptJob.start());
+ assertThat(result).isInstanceOf(CompletedResult.class);
+ assertThat(((CompletedResult) result).dexoptResult()).isSameInstanceAs(mOptimizeResult);
+ }
+
+ @Test
+ public void testStartAlreadyRunning() {
+ Semaphore optimizeDone = new Semaphore(0);
+ when(mArtManagerLocal.optimizePackages(any(), any(), any())).thenAnswer(invocation -> {
+ assertThat(optimizeDone.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ return mOptimizeResult;
+ });
+
+ Future<Result> future1 = mBackgroundDexOptJob.start();
+ Future<Result> future2 = mBackgroundDexOptJob.start();
+ assertThat(future1).isSameInstanceAs(future2);
+
+ optimizeDone.release();
+ Utils.getFuture(future1);
+
+ verify(mArtManagerLocal, times(1)).optimizePackages(any(), any(), any());
+ }
+
+ @Test
+ public void testStartAnother() {
+ when(mArtManagerLocal.optimizePackages(any(), any(), any())).thenReturn(mOptimizeResult);
+
+ 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.optimizePackages(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.optimizePackages(any(), any(), any())).thenReturn(mOptimizeResult);
+
+ // The `start` method should ignore the system property. The system property is for
+ // `schedule`.
+ Utils.getFuture(mBackgroundDexOptJob.start());
+ }
+
+ @Test
+ public void testCancel() {
+ Semaphore optimizeCancelled = new Semaphore(0);
+ when(mArtManagerLocal.optimizePackages(any(), any(), any())).thenAnswer(invocation -> {
+ assertThat(optimizeCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+ assertThat(cancellationSignal.isCanceled()).isTrue();
+ return mOptimizeResult;
+ });
+
+ Future<Result> future = mBackgroundDexOptJob.start();
+ mBackgroundDexOptJob.cancel();
+ optimizeCancelled.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()).isTrue();
+ }
+
+ @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.isRequireStorageNotLow()).isTrue();
+ 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
+ public void testUnschedule() {
+ mBackgroundDexOptJob.unschedule();
+ verify(mJobScheduler).cancel(anyInt());
+ }
+}
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..ac79d26
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -0,0 +1,628 @@
+/*
+ * 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.OptimizePackageDoneCallback;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+import static com.android.server.art.model.OptimizeResult.PackageOptimizeResult;
+
+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 com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.Config;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.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";
+
+ @Mock private DexOptHelper.Injector mInjector;
+ @Mock private PrimaryDexOptimizer mPrimaryDexOptimizer;
+ @Mock private SecondaryDexOptimizer mSecondaryDexOptimizer;
+ @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 = Executors.newSingleThreadExecutor();
+ private List<DexContainerFileOptimizeResult> mPrimaryResults;
+ private List<DexContainerFileOptimizeResult> mSecondaryResults;
+ private Config mConfig;
+ private OptimizeParams 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();
+ mConfig = new Config();
+
+ preparePackagesAndLibraries();
+
+ mPrimaryResults = createResults("/data/app/foo/base.apk", false /* partialFailure */);
+ mSecondaryResults =
+ createResults("/data/user_de/0/foo/foo.apk", false /* partialFailure */);
+
+ lenient()
+ .when(mInjector.getPrimaryDexOptimizer(any(), any(), any(), any()))
+ .thenReturn(mPrimaryDexOptimizer);
+ lenient().when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
+
+ lenient()
+ .when(mInjector.getSecondaryDexOptimizer(any(), any(), any(), any()))
+ .thenReturn(mSecondaryDexOptimizer);
+ lenient().when(mSecondaryDexOptimizer.dexopt()).thenReturn(mSecondaryResults);
+
+ mParams = new OptimizeParams.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);
+ }
+
+ @Test
+ public void testDexopt() throws Exception {
+ // Only package libbaz fails.
+ var failingPrimaryDexOptimizer = mock(PrimaryDexOptimizer.class);
+ List<DexContainerFileOptimizeResult> partialFailureResults =
+ createResults("/data/app/foo/base.apk", true /* partialFailure */);
+ lenient().when(failingPrimaryDexOptimizer.dexopt()).thenReturn(partialFailureResults);
+ when(mInjector.getPrimaryDexOptimizer(same(mPkgStateLibbaz), any(), any(), any()))
+ .thenReturn(failingPrimaryDexOptimizer);
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
+ assertThat(result.getReason()).isEqualTo("install");
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_FAILED);
+
+ // The requested packages must come first.
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, OptimizeResult.OPTIMIZE_FAILED,
+ List.of(partialFailureResults, mSecondaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_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).getPrimaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mWakeLock).release();
+
+ verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */);
+
+ verifyNoMoreInteractions(mWakeLock);
+ }
+
+ @Test
+ public void testDexoptNoDependencies() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(3);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults, mSecondaryResults));
+
+ verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptPrimaryOnly() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+
+ verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptPrimaryOnlyNoDependencies() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(0,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(3);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults));
+
+ verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception {
+ when(mPrimaryDexOptimizer.dexopt()).thenAnswer(invocation -> {
+ mCancellationSignal.cancel();
+ return mPrimaryResults;
+ });
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_CANCELLED);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_CANCELLED,
+ List.of(mPrimaryResults));
+ checkPackageResult(
+ result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+
+ verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+
+ verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptNoCode() throws Exception {
+ when(mPkgFoo.getSplits().get(0).isHasCode()).thenReturn(false);
+
+ mRequestedPackages = List.of(PKG_NAME_FOO);
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
+ assertThat(result.getPackageOptimizeResults()).hasSize(1);
+ checkPackageResult(
+ result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_SKIPPED, List.of());
+
+ verifyNoDexopt();
+ }
+
+ @Test
+ public void testDexoptLibraryNoCode() throws Exception {
+ when(mPkgLib1.getSplits().get(0).isHasCode()).thenReturn(false);
+
+ mRequestedPackages = List.of(PKG_NAME_FOO);
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_PERFORMED);
+ assertThat(result.getPackageOptimizeResults()).hasSize(1);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_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);
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
+ checkPackageResult(
+ result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_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);
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ }
+
+ @Test
+ public void testDexoptAlwaysReleasesWakeLock() throws Exception {
+ when(mPrimaryDexOptimizer.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 OptimizeParams.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 OptimizeParams.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<OptimizeResult> list1 = new ArrayList<>();
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, result -> list1.add(result));
+
+ List<OptimizeResult> list2 = new ArrayList<>();
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, result -> list2.add(result));
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(list1).containsExactly(result);
+ assertThat(list2).containsExactly(result);
+ }
+
+ @Test
+ public void testCallbackRemoved() throws Exception {
+ List<OptimizeResult> list1 = new ArrayList<>();
+ OptimizePackageDoneCallback callback1 = result -> list1.add(result);
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, callback1);
+
+ List<OptimizeResult> list2 = new ArrayList<>();
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, result -> list2.add(result));
+
+ mConfig.removeOptimizePackageDoneCallback(callback1);
+
+ OptimizeResult 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<OptimizeResult> list = new ArrayList<>();
+ OptimizePackageDoneCallback callback = result -> list.add(result);
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, callback);
+ mConfig.addOptimizePackageDoneCallback(Runnable::run, callback);
+ }
+
+ private AndroidPackage createPackage(boolean multiSplit) {
+ AndroidPackage pkg = mock(AndroidPackage.class);
+
+ var baseSplit = mock(AndroidPackageSplit.class);
+ lenient().when(baseSplit.isHasCode()).thenReturn(true);
+
+ if (multiSplit) {
+ var split0 = mock(AndroidPackageSplit.class);
+ lenient().when(split0.getName()).thenReturn("split_0");
+ lenient().when(split0.isHasCode()).thenReturn(true);
+
+ 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.getUsesLibraries()).thenReturn(deps);
+ AndroidPackage pkg = createPackage(multiSplit);
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+ return pkgState;
+ }
+
+ private SharedLibrary createLibrary(
+ String libraryName, String packageName, List<SharedLibrary> deps) {
+ SharedLibrary library = mock(SharedLibrary.class);
+ lenient().when(library.getName()).thenReturn(libraryName);
+ lenient().when(library.getPackageName()).thenReturn(packageName);
+ lenient().when(library.getDependencies()).thenReturn(deps);
+ return library;
+ }
+
+ private void preparePackagesAndLibraries() {
+ // Dependency graph:
+ // foo bar
+ // | |
+ // lib1a (lib1) lib1b (lib1) lib1c (lib1)
+ // / \ / \ |
+ // / \ / \ |
+ // libbaz (libbaz) lib2 (lib2) lib4 (lib4) lib3 (lib3)
+ //
+ // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1".
+
+ mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ);
+
+ SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of());
+ SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of());
+ SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of());
+ SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of());
+ SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2));
+ SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, lib4));
+ SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
+
+ mPkgStateFoo = createPackageState(PKG_NAME_FOO, List.of(lib1a), true /* multiSplit */);
+ mPkgFoo = mPkgStateFoo.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo);
+
+ mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b), false /* multiSplit */);
+ mPkgBar = mPkgStateBar.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar);
+
+ mPkgStateLib1 = createPackageState(
+ PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4), false /* multiSplit */);
+ mPkgLib1 = mPkgStateLib1.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1);
+
+ mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of(), false /* multiSplit */);
+ mPkgLib2 = mPkgStateLib2.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2);
+
+ // This should not be considered as a transitive dependency of any requested package, even
+ // though it is a dependency of package "lib1".
+ PackageState pkgStateLib3 =
+ createPackageState(PKG_NAME_LIB3, List.of(), false /* multiSplit */);
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3);
+
+ mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of(), false /* multiSplit */);
+ mPkgLib4 = mPkgStateLib4.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4);
+
+ mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of(), false /* multiSplit */);
+ mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz);
+ }
+
+ private void verifyNoDexopt() {
+ verify(mInjector, never()).getPrimaryDexOptimizer(any(), any(), any(), any());
+ verify(mInjector, never()).getSecondaryDexOptimizer(any(), any(), any(), any());
+ }
+
+ private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) {
+ verify(mInjector, times(expectedPrimaryTimes))
+ .getPrimaryDexOptimizer(any(), any(), any(), any());
+ verify(mInjector, times(expectedSecondaryTimes))
+ .getSecondaryDexOptimizer(any(), any(), any(), any());
+ }
+
+ private List<DexContainerFileOptimizeResult> createResults(
+ String dexPath, boolean partialFailure) {
+ return List.of(new DexContainerFileOptimizeResult(dexPath, true /* isPrimaryAbi */,
+ "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
+ 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult(dexPath, false /* isPrimaryAbi */, "armeabi-v7a",
+ "verify",
+ partialFailure ? OptimizeResult.OPTIMIZE_FAILED
+ : OptimizeResult.OPTIMIZE_PERFORMED,
+ 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */));
+ }
+
+ private void checkPackageResult(OptimizeResult result, int index, String packageName,
+ @OptimizeResult.OptimizeStatus int status,
+ List<List<DexContainerFileOptimizeResult>> dexContainerFileOptimizeResults) {
+ PackageOptimizeResult packageResult = result.getPackageOptimizeResults().get(index);
+ assertThat(packageResult.getPackageName()).isEqualTo(packageName);
+ assertThat(packageResult.getStatus()).isEqualTo(status);
+ assertThat(packageResult.getDexContainerFileOptimizeResults())
+ .containsExactlyElementsIn(dexContainerFileOptimizeResults.stream()
+ .flatMap(r -> r.stream())
+ .collect(Collectors.toList()));
+ }
+}
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..a2f7db6
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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.pm.ApplicationInfo;
+import android.os.Binder;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.wrapper.Environment;
+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.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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(Parameterized.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);
+
+ @Parameter(0) public String mVolumeUuid;
+
+ 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;
+ private DexUseManagerLocal mDexUseManager;
+ private String mCeDir;
+ private String mDeDir;
+
+ @Parameters(name = "volumeUuid={0}")
+ public static Iterable<? extends Object> data() {
+ List<String> volumeUuids = new ArrayList<>();
+ volumeUuids.add(null);
+ volumeUuids.add("volume-abcd");
+ return volumeUuids;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // No ISA translation.
+ lenient()
+ .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+ .thenReturn("");
+
+ lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+ lenient().when(Process.isIsolatedUid(anyInt())).thenReturn(false);
+
+ PackageState loadingPkgState = createPackageState(LOADING_PKG_NAME, "armeabi-v7a");
+ lenient().when(mSnapshot.getPackageState(eq(LOADING_PKG_NAME))).thenReturn(loadingPkgState);
+ PackageState owningPkgState = createPackageState(OWNING_PKG_NAME, "arm64-v8a");
+ lenient().when(mSnapshot.getPackageState(eq(OWNING_PKG_NAME))).thenReturn(owningPkgState);
+
+ lenient()
+ .doAnswer(invocation -> {
+ var consumer = invocation.<Consumer<PackageState>>getArgument(0);
+ consumer.accept(loadingPkgState);
+ consumer.accept(owningPkgState);
+ return null;
+ })
+ .when(mSnapshot)
+ .forAllPackageStates(any());
+
+ mCeDir = Environment
+ .getDataUserCePackageDirectory(mVolumeUuid,
+ Binder.getCallingUserHandle().getIdentifier(), OWNING_PKG_NAME)
+ .toString();
+ mDeDir = Environment
+ .getDataUserDePackageDirectory(mVolumeUuid,
+ Binder.getCallingUserHandle().getIdentifier(), OWNING_PKG_NAME)
+ .toString();
+
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+
+ mDexUseManager = new DexUseManagerLocal(mInjector);
+ }
+
+ @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 */);
+ }
+
+ /** Checks that it saves and loads data correctly. */
+ @Test
+ public void testPrimaryDexMultipleEntriesPersisted() throws Exception {
+ verifyPrimaryDexMultipleEntries(true /*saveAndLoad */);
+ }
+
+ private void verifyPrimaryDexMultipleEntries(boolean saveAndLoad) throws Exception {
+ // 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"));
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
+
+ if (saveAndLoad) {
+ File tempFile = File.createTempFile("dex-use", ".pb");
+ tempFile.deleteOnExit();
+ mDexUseManager.save(tempFile.getPath());
+ mDexUseManager.clear();
+ mDexUseManager.load(tempFile.getPath());
+ }
+
+ 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 */));
+ }
+
+ @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 */);
+ }
+
+ /** Checks that it saves and loads data correctly. */
+ @Test
+ public void testSecondaryDexMultipleEntriesPersisted() throws Exception {
+ verifySecondaryDexMultipleEntries(true /*saveAndLoad */);
+ }
+
+ private void verifySecondaryDexMultipleEntries(boolean saveAndLoad) throws Exception {
+ // 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"));
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+ Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
+
+ if (saveAndLoad) {
+ File tempFile = File.createTempFile("dex-use", ".pb");
+ tempFile.deleteOnExit();
+ mDexUseManager.save(tempFile.getPath());
+ mDexUseManager.clear();
+ mDexUseManager.load(tempFile.getPath());
+ }
+
+ 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));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexPublic() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a", "armeabi-v7a"),
+ Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+ DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+ true /* isUsedByOtherApps */, true /* isDexFilePublic */));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexPrivate() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a"),
+ Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+ false /* isUsedByOtherApps */, false /* isDexFilePublic */));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexFilteredDueToVisibility() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ when(Process.isIsolatedUid(anyInt())).thenReturn(true);
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexFilteredDueToNotFound() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
+
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownPackage() {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, "bogus", Map.of(BASE_APK, "CLC"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyMap() {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, Map.of());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullKey() {
+ var map = new HashMap<String, String>();
+ map.put(null, "CLC");
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonAbsoluteKey() {
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of("a/b.jar", "CLC"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullValue() {
+ var map = new HashMap<String, String>();
+ map.put(mCeDir + "/foo.apk", null);
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map);
+ }
+
+ private AndroidPackage createPackage(String packageName) {
+ AndroidPackage pkg = mock(AndroidPackage.class);
+
+ 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);
+ lenient().when(pkgState.getVolumeUuid()).thenReturn(mVolumeUuid);
+ return pkgState;
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
new file mode 100644
index 0000000..91147b0
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.OptimizeResult.DexContainerFileOptimizeResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.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.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.art.testing.OnSuccessRule;
+import com.android.server.art.testing.TestingUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class PrimaryDexOptimizerParameterizedTest extends PrimaryDexOptimizerTestBase {
+ @Rule
+ public OnSuccessRule onSuccessRule = new OnSuccessRule(() -> {
+ // Don't do this on failure because it will make the failure hard to understand.
+ verifyNoMoreInteractions(mArtd);
+ });
+
+ private OptimizeParams mOptimizeParams;
+
+ private PrimaryDexOptimizer mPrimaryDexOptimizer;
+
+ @Parameter(0) public Params mParams;
+
+ @Parameters(name = "{0}")
+ public static Iterable<Params> data() {
+ List<Params> list = new ArrayList<>();
+ Params params;
+
+ // Baseline.
+ params = new Params();
+ list.add(params);
+
+ params = new Params();
+ params.mRequestedCompilerFilter = "speed";
+ params.mExpectedCompilerFilter = "speed";
+ list.add(params);
+
+ params = new Params();
+ params.mIsSystem = true;
+ params.mExpectedIsInDalvikCache = true;
+ list.add(params);
+
+ params = new Params();
+ params.mIsSystem = true;
+ params.mIsUpdatedSystemApp = true;
+ list.add(params);
+
+ params = new Params();
+ params.mIsSystem = true;
+ params.mIsUsesNonSdkApi = true;
+ params.mExpectedIsInDalvikCache = true;
+ params.mExpectedIsHiddenApiPolicyEnabled = false;
+ list.add(params);
+
+ params = new Params();
+ params.mIsUpdatedSystemApp = true;
+ params.mIsUsesNonSdkApi = true;
+ params.mExpectedIsHiddenApiPolicyEnabled = false;
+ list.add(params);
+
+ params = new Params();
+ params.mIsSignedWithPlatformKey = true;
+ params.mExpectedIsHiddenApiPolicyEnabled = false;
+ list.add(params);
+
+ params = new Params();
+ params.mIsDebuggable = true;
+ params.mRequestedCompilerFilter = "speed";
+ params.mExpectedCompilerFilter = "verify";
+ params.mExpectedIsDebuggable = true;
+ list.add(params);
+
+ params = new Params();
+ params.mIsVmSafeMode = true;
+ params.mRequestedCompilerFilter = "speed";
+ params.mExpectedCompilerFilter = "verify";
+ list.add(params);
+
+ params = new Params();
+ params.mIsUseEmbeddedDex = true;
+ params.mRequestedCompilerFilter = "speed";
+ params.mExpectedCompilerFilter = "verify";
+ list.add(params);
+
+ params = new Params();
+ params.mAlwaysDebuggable = true;
+ params.mExpectedIsDebuggable = true;
+ list.add(params);
+
+ params = new Params();
+ params.mIsSystemUi = true;
+ params.mExpectedCompilerFilter = "speed";
+ list.add(params);
+
+ params = new Params();
+ params.mForce = true;
+ params.mShouldDowngrade = false;
+ params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ list.add(params);
+
+ params = new Params();
+ params.mForce = true;
+ params.mShouldDowngrade = true;
+ params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ list.add(params);
+
+ params = new Params();
+ params.mShouldDowngrade = true;
+ params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+ list.add(params);
+
+ return list;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(mParams.mIsSystemUi);
+
+ lenient()
+ .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+ .thenReturn(mParams.mAlwaysDebuggable);
+
+ lenient().when(mPkg.isVmSafeMode()).thenReturn(mParams.mIsVmSafeMode);
+ lenient().when(mPkg.isDebuggable()).thenReturn(mParams.mIsDebuggable);
+ lenient().when(mPkg.getTargetSdkVersion()).thenReturn(123);
+ lenient().when(mPkg.isSignedWithPlatformKey()).thenReturn(mParams.mIsSignedWithPlatformKey);
+ lenient().when(mPkg.isUsesNonSdkApi()).thenReturn(mParams.mIsUsesNonSdkApi);
+ lenient().when(mPkg.isUseEmbeddedDex()).thenReturn(mParams.mIsUseEmbeddedDex);
+ lenient().when(mPkgState.isSystem()).thenReturn(mParams.mIsSystem);
+ lenient().when(mPkgState.isUpdatedSystemApp()).thenReturn(mParams.mIsUpdatedSystemApp);
+
+ mOptimizeParams =
+ new OptimizeParams.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)
+ .build();
+
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ }
+
+ @Test
+ public void testDexopt() throws Exception {
+ PermissionSettings permissionSettings = buildPermissionSettings(
+ buildFsPermission(Process.SYSTEM_UID /* uid */, Process.SYSTEM_UID /* gid */,
+ false /* isOtherReadable */, true /* isOtherExecutable */),
+ buildFsPermission(Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */,
+ true /* isOtherReadable */),
+ null /* seContext */);
+ DexoptOptions dexoptOptions = new DexoptOptions();
+ dexoptOptions.compilationReason = "install";
+ dexoptOptions.targetSdkVersion = 123;
+ dexoptOptions.debuggable = mParams.mExpectedIsDebuggable;
+ dexoptOptions.generateAppImage = false;
+ dexoptOptions.hiddenApiPolicyEnabled = mParams.mExpectedIsHiddenApiPolicyEnabled;
+
+ when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
+ when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
+ // The first one is normal.
+ doReturn(dexoptIsNeeded())
+ .when(mArtd)
+ .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]",
+ mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+ doReturn(createDexoptResult(false /* cancelled */, 100 /* wallTimeMs */,
+ 400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */))
+ .when(mArtd)
+ .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
+ mParams.mExpectedIsInDalvikCache, permissionSettings)),
+ eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
+ eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+ isNull() /* inputVdex */, isNull() /* dmFile */,
+ eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+ // The second one fails on `dexopt`.
+ doReturn(dexoptIsNeeded())
+ .when(mArtd)
+ .getDexoptNeeded("/data/app/foo/base.apk", "arm", "PCL[]",
+ mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+ doThrow(ServiceSpecificException.class)
+ .when(mArtd)
+ .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm",
+ mParams.mExpectedIsInDalvikCache, permissionSettings)),
+ eq("/data/app/foo/base.apk"), eq("arm"), eq("PCL[]"),
+ eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+ isNull() /* inputVdex */, isNull() /* dmFile */,
+ eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+ // The third one doesn't need dexopt.
+ doReturn(dexoptIsNotNeeded())
+ .when(mArtd)
+ .getDexoptNeeded("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]",
+ mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+
+ // The fourth one is normal.
+ doReturn(dexoptIsNeeded())
+ .when(mArtd)
+ .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]",
+ mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
+ doReturn(createDexoptResult(false /* cancelled */, 200 /* wallTimeMs */,
+ 200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */))
+ .when(mArtd)
+ .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
+ mParams.mExpectedIsInDalvikCache, permissionSettings)),
+ eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
+ eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+ isNull() /* inputVdex */, isNull() /* dmFile */,
+ eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
+
+ assertThat(mPrimaryDexOptimizer.dexopt())
+ .comparingElementsUsing(TestingUtils.<DexContainerFileOptimizeResult>deepEquality())
+ .containsExactly(
+ new DexContainerFileOptimizeResult("/data/app/foo/base.apk",
+ true /* isPrimaryAbi */, "arm64-v8a",
+ mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
+ 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+ 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult("/data/app/foo/base.apk",
+ false /* isPrimaryAbi */, "armeabi-v7a",
+ mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_FAILED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult("/data/app/foo/split_0.apk",
+ true /* isPrimaryAbi */, "arm64-v8a",
+ mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_SKIPPED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult("/data/app/foo/split_0.apk",
+ false /* isPrimaryAbi */, "armeabi-v7a",
+ mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
+ 200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
+ 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */));
+ }
+
+ private static class Params {
+ // Package information.
+ public boolean mIsSystem = false;
+ public boolean mIsUpdatedSystemApp = false;
+ public boolean mIsSignedWithPlatformKey = false;
+ public boolean mIsUsesNonSdkApi = false;
+ public boolean mIsVmSafeMode = false;
+ public boolean mIsDebuggable = false;
+ public boolean mIsSystemUi = false;
+ public boolean mIsUseEmbeddedDex = false;
+
+ // Options.
+ public String mRequestedCompilerFilter = "verify";
+ public boolean mForce = false;
+ public boolean mShouldDowngrade = false;
+
+ // System properties.
+ public boolean mAlwaysDebuggable = false;
+
+ // Expectations.
+ public String mExpectedCompilerFilter = "verify";
+ public int mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ public boolean mExpectedIsInDalvikCache = false;
+ public boolean mExpectedIsDebuggable = false;
+ public boolean mExpectedIsHiddenApiPolicyEnabled = true;
+
+ public String toString() {
+ return String.format("isSystem=%b,isUpdatedSystemApp=%b,isSignedWithPlatformKey=%b,"
+ + "isUsesNonSdkApi=%b,isVmSafeMode=%b,isDebuggable=%b,isSystemUi=%b,"
+ + "isUseEmbeddedDex=%b,requestedCompilerFilter=%s,force=%b,"
+ + "shouldDowngrade=%b,alwaysDebuggable=%b => targetCompilerFilter=%s,"
+ + "expectedDexoptTrigger=%d,expectedIsInDalvikCache=%b,"
+ + "expectedIsDebuggable=%b,expectedIsHiddenApiPolicyEnabled=%b",
+ mIsSystem, mIsUpdatedSystemApp, mIsSignedWithPlatformKey, mIsUsesNonSdkApi,
+ mIsVmSafeMode, mIsDebuggable, mIsSystemUi, mIsUseEmbeddedDex,
+ mRequestedCompilerFilter, mForce, mShouldDowngrade, mAlwaysDebuggable,
+ mExpectedCompilerFilter, mExpectedDexoptTrigger, mExpectedIsInDalvikCache,
+ mExpectedIsDebuggable, mExpectedIsHiddenApiPolicyEnabled);
+ }
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
new file mode 100644
index 0000000..1c879d7
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -0,0 +1,642 @@
+/*
+ * 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.OptimizeResult.DexContainerFileOptimizeResult;
+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.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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.Executors;
+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 PrimaryDexOptimizerTest extends PrimaryDexOptimizerTestBase {
+ private final String mDexPath = "/data/app/foo/base.apk";
+ private final ProfilePath mRefProfile =
+ AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
+ private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
+ private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
+ private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
+ private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+ PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
+ private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
+ PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */);
+
+ private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
+ private final ProfilePath mSplit0RefProfile =
+ AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split");
+
+ private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+
+ private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+ private final DexoptResult mDexoptResult = createDexoptResult(false /* cancelled */);
+
+ private OptimizeParams mOptimizeParams =
+ new OptimizeParams.Builder("install").setCompilerFilter("speed-profile").build();
+
+ private PrimaryDexOptimizer mPrimaryDexOptimizer;
+
+ 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(mDexoptResult);
+
+ lenient()
+ .when(mArtd.createCancellationSignal())
+ .thenReturn(mock(IArtdCancellationSignal.class));
+
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, 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(mDexoptResult)
+ .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(mDexoptResult)
+ .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(mDexoptResult)
+ .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(mDexoptResult)
+ .when(mArtd)
+ .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
+ anyInt(), any(), any());
+
+ mPrimaryDexOptimizer.dexopt();
+ }
+
+ @Test
+ public void testDexoptDm() throws Exception {
+ lenient()
+ .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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);
+
+ mPrimaryDexOptimizer.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 createDexoptResult(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
+ // OPTIMIZE_CANCELLED.
+ assertThat(mPrimaryDexOptimizer.dexopt()
+ .stream()
+ .map(DexContainerFileOptimizeResult::getStatus)
+ .collect(Collectors.toList()))
+ .containsExactly(OptimizeResult.OPTIMIZE_CANCELLED);
+
+ // It shouldn't continue after being cancelled on the first file.
+ verify(mArtd, times(1)).createCancellationSignal();
+ verify(mArtd, times(1))
+ .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+ any());
+ }
+
+ @Test
+ public void testDexoptCancelledDuringDexopt() throws Exception {
+ Semaphore dexoptStarted = new Semaphore(0);
+ Semaphore dexoptCancelled = new Semaphore(0);
+ final long TIMEOUT_SEC = 1;
+
+ var artdCancellationSignal = mock(IArtdCancellationSignal.class);
+ when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
+
+ doAnswer(invocation -> {
+ dexoptStarted.release();
+ assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ return createDexoptResult(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<DexContainerFileOptimizeResult>> results =
+ Executors.newSingleThreadExecutor().submit(
+ () -> { return mPrimaryDexOptimizer.dexopt(); });
+
+ assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+
+ mCancellationSignal.cancel();
+
+ assertThat(results.get()
+ .stream()
+ .map(DexContainerFileOptimizeResult::getStatus)
+ .collect(Collectors.toList()))
+ .containsExactly(OptimizeResult.OPTIMIZE_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 {
+ mOptimizeParams =
+ new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+ .setSplitName(null)
+ .build();
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+
+ mPrimaryDexOptimizer.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 {
+ mOptimizeParams =
+ new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+ .setSplitName("split_0")
+ .build();
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+
+ mPrimaryDexOptimizer.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());
+ }
+
+ 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/PrimaryDexOptimizerTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
new file mode 100644
index 0000000..14047a0
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
@@ -0,0 +1,196 @@
+/*
+ * 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.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 android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+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 PrimaryDexOptimizerTestBase {
+ protected static final String PKG_NAME = "com.example.foo";
+ protected static final int UID = 12345;
+ protected static final int SHARED_GID = UserHandle.getSharedAppGid(UID);
+
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+ @Mock protected PrimaryDexOptimizer.Injector mInjector;
+ @Mock protected IArtd mArtd;
+ @Mock protected UserManager mUserManager;
+ @Mock protected DexUseManagerLocal mDexUseManager;
+ protected PackageState mPkgState;
+ protected AndroidPackage mPkg;
+ protected PackageUserState mPkgUserStateNotInstalled;
+ protected PackageUserState mPkgUserStateInstalled;
+ protected CancellationSignal mCancellationSignal;
+
+ @Before
+ public void setUp() throws Exception {
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+ lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+ lenient().when(mInjector.getUserManager()).thenReturn(mUserManager);
+ lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+
+ lenient()
+ .when(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);
+
+ mPkgUserStateNotInstalled = createPackageUserState(false /* installed */);
+ mPkgUserStateInstalled = createPackageUserState(true /* installed */);
+ mPkgState = createPackageState();
+ mPkg = mPkgState.getAndroidPackage();
+ mCancellationSignal = new CancellationSignal();
+ }
+
+ private AndroidPackage createPackage() {
+ // This package has the base APK and one split APK that has code.
+ AndroidPackage pkg = mock(AndroidPackage.class);
+ var baseSplit = mock(AndroidPackageSplit.class);
+ lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
+ lenient().when(baseSplit.isHasCode()).thenReturn(true);
+ lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+ var split0 = mock(AndroidPackageSplit.class);
+ lenient().when(split0.getName()).thenReturn("split_0");
+ lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+ lenient().when(split0.isHasCode()).thenReturn(true);
+
+ var split1 = mock(AndroidPackageSplit.class);
+ lenient().when(split1.getName()).thenReturn("split_1");
+ lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+ lenient().when(split1.isHasCode()).thenReturn(false);
+
+ var splits = List.of(baseSplit, split0, split1);
+ lenient().when(pkg.getSplits()).thenReturn(splits);
+
+ lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+ lenient().when(pkg.isDebuggable()).thenReturn(false);
+ lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+ lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+ lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+ lenient().when(pkg.getSdkLibraryName()).thenReturn(null);
+ lenient().when(pkg.getStaticSharedLibraryName()).thenReturn(null);
+ lenient().when(pkg.getLibraryNames()).thenReturn(new ArrayList<>());
+ 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.getUsesLibraries()).thenReturn(new ArrayList<>());
+ lenient()
+ .when(pkgState.getStateForUser(any()))
+ .thenReturn(mPkgUserStateNotInstalled);
+ AndroidPackage pkg = createPackage();
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+ 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 DexoptResult createDexoptResult(boolean cancelled, long wallTimeMs, long cpuTimeMs,
+ long sizeBytes, long sizeBeforeBytes) {
+ var result = new DexoptResult();
+ result.cancelled = cancelled;
+ result.wallTimeMs = wallTimeMs;
+ result.cpuTimeMs = cpuTimeMs;
+ result.sizeBytes = sizeBytes;
+ result.sizeBeforeBytes = sizeBeforeBytes;
+ return result;
+ }
+
+ protected DexoptResult createDexoptResult(boolean cancelled) {
+ return createDexoptResult(cancelled, 0 /* wallTimeMs */, 0 /* cpuTimeMs */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */);
+ }
+}
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..ab703fc
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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 static org.mockito.Mockito.when;
+
+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);
+ 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.
+ when(split0.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+ when(split1.getClassLoaderName()).thenReturn(null);
+ when(split2.getClassLoaderName()).thenReturn(DelegateLastClassLoader.class.getName());
+ when(split3.getClassLoaderName()).thenReturn(DexClassLoader.class.getName());
+ when(split4.getClassLoaderName()).thenReturn(null);
+
+ when(split0.getDependencies()).thenReturn(List.of(split2));
+ when(split2.getDependencies()).thenReturn(List.of(baseSplit));
+ when(split4.getDependencies()).thenReturn(List.of(split3));
+ when(pkg.isIsolatedSplitLoading()).thenReturn(true);
+ } else {
+ when(pkg.isIsolatedSplitLoading()).thenReturn(false);
+ }
+
+ return pkg;
+ }
+
+ private PackageState createPackageState() {
+ PackageState pkgState = mock(PackageState.class);
+
+ when(pkgState.getPackageName()).thenReturn("com.example.foo");
+
+ // Base depends on library 2, 3, 4.
+ // Library 2, 4 depends on library 1.
+ List<SharedLibrary> usesLibraryInfos = new ArrayList<>();
+
+ SharedLibrary library1 = mock(SharedLibrary.class);
+ when(library1.getAllCodePaths())
+ .thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar"));
+ when(library1.getDependencies()).thenReturn(null);
+
+ SharedLibrary library2 = mock(SharedLibrary.class);
+ when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
+ when(library2.getDependencies()).thenReturn(List.of(library1));
+ usesLibraryInfos.add(library2);
+
+ SharedLibrary library3 = mock(SharedLibrary.class);
+ when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
+ when(library3.getDependencies()).thenReturn(null);
+ usesLibraryInfos.add(library3);
+
+ SharedLibrary library4 = mock(SharedLibrary.class);
+ when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar"));
+ when(library4.getDependencies()).thenReturn(List.of(library1));
+ usesLibraryInfos.add(library4);
+
+ when(pkgState.getUsesLibraries()).thenReturn(usesLibraryInfos);
+
+ return pkgState;
+ }
+}
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/SecondaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
new file mode 100644
index 0000000..3a5c0e8
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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.SecondaryDexInfo;
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+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.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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 SecondaryDexOptimizerTest {
+ 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 OptimizeParams mOptimizeParams =
+ new OptimizeParams.Builder("bg-dexopt")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX, ArtFlags.FLAG_FOR_SECONDARY_DEX)
+ .build();
+
+ private final ProfilePath mDex1RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_1);
+ private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1);
+ private final ProfilePath mDex2RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_2);
+ private final ProfilePath mDex3RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_3);
+ private final OutputProfile mDex1PrivateOutputProfile =
+ AidlUtils.buildOutputProfileForSecondary(DEX_1, UID, UID, false /* isOtherReadable */);
+
+ private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+
+ private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
+
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+ @Mock private SecondaryDexOptimizer.Injector mInjector;
+ @Mock private IArtd mArtd;
+ @Mock private DexUseManagerLocal mDexUseManager;
+ private PackageState mPkgState;
+ private AndroidPackage mPkg;
+ private CancellationSignal mCancellationSignal;
+
+ private SecondaryDexOptimizer mSecondaryDexOptimizer;
+
+ @Before
+ public void setUp() throws Exception {
+ lenient()
+ .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+ .thenReturn(false);
+ lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+ lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+ // No ISA translation.
+ lenient()
+ .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+ .thenReturn("");
+
+ lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+ lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+ lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+
+ List<DetailedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
+ lenient()
+ .when(mDexUseManager.getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME)))
+ .thenReturn(secondaryDexInfo);
+
+ mPkgState = createPackageState();
+ mPkg = mPkgState.getAndroidPackage();
+ mCancellationSignal = new CancellationSignal();
+
+ prepareProfiles();
+
+ // Dexopt is always needed and successful.
+ lenient()
+ .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+ .thenReturn(dexoptIsNeeded());
+ lenient()
+ .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+ any(), any()))
+ .thenReturn(createDexoptResult());
+
+ lenient()
+ .when(mArtd.createCancellationSignal())
+ .thenReturn(mock(IArtdCancellationSignal.class));
+
+ mSecondaryDexOptimizer = new SecondaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ }
+
+ @Test
+ public void testDexopt() throws Exception {
+ assertThat(mSecondaryDexOptimizer.dexopt())
+ .comparingElementsUsing(TestingUtils.<DexContainerFileOptimizeResult>deepEquality())
+ .containsExactly(
+ new DexContainerFileOptimizeResult(DEX_1, true /* isPrimaryAbi */,
+ "arm64-v8a", "speed-profile", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult(DEX_2, true /* isPrimaryAbi */,
+ "arm64-v8a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult(DEX_2, false /* isPrimaryAbi */,
+ "armeabi-v7a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
+ new DexContainerFileOptimizeResult(DEX_3, true /* isPrimaryAbi */,
+ "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+ 0 /* sizeBytes */, 0 /* sizeBeforeBytes */));
+
+ // It should use profile for dex 1.
+
+ verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile),
+ deepEq(mDex1PrivateOutputProfile), deepEq(List.of(DEX_1)),
+ deepEq(mMergeProfileOptions));
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger));
+ checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64",
+ ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1");
+
+ verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath));
+
+ verify(mArtd).deleteProfile(deepEq(mDex1CurProfile));
+
+ // It should use "speed" for dex 2 for both ISAs and make the artifacts public.
+
+ verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any());
+ verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any(), any());
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(
+ verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(
+ verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+ // It should use "verify" for dex 3 and make the artifacts private.
+
+ verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any());
+ verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any(), any());
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify",
+ null /* classLoaderContext */, false /* isPublic */);
+ }
+
+ private AndroidPackage createPackage() {
+ var pkg = mock(AndroidPackage.class);
+ lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+ lenient().when(pkg.isDebuggable()).thenReturn(false);
+ lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+ lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+ lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+ return pkg;
+ }
+
+ private PackageState createPackageState() {
+ // TODO(b/254029037): Change PackageSetting to PackageState.
+ var pkgState = mock(PackageSetting.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);
+ AndroidPackage pkg = createPackage();
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+
+ // TODO(b/254029037): Mock the real API instead of the hidden API.
+ var transientState = mock(PackageStateUnserialized.class);
+ lenient().when(transientState.getOverrideSeInfo()).thenReturn("se-info");
+ lenient().when(pkgState.getTransientState()).thenReturn(transientState);
+
+ 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 DexoptResult createDexoptResult() {
+ var result = new DexoptResult();
+ 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..93e927b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.art;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemProperties;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.Utils;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UtilsTest {
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+ @Before
+ public void setUp() throws Exception {
+ lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("arm64");
+ lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86"))).thenReturn("arm");
+
+ // In reality, the preferred ABI should be either the native 64 bit ABI or the native 32 bit
+ // ABI, but we use a different value here to make sure the value is used only if expected.
+ lenient().when(Constants.getPreferredAbi()).thenReturn("x86");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+ }
+
+ @Test
+ public void testCollectionIsEmptyTrue() {
+ assertThat(Utils.isEmpty(List.of())).isTrue();
+ }
+
+ @Test
+ public void testCollectionIsEmptyFalse() {
+ assertThat(Utils.isEmpty(List.of(1))).isFalse();
+ }
+
+ @Test
+ public void testSparseArrayIsEmptyTrue() {
+ assertThat(Utils.isEmpty(new SparseArray<Integer>())).isTrue();
+ }
+
+ @Test
+ public void testSparseArrayIsEmptyFalse() {
+ SparseArray<Integer> array = new SparseArray<>();
+ array.put(1, 1);
+ assertThat(Utils.isEmpty(array)).isFalse();
+ }
+
+ @Test
+ public void testArrayIsEmptyTrue() {
+ assertThat(Utils.isEmpty(new int[0])).isTrue();
+ }
+
+ @Test
+ public void testArrayIsEmptyFalse() {
+ assertThat(Utils.isEmpty(new int[] {1})).isFalse();
+ }
+
+ @Test
+ public void testGetAllAbis() {
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+ when(pkgState.getSecondaryCpuAbi()).thenReturn("arm64-v8a");
+
+ assertThat(Utils.getAllAbis(pkgState))
+ .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */),
+ Utils.Abi.create("arm64-v8a", "arm64", false /* isPrimaryAbi */));
+ }
+
+ @Test
+ public void testGetAllAbisTranslated() {
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+ when(pkgState.getSecondaryCpuAbi()).thenReturn("x86");
+
+ assertThat(Utils.getAllAbis(pkgState))
+ .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */),
+ Utils.Abi.create("armeabi-v7a", "arm", false /* isPrimaryAbi */));
+ }
+
+ @Test
+ public void testGetAllAbisPrimaryOnly() {
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a");
+ when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+ assertThat(Utils.getAllAbis(pkgState))
+ .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */));
+ }
+
+ @Test
+ public void testGetAllAbisNone() {
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn(null);
+ when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+ assertThat(Utils.getAllAbis(pkgState))
+ .containsExactly(Utils.Abi.create("x86", "x86", true /* isPrimaryAbi */));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetAllAbisInvalidNativeIsa() {
+ lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("x86");
+
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+ when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+ Utils.getAllAbis(pkgState);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetAllAbisUnsupportedTranslation() {
+ lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("");
+
+ var pkgState = mock(PackageState.class);
+ when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64");
+ when(pkgState.getSecondaryCpuAbi()).thenReturn(null);
+
+ Utils.getAllAbis(pkgState);
+ }
+
+ @Test
+ public void testImplies() {
+ assertThat(Utils.implies(false, false)).isTrue();
+ assertThat(Utils.implies(false, true)).isTrue();
+ assertThat(Utils.implies(true, false)).isFalse();
+ assertThat(Utils.implies(true, true)).isTrue();
+ }
+
+ @Test
+ public void testCheck() {
+ Utils.check(true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCheckFailed() throws Exception {
+ Utils.check(false);
+ }
+
+ @Test
+ public void testExecuteAndWait() {
+ Executor executor = Executors.newSingleThreadExecutor();
+ 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 = Executors.newSingleThreadExecutor();
+ Utils.executeAndWait(executor, () -> { throw new IllegalArgumentException(); });
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/model/OptimizeParamsTest.java b/libartservice/service/javatests/com/android/server/art/model/OptimizeParamsTest.java
new file mode 100644
index 0000000..1578241
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/model/OptimizeParamsTest.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 OptimizeParamsTest {
+ @Test
+ public void testBuild() {
+ new OptimizeParams.Builder("install").build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildEmptyReason() {
+ new OptimizeParams.Builder("").build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildInvalidCompilerFilter() {
+ new OptimizeParams.Builder("install").setCompilerFilter("invalid").build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildInvalidPriorityClass() {
+ new OptimizeParams.Builder("install").setPriorityClass(101).build();
+ }
+
+ @Test
+ public void testBuildCustomReason() {
+ new OptimizeParams.Builder("custom")
+ .setCompilerFilter("speed")
+ .setPriorityClass(90)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildCustomReasonEmptyCompilerFilter() {
+ new OptimizeParams.Builder("custom")
+ .setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildCustomReasonEmptyPriorityClass() {
+ new OptimizeParams.Builder("custom").setCompilerFilter("speed").build();
+ }
+
+ @Test
+ public void testSingleSplit() {
+ new OptimizeParams.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 OptimizeParams.Builder("install")
+ .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT)
+ .setSplitName("split_0")
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSingleSplitSecondaryFlag() {
+ new OptimizeParams.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 OptimizeParams.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 OptimizeParams.Builder("install")
+ .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX)
+ .setSplitName("split_0")
+ .build();
+ }
+}
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..9e51c87
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+public final class TestingUtils {
+ private static final String TAG = "TestingUtils";
+
+ private TestingUtils() {}
+
+ /**
+ * Recursively compares two objects using reflection. Returns true if the two objects are equal.
+ * For simplicity, this method only supports types that every field is a primitive type, a
+ * string, a {@link List}, or a supported type.
+ */
+ public static boolean deepEquals(
+ @Nullable Object a, @Nullable Object b, @NonNull StringBuilder errorMsg) {
+ try {
+ if (a == null && b == null) {
+ return true;
+ }
+ if (a == null || b == null) {
+ errorMsg.append(String.format("Nullability mismatch: %s != %s",
+ a == null ? "null" : "nonnull", b == null ? "null" : "nonnull"));
+ return false;
+ }
+ if (a instanceof List && b instanceof List) {
+ return listDeepEquals((List<?>) a, (List<?>) b, errorMsg);
+ }
+ if (a.getClass() != b.getClass()) {
+ errorMsg.append(
+ String.format("Type mismatch: %s != %s", a.getClass(), b.getClass()));
+ return false;
+ }
+ if (a.getClass() == String.class) {
+ if (!a.equals(b)) {
+ errorMsg.append(String.format("%s != %s", a, b));
+ }
+ return a.equals(b);
+ }
+ if (a.getClass().isArray()) {
+ throw new UnsupportedOperationException("Array type is not supported");
+ }
+ for (Field field : a.getClass().getDeclaredFields()) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ field.setAccessible(true);
+ if (field.getType().isPrimitive()) {
+ if (!field.get(a).equals(field.get(b))) {
+ errorMsg.append(String.format("Field %s mismatch: %s != %s",
+ field.getName(), field.get(a), field.get(b)));
+ return false;
+ }
+ } else if (!deepEquals(field.get(a), field.get(b), errorMsg)) {
+ errorMsg.insert(0, String.format("Field %s mismatch: ", field.getName()));
+ return false;
+ }
+ }
+ return true;
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Same as above, but ignores any error message. */
+ public static boolean deepEquals(@Nullable Object a, @Nullable Object b) {
+ var errorMsgIgnored = new StringBuilder();
+ return deepEquals(a, b, errorMsgIgnored);
+ }
+
+ /**
+ * A Mockito argument matcher that uses {@link #deepEquals} to compare objects and logs any
+ * mismatch.
+ */
+ public static <T> T deepEq(@Nullable T expected) {
+ return argThat(arg -> {
+ var errorMsg = new StringBuilder();
+ boolean result = deepEquals(arg, expected, errorMsg);
+ if (!result) {
+ Log.e(TAG, errorMsg.toString());
+ }
+ return result;
+ });
+ }
+
+ /**
+ * A 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..5ef532c
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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 androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TestingUtilsTest {
+ @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);
+ }
+}
+
+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..1599599
--- /dev/null
+++ b/libartservice/service/proto/dex_use.proto
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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;
+}
+
+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;
+}
diff --git a/libarttools/Android.bp b/libarttools/Android.bp
index 3df40a5..f927011 100644
--- a/libarttools/Android.bp
+++ b/libarttools/Android.bp
@@ -49,12 +49,18 @@
art_cc_defaults {
name: "art_libarttools_tests_defaults",
srcs: [
+ "tools/art_exec_test.cc",
+ "tools/cmdline_builder_test.cc",
+ "tools/system_properties_test.cc",
"tools/tools_test.cc",
],
shared_libs: [
"libbase",
"libarttools",
],
+ static_libs: [
+ "libgmock",
+ ],
}
// Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target.
@@ -75,4 +81,44 @@
"art_standalone_gtest_defaults",
"art_libarttools_tests_defaults",
],
+ // Some tests are currently failing (observed on
+ // `cf_x86_64_phone-userdebug`); use a special test configuration for
+ // `art_standalone_libarttools_tests` to filter them out for now.
+ // TODO(b/204649079): Investigate these failures and re-enable these tests.
+ test_config: "art_standalone_libarttools_tests.xml",
+}
+
+// A defaults that contains libprocessgroup and all its dependencies.
+cc_defaults {
+ name: "art_libprocessgroup_defaults",
+ shared_libs: [
+ "libbase",
+ "libcgrouprc",
+ ],
+ static_libs: [
+ "libjsoncpp",
+ "libprocessgroup",
+ ],
+}
+
+cc_binary {
+ name: "art_exec",
+ defaults: [
+ "art_defaults",
+ "art_libprocessgroup_defaults",
+ ],
+ srcs: [
+ "tools/art_exec.cc",
+ ],
+ shared_libs: [
+ "libartbase",
+ "libbase",
+ ],
+ static_libs: [
+ "libcap",
+ ],
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
}
diff --git a/libarttools/art_standalone_libarttools_tests.xml b/libarttools/art_standalone_libarttools_tests.xml
new file mode 100644
index 0000000..41e9c88
--- /dev/null
+++ b/libarttools/art_standalone_libarttools_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.
+-->
+<configuration description="Runs art_standalone_libarttools_tests.">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="art_standalone_libarttools_tests->/data/local/tmp/art_standalone_libarttools_tests/art_standalone_libarttools_tests" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp/art_standalone_libarttools_tests" />
+ <option name="module-name" value="art_standalone_libarttools_tests" />
+ <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+ <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+
+ <!-- The following tests from `art_standalone_runtime_tests` are currently failing when
+ run as 32-bit on a 64-bit device, because they try to execute other system (64-bit)
+ processes but `LD_LIBRARY_PATH` is set to a directory of 32-bit libraries which make
+ them fail to dynamically link to the expected (64-bit) libraries.
+
+ TODO(b/204649079): Investigate these failures and re-enable these tests. -->
+ <option name="exclude-filter" value="*ArtExecTest.DropCapabilities*" />
+ <option name="exclude-filter" value="*ArtExecTest.SetPriority*" />
+ <option name="exclude-filter" value="*ArtExecTest.SetTaskProfiles*" />
+ </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. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/libarttools/tools/art_exec.cc b/libarttools/tools/art_exec.cc
new file mode 100644
index 0000000..48dadb5
--- /dev/null
+++ b/libarttools/tools/art_exec.cc
@@ -0,0 +1,155 @@
+/*
+ * 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 <sys/capability.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "base/macros.h"
+#include "base/scoped_cap.h"
+#include "processgroup/processgroup.h"
+#include "system/thread_defs.h"
+
+namespace {
+
+using ::android::base::ConsumePrefix;
+using ::android::base::Join;
+using ::android::base::Result;
+using ::android::base::Split;
+
+constexpr const char* kUsage =
+ R"(A wrapper binary that configures the process and executes a command.
+
+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.
+)";
+
+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;
+};
+
+[[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 (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 {};
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ android::base::InitLogging(argv);
+
+ Options options = ParseOptions(argc, argv);
+
+ if (!options.task_profiles.empty()) {
+ if (!SetTaskProfiles(/*tid=*/0, options.task_profiles)) {
+ LOG(ERROR) << "Failed to set task profile";
+ return kErrorOther;
+ }
+ }
+
+ if (options.priority.has_value()) {
+ if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
+ PLOG(ERROR) << "Failed to setpriority";
+ return kErrorOther;
+ }
+ }
+
+ if (options.drop_capabilities) {
+ if (auto result = DropInheritableCaps(); !result.ok()) {
+ LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
+ return kErrorOther;
+ }
+ }
+
+ 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..37a1070
--- /dev/null
+++ b/libarttools/tools/art_exec_test.cc
@@ -0,0 +1,180 @@
+/*
+ * 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 "base/common_art_test.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "base/scoped_cap.h"
+#include "exec_utils.h"
+#include "fmt/format.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "system/thread_defs.h"
+
+namespace art {
+namespace {
+
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+using ::testing::HasSubstr;
+
+// clang-tidy incorrectly complaints about the using declaration while the user-defined literal is
+// actually being used.
+using ::fmt::literals::operator""_format; // NOLINT
+
+constexpr uid_t kRoot = 0;
+constexpr uid_t kNobody = 9999;
+
+std::string GetArtBin(const std::string& name) { return "{}/bin/{}"_format(GetArtRoot(), name); }
+
+std::string GetBin(const std::string& name) { return "{}/bin/{}"_format(GetAndroidRoot(), name); }
+
+// Executes the command, waits for it to finish, and keeps it in a waitable state until the current
+// scope exits.
+std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait(
+ std::vector<std::string>& args) {
+ std::vector<char*> execv_args;
+ execv_args.reserve(args.size() + 1);
+ for (std::string& arg : args) {
+ execv_args.push_back(arg.data());
+ }
+ execv_args.push_back(nullptr);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ execv(execv_args[0], execv_args.data());
+ UNREACHABLE();
+ } else if (pid > 0) {
+ siginfo_t info;
+ CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0);
+ CHECK_EQ(info.si_code, CLD_EXITED);
+ CHECK_EQ(info.si_status, 0);
+ std::function<void()> cleanup([=] {
+ siginfo_t info;
+ CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0);
+ });
+ return std::make_pair(pid, make_scope_guard(std::move(cleanup)));
+ } else {
+ LOG(FATAL) << "Failed to call fork";
+ UNREACHABLE();
+ }
+}
+
+// Grants the current process the given root capability.
+void SetCap(cap_flag_t flag, cap_value_t value) {
+ ScopedCap cap(cap_get_proc());
+ CHECK_NE(cap.Get(), nullptr);
+ cap_value_t caps[]{value};
+ CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0);
+ CHECK_EQ(cap_set_proc(cap.Get()), 0);
+}
+
+// Returns true if the given process has the given root capability.
+bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) {
+ ScopedCap cap(cap_get_pid(pid));
+ CHECK_NE(cap.Get(), nullptr);
+ cap_flag_value_t flag_value;
+ CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0);
+ return flag_value == CAP_SET;
+}
+
+class ArtExecTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ testing::Test::SetUp();
+ if (!kIsTargetAndroid) {
+ GTEST_SKIP() << "art_exec is for device only";
+ }
+ if (getuid() != kRoot) {
+ GTEST_SKIP() << "art_exec requires root";
+ }
+ art_exec_bin_ = GetArtBin("art_exec");
+ }
+
+ std::string art_exec_bin_;
+};
+
+TEST_F(ArtExecTest, Command) {
+ std::string error_msg;
+ int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg);
+ ASSERT_EQ(ret, 123) << error_msg;
+}
+
+TEST_F(ArtExecTest, SetTaskProfiles) {
+ std::string filename = "/data/local/tmp/art-exec-test-XXXXXX";
+ ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false));
+ ASSERT_GE(scratch_file.GetFd(), 0);
+
+ std::vector<std::string> args{art_exec_bin_,
+ "--set-task-profile=ProcessCapacityHigh",
+ "--",
+ 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", "--", 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_, "--", 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", "--", GetBin("true")};
+ auto [pid, scope_guard] = ScopedExecAndWait(args);
+ EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
+ }
+}
+
+} // namespace
+} // namespace art
diff --git a/libarttools/tools/cmdline_builder.h b/libarttools/tools/cmdline_builder.h
new file mode 100644
index 0000000..13b79ca
--- /dev/null
+++ b/libarttools/tools/cmdline_builder.h
@@ -0,0 +1,145 @@
+/*
+ * 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 <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;
+ }
+
+ 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..8509f73
--- /dev/null
+++ b/libarttools/tools/cmdline_builder_test.cc
@@ -0,0 +1,120 @@
+/*
+ * 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 "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());
+}
+
+} // namespace
+} // namespace tools
+} // namespace art
diff --git a/libarttools/tools/system_properties.h b/libarttools/tools/system_properties.h
new file mode 100644
index 0000000..06b7bcb
--- /dev/null
+++ b/libarttools/tools/system_properties.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+#define ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+
+#include <string>
+
+#include "android-base/parsebool.h"
+#include "android-base/properties.h"
+
+namespace art {
+namespace tools {
+
+// A class for getting system properties with fallback lookup support. Different from
+// android::base::GetProperty, this class is mockable.
+class SystemProperties {
+ public:
+ virtual ~SystemProperties() = default;
+
+ // Returns the current value of the system property `key`, or `default_value` if the property
+ // doesn't have a value.
+ std::string Get(const std::string& key, const std::string& default_value) const {
+ std::string value = GetProperty(key);
+ if (!value.empty()) {
+ return value;
+ }
+ return default_value;
+ }
+
+ // Same as above, but allows specifying one or more fallback keys. The last argument is a string
+ // default value that will be used if none of the given keys has a value.
+ //
+ // Usage:
+ //
+ // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return "default":
+ // Get("key_1", "key_2", "key_3", /*default_value=*/"default")
+ template <typename... Args>
+ std::string Get(const std::string& key, const std::string& fallback_key, Args... args) const {
+ return Get(key, Get(fallback_key, args...));
+ }
+
+ // Returns the current value of the system property `key` with zero or more fallback keys, or an
+ // empty string if none of the given keys has a value.
+ //
+ // Usage:
+ //
+ // Look up for "key_1". If it doesn't have a value, return an empty string:
+ // GetOrEmpty("key_1")
+ //
+ // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return an empty
+ // string:
+ // GetOrEmpty("key_1", "key_2", "key_3")
+ template <typename... Args>
+ std::string GetOrEmpty(const std::string& key, Args... fallback_keys) const {
+ return Get(key, fallback_keys..., /*default_value=*/"");
+ }
+
+ // Returns the current value of the boolean system property `key`, or `default_value` if the
+ // property doesn't have a value. See `android::base::ParseBool` for how the value is parsed.
+ bool GetBool(const std::string& key, bool default_value) const {
+ android::base::ParseBoolResult result = android::base::ParseBool(GetProperty(key));
+ if (result != android::base::ParseBoolResult::kError) {
+ return result == android::base::ParseBoolResult::kTrue;
+ }
+ return default_value;
+ }
+
+ // Same as above, but allows specifying one or more fallback keys. The last argument is a bool
+ // default value that will be used if none of the given keys has a value.
+ //
+ // Usage:
+ //
+ // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return true:
+ // Get("key_1", "key_2", "key_3", /*default_value=*/true)
+ template <typename... Args>
+ bool GetBool(const std::string& key, const std::string& fallback_key, Args... args) const {
+ return GetBool(key, GetBool(fallback_key, args...));
+ }
+
+ protected:
+ // The single source of truth of system properties. Can be mocked in unit tests.
+ virtual std::string GetProperty(const std::string& key) const {
+ return android::base::GetProperty(key, /*default_value=*/"");
+ }
+};
+
+} // namespace tools
+} // namespace art
+
+#endif // ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
diff --git a/libarttools/tools/system_properties_test.cc b/libarttools/tools/system_properties_test.cc
new file mode 100644
index 0000000..80300f0
--- /dev/null
+++ b/libarttools/tools/system_properties_test.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "system_properties.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::Return;
+
+class MockSystemProperties : public SystemProperties {
+ public:
+ MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class SystemPropertiesTest : public testing::Test {
+ protected:
+ MockSystemProperties system_properties_;
+};
+
+TEST_F(SystemPropertiesTest, Get) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+ EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetWithFallback) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+ EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+ EXPECT_EQ(system_properties_.Get("key_1", "key_2", "key_3", /*default_value=*/"default"),
+ "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetDefault) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "default");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmpty) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+ EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyWithFallback) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+ EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+ EXPECT_EQ(system_properties_.GetOrEmpty("key_1", "key_2", "key_3"), "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyDefault) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "");
+}
+
+TEST_F(SystemPropertiesTest, GetBoolTrue) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("true"));
+ EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolFalse) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("false"));
+ EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), false);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolWithFallback) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("true"));
+ EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("false"));
+ EXPECT_EQ(system_properties_.GetBool("key_1", "key_2", "key_3", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolDefault) {
+ EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+ EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), true);
+}
+
+} // namespace
+} // namespace tools
+} // namespace art
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index bb48713..83da564 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2388,8 +2388,8 @@
}
bool ProfileCompilationInfo::UpdateProfileKeys(
- const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* updated) {
- *updated = false;
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* matched) {
+ *matched = false;
for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
for (const std::unique_ptr<DexFileData>& dex_data : info_) {
if (dex_data->checksum == dex_file->GetLocationChecksum() &&
@@ -2409,8 +2409,8 @@
// form the old key.
dex_data->profile_key = MigrateAnnotationInfo(new_profile_key, dex_data->profile_key);
profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
- *updated = true;
}
+ *matched = true;
}
}
}
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 76cbf9a..27902ad 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -646,9 +646,9 @@
// If the new profile key would collide with an existing key (for a different dex)
// the method returns false. Otherwise it returns true.
//
- // `updated` is set to true if any profile key has been updated by this method.
+ // `matched` is set to true if any profile has matched any input dex file.
bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
- /*out*/ bool* updated);
+ /*out*/ bool* matched);
// Checks if the profile is empty.
bool IsEmpty() const;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 2ee34f2..6d10467 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -956,9 +956,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0);
// Update the profile keys based on the original dex files
- bool updated = false;
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
- ASSERT_TRUE(updated);
+ bool matched = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+ ASSERT_TRUE(matched);
// Verify that we find the methods when searched with the original dex files.
for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -984,9 +984,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0, Hotness::kFlagHot, annotation);
// Update the profile keys based on the original dex files
- bool updated = false;
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
- ASSERT_TRUE(updated);
+ bool matched = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+ ASSERT_TRUE(matched);
// Verify that we find the methods when searched with the original dex files.
for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -1001,7 +1001,33 @@
}
}
-TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoUpdate) {
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkMatchedButNoUpdate) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
+
+ // Both the checksum and the location match the original dex file.
+ ProfileCompilationInfo info;
+ AddMethod(&info, dex1, /*method_idx=*/0);
+
+ // No update should happen, but this should be considered as a happy case.
+ bool matched = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+ ASSERT_TRUE(matched);
+
+ // Verify that we find the methods when searched with the original dex files.
+ for (const std::unique_ptr<const DexFile>& dex : dex_files) {
+ ProfileCompilationInfo::MethodHotness loaded_hotness =
+ GetMethod(info, dex.get(), /*method_idx=*/ 0);
+ ASSERT_TRUE(loaded_hotness.IsHot());
+ }
+
+ // Release the ownership as this is held by the test class;
+ for (std::unique_ptr<const DexFile>& dex : dex_files) {
+ UNUSED(dex.release());
+ }
+}
+
+TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoMatch) {
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files.push_back(std::unique_ptr<const DexFile>(dex1));
@@ -1009,9 +1035,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0);
// Update the profile keys based on the original dex files.
- bool updated = false;
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
- ASSERT_FALSE(updated);
+ bool matched = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &matched));
+ ASSERT_FALSE(matched);
// Verify that we did not perform any update and that we cannot find anything with the new
// location.
@@ -1043,9 +1069,9 @@
// This will cause the rename to fail because an existing entry would already have that name.
AddMethod(&info, dex1_renamed, /*method_idx=*/ 0);
- bool updated = false;
- ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &updated));
- ASSERT_FALSE(updated);
+ bool matched = false;
+ ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &matched));
+ ASSERT_FALSE(matched);
// Release the ownership as this is held by the test class;
for (std::unique_ptr<const DexFile>& dex : dex_files) {
diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h
index 4d2b733..9c9aca9 100644
--- a/profman/include/profman/profman_result.h
+++ b/profman/include/profman/profman_result.h
@@ -57,7 +57,7 @@
// The return codes of running profman with `--copy-and-update-profile-key`.
enum CopyAndUpdateResult {
kCopyAndUpdateSuccess = 0,
- kCopyAndUpdateNoUpdate = 21,
+ kCopyAndUpdateNoMatch = 21,
kCopyAndUpdateErrorFailedToUpdateProfile = 22,
kCopyAndUpdateErrorFailedToSaveProfile = 23,
kCopyAndUpdateErrorFailedToLoadProfile = 24,
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index 4fc8143..f7c4255 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -2068,8 +2068,8 @@
argv_str.push_back("--copy-and-update-profile-key");
std::string error;
- // Must return kCopyAndUpdateNoUpdate.
- ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoUpdate) << error;
+ // Must return kCopyAndUpdateNoMatch.
+ ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoMatch) << error;
// Verify that the content is the same.
std::string output_content;
diff --git a/profman/profman.cc b/profman/profman.cc
index 8e2f3b1..b0a3687 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -1880,8 +1880,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
@@ -1890,7 +1890,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/class_linker.cc b/runtime/class_linker.cc
index 3bb756f..3fc6573 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3218,6 +3218,7 @@
ScopedDefiningClass sdc(self);
StackHandleScope<3> hs(self);
metrics::AutoTimer timer{GetMetrics()->ClassLoadingTotalTime()};
+ metrics::AutoTimer timeDelta{GetMetrics()->ClassLoadingTotalTimeDelta()};
auto klass = hs.NewHandle<mirror::Class>(nullptr);
// Load the class from the dex file.
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index ccc5c73..806ab5e 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -51,7 +51,10 @@
// Special encoding used to denote a foreign ClassLoader was found when trying to encode class
// loader contexts for each classpath element in a ClassLoader. See
- // EncodeClassPathContextsForClassLoader. Keep in sync with PackageDexUsage in the framework.
+ // EncodeClassPathContextsForClassLoader. Keep in sync with PackageDexUsage in the framework
+ // (frameworks/base/services/core/java/com/android/server/pm/dex/PackageDexUsage.java) and
+ // DexUseManager in ART Services
+ // (art/libartservice/service/java/com/android/server/art/DexUseManager.java).
static constexpr const char* kUnsupportedClassLoaderContextEncoding =
"=UnsupportedClassLoaderContext=";
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index ba9d48a..44aeeff 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -160,23 +160,31 @@
if (young_gen_) {
gc_time_histogram_ = metrics->YoungGcCollectionTime();
metrics_gc_count_ = metrics->YoungGcCount();
+ metrics_gc_count_delta_ = metrics->YoungGcCountDelta();
gc_throughput_histogram_ = metrics->YoungGcThroughput();
gc_tracing_throughput_hist_ = metrics->YoungGcTracingThroughput();
gc_throughput_avg_ = metrics->YoungGcThroughputAvg();
gc_tracing_throughput_avg_ = metrics->YoungGcTracingThroughputAvg();
gc_scanned_bytes_ = metrics->YoungGcScannedBytes();
+ gc_scanned_bytes_delta_ = metrics->YoungGcScannedBytesDelta();
gc_freed_bytes_ = metrics->YoungGcFreedBytes();
+ gc_freed_bytes_delta_ = metrics->YoungGcFreedBytesDelta();
gc_duration_ = metrics->YoungGcDuration();
+ gc_duration_delta_ = metrics->YoungGcDurationDelta();
} else {
gc_time_histogram_ = metrics->FullGcCollectionTime();
metrics_gc_count_ = metrics->FullGcCount();
+ metrics_gc_count_delta_ = metrics->FullGcCountDelta();
gc_throughput_histogram_ = metrics->FullGcThroughput();
gc_tracing_throughput_hist_ = metrics->FullGcTracingThroughput();
gc_throughput_avg_ = metrics->FullGcThroughputAvg();
gc_tracing_throughput_avg_ = metrics->FullGcTracingThroughputAvg();
gc_scanned_bytes_ = metrics->FullGcScannedBytes();
+ gc_scanned_bytes_delta_ = metrics->FullGcScannedBytesDelta();
gc_freed_bytes_ = metrics->FullGcFreedBytes();
+ gc_freed_bytes_delta_ = metrics->FullGcFreedBytesDelta();
gc_duration_ = metrics->FullGcDuration();
+ gc_duration_delta_ = metrics->FullGcDurationDelta();
}
}
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 4efe48c..03a432d 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -72,13 +72,17 @@
freed_bytes_histogram_((name_ + " freed-bytes").c_str(), kMemBucketSize, kMemBucketCount),
gc_time_histogram_(nullptr),
metrics_gc_count_(nullptr),
+ metrics_gc_count_delta_(nullptr),
gc_throughput_histogram_(nullptr),
gc_tracing_throughput_hist_(nullptr),
gc_throughput_avg_(nullptr),
gc_tracing_throughput_avg_(nullptr),
gc_scanned_bytes_(nullptr),
+ gc_scanned_bytes_delta_(nullptr),
gc_freed_bytes_(nullptr),
+ gc_freed_bytes_delta_(nullptr),
gc_duration_(nullptr),
+ gc_duration_delta_(nullptr),
cumulative_timings_(name),
pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true),
is_transaction_active_(false),
@@ -203,11 +207,15 @@
const uint64_t total_pause_time_us = total_pause_time_ns / 1'000;
metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time_us);
metrics->GcWorldStopTime()->Add(total_pause_time_us);
+ metrics->GcWorldStopTimeDelta()->Add(total_pause_time_us);
metrics->GcWorldStopCount()->AddOne();
+ metrics->GcWorldStopCountDelta()->AddOne();
// Report total collection time of all GCs put together.
metrics->TotalGcCollectionTime()->Add(NsToMs(duration_ns));
+ metrics->TotalGcCollectionTimeDelta()->Add(NsToMs(duration_ns));
if (are_metrics_initialized_) {
metrics_gc_count_->Add(1);
+ metrics_gc_count_delta_->Add(1);
// Report GC time in milliseconds.
gc_time_histogram_->Add(NsToMs(duration_ns));
// Throughput in bytes/s. Add 1us to prevent possible division by 0.
@@ -224,8 +232,11 @@
gc_throughput_avg_->Add(throughput);
gc_scanned_bytes_->Add(current_iteration->GetScannedBytes());
+ gc_scanned_bytes_delta_->Add(current_iteration->GetScannedBytes());
gc_freed_bytes_->Add(current_iteration->GetFreedBytes());
+ gc_freed_bytes_delta_->Add(current_iteration->GetFreedBytes());
gc_duration_->Add(NsToMs(current_iteration->GetDurationNs()));
+ gc_duration_delta_->Add(NsToMs(current_iteration->GetDurationNs()));
}
is_transaction_active_ = false;
}
diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h
index d11aea3..948a868 100644
--- a/runtime/gc/collector/garbage_collector.h
+++ b/runtime/gc/collector/garbage_collector.h
@@ -162,13 +162,17 @@
Histogram<size_t> freed_bytes_histogram_;
metrics::MetricsBase<int64_t>* gc_time_histogram_;
metrics::MetricsBase<uint64_t>* metrics_gc_count_;
+ metrics::MetricsBase<uint64_t>* metrics_gc_count_delta_;
metrics::MetricsBase<int64_t>* gc_throughput_histogram_;
metrics::MetricsBase<int64_t>* gc_tracing_throughput_hist_;
metrics::MetricsBase<uint64_t>* gc_throughput_avg_;
metrics::MetricsBase<uint64_t>* gc_tracing_throughput_avg_;
metrics::MetricsBase<uint64_t>* gc_scanned_bytes_;
+ metrics::MetricsBase<uint64_t>* gc_scanned_bytes_delta_;
metrics::MetricsBase<uint64_t>* gc_freed_bytes_;
+ metrics::MetricsBase<uint64_t>* gc_freed_bytes_delta_;
metrics::MetricsBase<uint64_t>* gc_duration_;
+ metrics::MetricsBase<uint64_t>* gc_duration_delta_;
uint64_t total_thread_cpu_time_ns_;
uint64_t total_time_ns_;
uint64_t total_freed_objects_;
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 9c76060..922b588 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -214,6 +214,7 @@
need_gc = true;
}
GetMetrics()->TotalBytesAllocated()->Add(bytes_tl_bulk_allocated);
+ GetMetrics()->TotalBytesAllocatedDelta()->Add(bytes_tl_bulk_allocated);
}
}
if (kIsDebugBuild && Runtime::Current()->IsStarted()) {
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index d746ade..8466753 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -23,7 +23,6 @@
#include <unistd.h>
#include "android-base/strings.h"
-
#include "art_method-inl.h"
#include "base/compiler_filter.h"
#include "base/enums.h"
@@ -32,6 +31,7 @@
#include "base/stl_util.h"
#include "base/systrace.h"
#include "base/time_utils.h"
+#include "base/unix_file/fd_file.h"
#include "class_table-inl.h"
#include "dex/dex_file_loader.h"
#include "dex_reference_collection.h"
@@ -871,10 +871,23 @@
{
ProfileCompilationInfo info(Runtime::Current()->GetArenaPool(),
/*for_boot_image=*/ options_.GetProfileBootClassPath());
- if (!info.Load(filename, /*clear_if_invalid=*/ true)) {
- LOG(WARNING) << "Could not forcefully load profile " << filename;
- continue;
+ if (OS::FileExists(filename.c_str())) {
+ if (!info.Load(filename, /*clear_if_invalid=*/true)) {
+ LOG(WARNING) << "Could not forcefully load profile " << filename;
+ continue;
+ }
+ } else {
+ // Create a file if it doesn't exist.
+ unix_file::FdFile file(filename.c_str(),
+ O_WRONLY | O_TRUNC | O_CREAT,
+ S_IRUSR | S_IWUSR,
+ /*check_usage=*/false);
+ if (!file.IsValid()) {
+ LOG(WARNING) << "Could not create profile " << filename;
+ continue;
+ }
}
+
uint64_t last_save_number_of_methods = info.GetNumberOfMethods();
uint64_t last_save_number_of_classes = info.GetNumberOfResolvedClasses();
VLOG(profiler) << "last_save_number_of_methods=" << last_save_number_of_methods
diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc
index 28ca997..736ef7f 100644
--- a/runtime/metrics/reporter.cc
+++ b/runtime/metrics/reporter.cc
@@ -196,12 +196,10 @@
}
}
-const ArtMetrics* MetricsReporter::GetMetrics() {
- return runtime_->GetMetrics();
-}
+ArtMetrics* MetricsReporter::GetMetrics() { return runtime_->GetMetrics(); }
void MetricsReporter::ReportMetrics() {
- const ArtMetrics* metrics = GetMetrics();
+ ArtMetrics* metrics = GetMetrics();
if (!session_started_) {
for (auto& backend : backends_) {
@@ -210,9 +208,7 @@
session_started_ = true;
}
- for (auto& backend : backends_) {
- metrics->ReportAllMetrics(backend.get());
- }
+ metrics->ReportAllMetricsAndResetValueMetrics(ToRawPointers(backends_));
}
void MetricsReporter::UpdateSessionInBackends() {
diff --git a/runtime/metrics/reporter.h b/runtime/metrics/reporter.h
index af9e0ca..865815e 100644
--- a/runtime/metrics/reporter.h
+++ b/runtime/metrics/reporter.h
@@ -136,7 +136,7 @@
// Returns the metrics to be reported.
// This exists only for testing purposes so that we can verify reporting with minimum
// runtime interference.
- virtual const ArtMetrics* GetMetrics();
+ virtual ArtMetrics* GetMetrics();
MetricsReporter(const ReportingConfig& config, Runtime* runtime);
diff --git a/runtime/metrics/reporter_test.cc b/runtime/metrics/reporter_test.cc
index 62131d8..848a74e 100644
--- a/runtime/metrics/reporter_test.cc
+++ b/runtime/metrics/reporter_test.cc
@@ -34,13 +34,10 @@
// other runtime setup logic.
class MockMetricsReporter : public MetricsReporter {
protected:
- MockMetricsReporter(const ReportingConfig& config, Runtime* runtime) :
- MetricsReporter(config, runtime),
- art_metrics_(new ArtMetrics()) {}
+ MockMetricsReporter(const ReportingConfig& config, Runtime* runtime)
+ : MetricsReporter(config, runtime), art_metrics_(std::make_unique<ArtMetrics>()) {}
- const ArtMetrics* GetMetrics() override {
- return art_metrics_.get();
- }
+ ArtMetrics* GetMetrics() override { return art_metrics_.get(); }
std::unique_ptr<ArtMetrics> art_metrics_;
diff --git a/runtime/metrics/statsd.cc b/runtime/metrics/statsd.cc
index 036f667..3b498db 100644
--- a/runtime/metrics/statsd.cc
+++ b/runtime/metrics/statsd.cc
@@ -47,27 +47,49 @@
case DatumId::kClassVerificationTotalTime:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_VERIFICATION_TIME_COUNTER_MICROS);
+ case DatumId::kClassVerificationTotalTimeDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_VERIFICATION_TIME_MICROS);
case DatumId::kJitMethodCompileTotalTime:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_JIT_METHOD_COMPILE_TIME_MICROS);
+ case DatumId::kJitMethodCompileTotalTimeDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_JIT_METHOD_COMPILE_TIME_MICROS);
case DatumId::kClassLoadingTotalTime:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_LOADING_TIME_COUNTER_MICROS);
+ case DatumId::kClassLoadingTotalTimeDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_LOADING_TIME_MICROS);
case DatumId::kClassVerificationCount:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_CLASS_VERIFICATION_COUNT);
+ case DatumId::kClassVerificationCountDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_CLASS_VERIFICATION_COUNT);
case DatumId::kWorldStopTimeDuringGCAvg:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_AVG_MICROS);
case DatumId::kYoungGcCount:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_COUNT);
+ case DatumId::kYoungGcCountDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_COUNT);
case DatumId::kFullGcCount:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_COUNT);
+ case DatumId::kFullGcCountDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_COUNT);
case DatumId::kTotalBytesAllocated:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_TOTAL_BYTES_ALLOCATED);
+ case DatumId::kTotalBytesAllocatedDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_TOTAL_BYTES_ALLOCATED);
case DatumId::kYoungGcCollectionTime:
return std::make_optional(
statsd::
@@ -86,6 +108,9 @@
case DatumId::kJitMethodCompileCount:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_JIT_METHOD_COMPILE_COUNT);
+ case DatumId::kJitMethodCompileCountDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_JIT_METHOD_COMPILE_COUNT);
case DatumId::kYoungGcTracingThroughput:
return std::make_optional(
statsd::
@@ -97,6 +122,9 @@
case DatumId::kTotalGcCollectionTime:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_TOTAL_COLLECTION_TIME_MS);
+ case DatumId::kTotalGcCollectionTimeDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_TOTAL_COLLECTION_TIME_MS);
case DatumId::kYoungGcThroughputAvg:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_THROUGHPUT_AVG_MB_PER_SEC);
@@ -112,27 +140,57 @@
case DatumId::kGcWorldStopTime:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_US);
+ case DatumId::kGcWorldStopTimeDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_WORLD_STOP_TIME_US);
case DatumId::kGcWorldStopCount:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_COUNT);
+ case DatumId::kGcWorldStopCountDelta:
+ return std::make_optional(
+ statsd::ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_WORLD_STOP_COUNT);
case DatumId::kYoungGcScannedBytes:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES);
+ case DatumId::kYoungGcScannedBytesDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES);
case DatumId::kYoungGcFreedBytes:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES);
+ case DatumId::kYoungGcFreedBytesDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES);
case DatumId::kYoungGcDuration:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS);
+ case DatumId::kYoungGcDurationDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS);
case DatumId::kFullGcScannedBytes:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES);
+ case DatumId::kFullGcScannedBytesDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES);
case DatumId::kFullGcFreedBytes:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_FREED_BYTES);
+ case DatumId::kFullGcFreedBytesDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_FREED_BYTES);
case DatumId::kFullGcDuration:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_DURATION_MS);
+ case DatumId::kFullGcDurationDelta:
+ return std::make_optional(
+ statsd::
+ ART_DATUM_DELTA_REPORTED__KIND__ART_DATUM_DELTA_GC_FULL_HEAP_COLLECTION_DURATION_MS);
}
}
@@ -247,22 +305,39 @@
void ReportCounter(DatumId counter_type, uint64_t value) override {
std::optional<int32_t> datum_id = EncodeDatumId(counter_type);
- if (datum_id.has_value()) {
- statsd::stats_write(
- statsd::ART_DATUM_REPORTED,
- session_data_.session_id,
- session_data_.uid,
- EncodeCompileFilter(session_data_.compiler_filter),
- EncodeCompilationReason(session_data_.compilation_reason),
- current_timestamp_,
- 0, // TODO: collect and report thread type (0 means UNKNOWN, but that
- // constant is not present in all branches)
- datum_id.value(),
- static_cast<int64_t>(value),
- statsd::ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN,
- statsd::ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN,
- EncodeInstructionSet(kRuntimeISA));
+ if (!datum_id.has_value()) {
+ return;
}
+
+ int32_t atom;
+ switch (counter_type) {
+#define EVENT_METRIC_CASE(name, ...) case DatumId::k##name:
+ ART_EVENT_METRICS(EVENT_METRIC_CASE)
+#undef EVENT_METRIC_CASE
+ atom = statsd::ART_DATUM_REPORTED;
+ break;
+
+#define VALUE_METRIC_CASE(name, type, ...) case DatumId::k##name:
+ ART_VALUE_METRICS(VALUE_METRIC_CASE)
+#undef VALUE_METRIC_CASE
+ atom = statsd::ART_DATUM_DELTA_REPORTED;
+ break;
+ }
+
+ statsd::stats_write(
+ atom,
+ session_data_.session_id,
+ session_data_.uid,
+ EncodeCompileFilter(session_data_.compiler_filter),
+ EncodeCompilationReason(session_data_.compilation_reason),
+ current_timestamp_,
+ 0, // TODO: collect and report thread type (0 means UNKNOWN, but that
+ // constant is not present in all branches)
+ datum_id.value(),
+ static_cast<int64_t>(value),
+ statsd::ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN,
+ statsd::ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN,
+ EncodeInstructionSet(kRuntimeISA));
}
void ReportHistogram(DatumId /*histogram_type*/,
diff --git a/runtime/verifier/class_verifier.cc b/runtime/verifier/class_verifier.cc
index 8c541f8..8946bb2 100644
--- a/runtime/verifier/class_verifier.cc
+++ b/runtime/verifier/class_verifier.cc
@@ -176,6 +176,9 @@
GetMetrics()->ClassVerificationCount()->AddOne();
+ GetMetrics()->ClassVerificationTotalTimeDelta()->Add(elapsed_time_microseconds);
+ GetMetrics()->ClassVerificationCountDelta()->AddOne();
+
if (failure_data.kind == verifier::FailureKind::kHardFailure && callbacks != nullptr) {
ClassReference ref(dex_file, dex_file->GetIndexForClassDef(class_def));
callbacks->ClassRejected(ref);
diff --git a/test/2232-write-metrics-to-log/expected-stderr.txt b/test/2232-write-metrics-to-log/expected-stderr.txt
index 9546999..5bc27fe 100644
--- a/test/2232-write-metrics-to-log/expected-stderr.txt
+++ b/test/2232-write-metrics-to-log/expected-stderr.txt
@@ -1 +1 @@
-ClassVerificationTotalTime
+ClassVerificationTotalTimeDelta
diff --git a/test/2232-write-metrics-to-log/run.py b/test/2232-write-metrics-to-log/run.py
index 980da22..89fe040 100644
--- a/test/2232-write-metrics-to-log/run.py
+++ b/test/2232-write-metrics-to-log/run.py
@@ -26,5 +26,5 @@
# Check that one of the metrics appears in stderr.
ctx.run(
- fr"sed -i -n 's/.*\(ClassVerificationTotalTime\).*/\1/p' '{args.stderr_file}'"
+ fr"sed -i -n 's/.*\(ClassVerificationTotalTimeDelta\).*/\1/p' '{args.stderr_file}'"
)
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 5b1a448..37a8e6c 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -39,8 +39,12 @@
File.class, Method.class);
testAddMethodToProfile(file, appMethod);
+ // Delete the file to check that the runtime can save the profile even if the file doesn't
+ // exist.
+ file.delete();
+
// Test that the profile saves a boot class path method with a profiling info.
- Method bootMethod = File.class.getDeclaredMethod("delete");
+ Method bootMethod = File.class.getDeclaredMethod("exists");
if (bootMethod.getDeclaringClass().getClassLoader() != Object.class.getClassLoader()) {
System.out.println("Class loader does not match boot class");
}
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index f82232a..3a54120 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -226,12 +226,10 @@
"art_standalone_artd_tests",
"art_standalone_cmdline_tests",
"art_standalone_compiler_tests",
- # Temporarily disable this test as it is failing with ART module prebuilts (see b/243510263).
- ### "art_standalone_dex2oat_tests",
+ "art_standalone_dex2oat_tests",
"art_standalone_dexdump_tests",
"art_standalone_dexlist_tests",
- # Temporarily disable this test as it is failing with ART module prebuilts (see b/243507635).
- ### "art_standalone_libartbase_tests",
+ "art_standalone_libartbase_tests",
"art_standalone_libartpalette_tests",
"art_standalone_libartservice_tests",
"art_standalone_libarttools_tests",