diff options
author | 2023-08-25 13:34:22 -0700 | |
---|---|---|
committer | 2023-08-25 13:34:22 -0700 | |
commit | 59c2c6c6ec304ea724c41666f6bdd6ef80db33c1 (patch) | |
tree | a81384d7f77d498ca5be6b9620ce47b4d6d4ff55 | |
parent | 6869ce9307283d83e45f93613595419519ee7f8e (diff) | |
parent | 11777c13f04ab9adde15f926a26ad439c099f8de (diff) |
Merge Android U (ab/10368041)
Bug: 291102124
Merged-In: Id2163eba86d9d9a57d7597d1f5b04d7e706ae667
Change-Id: I02f3965720916b52675d58f45dc428d6530d7b09
112 files changed, 22675 insertions, 844 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index 9c744111dd..87deca6f46 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1354,6 +1354,9 @@ ] }, { + "name": "art_standalone_dex2oat_tests[com.google.android.art.apex]" + }, + { "name": "art_standalone_dexdump_tests[com.google.android.art.apex]" }, { @@ -1363,6 +1366,9 @@ "name": "art_standalone_dexoptanalyzer_tests[com.google.android.art.apex]" }, { + "name": "art_standalone_libartbase_tests[com.google.android.art.apex]" + }, + { "name": "art_standalone_libartpalette_tests[com.google.android.art.apex]", "options": [ { @@ -2771,6 +2777,9 @@ "name": "art_standalone_compiler_tests" }, { + "name": "art_standalone_dex2oat_tests" + }, + { "name": "art_standalone_dexdump_tests" }, { @@ -2780,6 +2789,9 @@ "name": "art_standalone_dexoptanalyzer_tests" }, { + "name": "art_standalone_libartbase_tests" + }, + { "name": "art_standalone_libartpalette_tests" }, { @@ -4177,6 +4189,9 @@ "name": "art_standalone_compiler_tests" }, { + "name": "art_standalone_dex2oat_tests" + }, + { "name": "art_standalone_dexdump_tests" }, { @@ -4186,6 +4201,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 4c551c975b..1288ebcf25 100644 --- a/artd/Android.bp +++ b/artd/Android.bp @@ -27,11 +27,18 @@ cc_defaults { defaults: ["art_defaults"], srcs: [ "artd.cc", + "file_utils.cc", + "path_utils.cc", + ], + header_libs: [ + "art_cmdlineparser_headers", + "profman_headers", ], shared_libs: [ "libarttools", // Contains "libc++fs". "libbase", "libbinder_ndk", + "libselinux", ], static_libs: [ "artd-aidl-ndk", @@ -45,6 +52,7 @@ art_cc_binary { "artd_main.cc", ], shared_libs: [ + "libart", "libartbase", ], apex_available: [ @@ -56,8 +64,17 @@ art_cc_binary { art_cc_defaults { name: "art_artd_tests_defaults", defaults: ["artd_defaults"], + static_libs: [ + "libgmock", + ], srcs: [ "artd_test.cc", + "file_utils_test.cc", + "path_utils_test.cc", + ], + data: [ + ":art-gtest-jars-Main", + ":art-gtest-jars-Nested", ], } @@ -83,4 +100,5 @@ art_cc_test { "art_standalone_gtest_defaults", "art_artd_tests_defaults", ], + test_config_template: "art_standalone_artd_tests.xml", } diff --git a/artd/README.md b/artd/README.md new file mode 100644 index 0000000000..a2dfc099e9 --- /dev/null +++ b/artd/README.md @@ -0,0 +1,16 @@ +## artd + +artd is a component of ART Service. It is a shim service to do tasks that +require elevated permissions that are not available to system_server, such as +manipulation of the file system and invoking dex2oat. It publishes a binder +interface that is internal to ART service's Java code. When it invokes other +binaries, it passes input and output files as FDs and drops capability before +exec. + +### System properties + +artd can be controlled by the system properties listed below. Note that the list +doesn't include options passed to dex2oat and other processes. + +- `dalvik.vm.artd-verbose`: Log verbosity of the artd process. The syntax is the + same as the runtime's `-verbose` flag. diff --git a/artd/art_standalone_artd_tests.xml b/artd/art_standalone_artd_tests.xml new file mode 100644 index 0000000000..9125046d58 --- /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 27a609d7be..afed96543a 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -16,37 +16,1079 @@ #include "artd.h" +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/types.h> #include <unistd.h> +#include <climits> +#include <csignal> +#include <cstdint> +#include <cstring> +#include <filesystem> +#include <functional> +#include <map> +#include <memory> +#include <mutex> +#include <optional> +#include <ostream> #include <string> +#include <string_view> +#include <system_error> +#include <type_traits> +#include <unordered_set> +#include <utility> +#include <vector> #include "aidl/com/android/server/art/BnArtd.h" +#include "aidl/com/android/server/art/DexoptTrigger.h" +#include "aidl/com/android/server/art/IArtdCancellationSignal.h" +#include "android-base/errors.h" +#include "android-base/file.h" #include "android-base/logging.h" #include "android-base/result.h" +#include "android-base/scopeguard.h" +#include "android-base/strings.h" #include "android/binder_auto_utils.h" +#include "android/binder_interface_utils.h" #include "android/binder_manager.h" #include "android/binder_process.h" +#include "base/compiler_filter.h" +#include "base/file_utils.h" +#include "base/globals.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/os.h" +#include "cmdline_types.h" +#include "exec_utils.h" +#include "file_utils.h" +#include "oat_file_assistant.h" +#include "oat_file_assistant_context.h" +#include "path_utils.h" +#include "profman/profman_result.h" +#include "selinux/android.h" +#include "tools/cmdline_builder.h" #include "tools/tools.h" +#ifdef __BIONIC__ +#include <linux/incrementalfs.h> +#endif + namespace art { namespace artd { namespace { +using ::aidl::com::android::server::art::ArtdDexoptResult; +using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::DexoptOptions; +using ::aidl::com::android::server::art::DexoptTrigger; +using ::aidl::com::android::server::art::FileVisibility; +using ::aidl::com::android::server::art::FsPermission; +using ::aidl::com::android::server::art::GetDexoptNeededResult; +using ::aidl::com::android::server::art::GetDexoptStatusResult; +using ::aidl::com::android::server::art::IArtdCancellationSignal; +using ::aidl::com::android::server::art::MergeProfileOptions; +using ::aidl::com::android::server::art::OutputArtifacts; +using ::aidl::com::android::server::art::OutputProfile; +using ::aidl::com::android::server::art::PriorityClass; +using ::aidl::com::android::server::art::ProfilePath; +using ::aidl::com::android::server::art::VdexPath; +using ::android::base::Dirname; using ::android::base::Error; +using ::android::base::Join; +using ::android::base::make_scope_guard; +using ::android::base::ReadFileToString; using ::android::base::Result; +using ::android::base::Split; +using ::android::base::StringReplace; +using ::android::base::WriteStringToFd; +using ::art::tools::CmdlineBuilder; using ::ndk::ScopedAStatus; +using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation; +using TmpProfilePath = ProfilePath::TmpProfilePath; + constexpr const char* kServiceName = "artd"; +constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal"; + +// Timeout for short operations, such as merging profiles. +constexpr int kShortTimeoutSec = 60; // 1 minute. + +// Timeout for long operations, such as compilation. We set it to be smaller than the Package +// Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation +// is called from the Package Manager's thread handler, it will be aborted before that watchdog +// would take down the system server. +constexpr int kLongTimeoutSec = 570; // 9.5 minutes. + +std::optional<int64_t> GetSize(std::string_view path) { + std::error_code ec; + int64_t size = std::filesystem::file_size(path, ec); + if (ec) { + // It is okay if the file does not exist. We don't have to log it. + if (ec.value() != ENOENT) { + LOG(ERROR) << ART_FORMAT("Failed to get the file size of '{}': {}", path, ec.message()); + } + return std::nullopt; + } + return size; +} + +// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an +// error occurs. +int64_t GetSizeAndDeleteFile(const std::string& path) { + std::optional<int64_t> size = GetSize(path); + if (!size.has_value()) { + return 0; + } + + std::error_code ec; + if (!std::filesystem::remove(path, ec)) { + LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", path, ec.message()); + return 0; + } + + return size.value(); +} + +std::string EscapeErrorMessage(const std::string& message) { + return StringReplace(message, std::string("\0", /*n=*/1), "\\0", /*all=*/true); +} + +// Indicates an error that should never happen (e.g., illegal arguments passed by service-art +// internally). System server should crash if this kind of error happens. +ScopedAStatus Fatal(const std::string& message) { + return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, + EscapeErrorMessage(message).c_str()); +} + +// Indicates an error that service-art should handle (e.g., I/O errors, sub-process crashes). +// The scope of the error depends on the function that throws it, so service-art should catch the +// error at every call site and take different actions. +// Ideally, this should be a checked exception or an additional return value that forces service-art +// to handle it, but `ServiceSpecificException` (a separate runtime exception type) is the best +// approximate we have given the limitation of Java and Binder. +ScopedAStatus NonFatal(const std::string& message) { + constexpr int32_t kArtdNonFatalErrorCode = 1; + return ScopedAStatus::fromServiceSpecificErrorWithMessage(kArtdNonFatalErrorCode, + EscapeErrorMessage(message).c_str()); +} + +Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) { + CompilerFilter::Filter compiler_filter; + if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) { + return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str); + } + return compiler_filter; +} + +OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int32_t aidl_value) { + OatFileAssistant::DexOptTrigger trigger{}; + if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) { + trigger.targetFilterIsBetter = true; + } + if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) { + trigger.targetFilterIsSame = true; + } + if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) { + trigger.targetFilterIsWorse = true; + } + if ((aidl_value & static_cast<int32_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) { + trigger.primaryBootImageBecomesUsable = true; + } + if ((aidl_value & static_cast<int32_t>(DexoptTrigger::NEED_EXTRACTION)) != 0) { + trigger.needExtraction = true; + } + return trigger; +} + +ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) { + switch (location) { + case OatFileAssistant::Location::kLocationNoneOrError: + return ArtifactsLocation::NONE_OR_ERROR; + case OatFileAssistant::Location::kLocationOat: + return ArtifactsLocation::DALVIK_CACHE; + case OatFileAssistant::Location::kLocationOdex: + return ArtifactsLocation::NEXT_TO_DEX; + case OatFileAssistant::Location::kLocationDm: + return ArtifactsLocation::DM; + // No default. All cases should be explicitly handled, or the compilation will fail. + } + // This should never happen. Just in case we get a non-enumerator value. + LOG(FATAL) << "Unexpected Location " << location; +} + +Result<void> PrepareArtifactsDir(const std::string& path, const FsPermission& fs_permission) { + std::error_code ec; + bool created = std::filesystem::create_directory(path, ec); + if (ec) { + return Errorf("Failed to create directory '{}': {}", path, ec.message()); + } + + auto cleanup = make_scope_guard([&] { + if (created) { + std::filesystem::remove(path, ec); + } + }); + + if (chmod(path.c_str(), DirFsPermissionToMode(fs_permission)) != 0) { + return ErrnoErrorf("Failed to chmod directory '{}'", path); + } + OR_RETURN(Chown(path, fs_permission)); + + cleanup.Disable(); + return {}; +} + +Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts, + /*out*/ std::string* oat_dir_path) { + if (output_artifacts.artifactsPath.isInDalvikCache) { + return {}; + } + + std::filesystem::path oat_path(OR_RETURN(BuildOatPath(output_artifacts.artifactsPath))); + std::filesystem::path isa_dir = oat_path.parent_path(); + std::filesystem::path oat_dir = isa_dir.parent_path(); + DCHECK_EQ(oat_dir.filename(), "oat"); + + OR_RETURN(PrepareArtifactsDir(oat_dir, output_artifacts.permissionSettings.dirFsPermission)); + OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission)); + *oat_dir_path = oat_dir; + return {}; +} + +Result<void> Restorecon( + const std::string& path, + const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context) { + if (!kIsTargetAndroid) { + return {}; + } + + int res = 0; + if (se_context.has_value()) { + res = selinux_android_restorecon_pkgdir(path.c_str(), + se_context->seInfo.c_str(), + se_context->uid, + SELINUX_ANDROID_RESTORECON_RECURSE); + } else { + res = selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE); + } + if (res != 0) { + return ErrnoErrorf("Failed to restorecon directory '{}'", path); + } + return {}; +} + +Result<FileVisibility> GetFileVisibility(const std::string& file) { + std::error_code ec; + std::filesystem::file_status status = std::filesystem::status(file, ec); + if (!std::filesystem::status_known(status)) { + return Errorf("Failed to get status of '{}': {}", file, ec.message()); + } + if (!std::filesystem::exists(status)) { + return FileVisibility::NOT_FOUND; + } + + return (status.permissions() & std::filesystem::perms::others_read) != + std::filesystem::perms::none ? + FileVisibility::OTHER_READABLE : + FileVisibility::NOT_OTHER_READABLE; +} + +Result<ArtdCancellationSignal*> ToArtdCancellationSignal(IArtdCancellationSignal* input) { + if (input == nullptr) { + return Error() << "Cancellation signal must not be nullptr"; + } + // We cannot use `dynamic_cast` because ART code is compiled with `-fno-rtti`, so we have to check + // the magic number. + int64_t type; + if (!input->getType(&type).isOk() || + type != reinterpret_cast<intptr_t>(kArtdCancellationSignalType)) { + // The cancellation signal must be created by `Artd::createCancellationSignal`. + return Error() << "Invalid cancellation signal type"; + } + return static_cast<ArtdCancellationSignal*>(input); +} + +Result<void> CopyFile(const std::string& src_path, const NewFile& dst_file) { + std::string content; + if (!ReadFileToString(src_path, &content)) { + return Errorf("Failed to read file '{}': {}", src_path, strerror(errno)); + } + if (!WriteStringToFd(content, dst_file.Fd())) { + return Errorf("Failed to write file '{}': {}", dst_file.TempPath(), strerror(errno)); + } + if (fsync(dst_file.Fd()) != 0) { + return Errorf("Failed to flush file '{}': {}", dst_file.TempPath(), strerror(errno)); + } + if (lseek(dst_file.Fd(), /*offset=*/0, SEEK_SET) != 0) { + return Errorf( + "Failed to reset the offset for file '{}': {}", dst_file.TempPath(), strerror(errno)); + } + return {}; +} + +Result<void> SetLogVerbosity() { + std::string options = android::base::GetProperty("dalvik.vm.artd-verbose", /*default_value=*/""); + if (options.empty()) { + return {}; + } + + CmdlineType<LogVerbosity> parser; + CmdlineParseResult<LogVerbosity> result = parser.Parse(options); + if (!result.IsSuccess()) { + return Error() << result.GetMessage(); + } + + gLogVerbosity = result.ReleaseValue(); + return {}; +} + +class FdLogger { + public: + void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); } + void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); } + + std::string GetFds() { + std::vector<int> fds; + fds.reserve(fd_mapping_.size()); + for (const auto& [fd, path] : fd_mapping_) { + fds.push_back(fd); + } + return Join(fds, ':'); + } + + private: + std::vector<std::pair<int, std::string>> fd_mapping_; + + friend std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger); +}; + +std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger) { + for (const auto& [fd, path] : fd_logger.fd_mapping_) { + os << fd << ":" << path << ' '; + } + return os; +} } // namespace +#define OR_RETURN_ERROR(func, expr) \ + ({ \ + decltype(expr)&& tmp = (expr); \ + if (!tmp.ok()) { \ + return (func)(tmp.error().message()); \ + } \ + std::move(tmp).value(); \ + }) + +#define OR_RETURN_FATAL(expr) OR_RETURN_ERROR(Fatal, expr) +#define OR_RETURN_NON_FATAL(expr) OR_RETURN_ERROR(NonFatal, expr) + ScopedAStatus Artd::isAlive(bool* _aidl_return) { *_aidl_return = true; return ScopedAStatus::ok(); } +ScopedAStatus Artd::deleteArtifacts(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) { + std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath)); + + *_aidl_return = 0; + *_aidl_return += GetSizeAndDeleteFile(oat_path); + *_aidl_return += GetSizeAndDeleteFile(OatPathToVdexPath(oat_path)); + *_aidl_return += GetSizeAndDeleteFile(OatPathToArtPath(oat_path)); + + return ScopedAStatus::ok(); +} + +ScopedAStatus Artd::getDexoptStatus(const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + GetDexoptStatusResult* _aidl_return) { + Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext(); + if (!ofa_context.ok()) { + return NonFatal("Failed to get runtime options: " + ofa_context.error().message()); + } + + std::unique_ptr<ClassLoaderContext> context; + std::string error_msg; + auto oat_file_assistant = OatFileAssistant::Create(in_dexFile, + in_instructionSet, + in_classLoaderContext, + /*load_executable=*/false, + /*only_load_trusted_executable=*/true, + ofa_context.value(), + &context, + &error_msg); + if (oat_file_assistant == nullptr) { + return NonFatal("Failed to create OatFileAssistant: " + error_msg); + } + + std::string ignored_odex_status; + oat_file_assistant->GetOptimizationStatus(&_aidl_return->locationDebugString, + &_aidl_return->compilerFilter, + &_aidl_return->compilationReason, + &ignored_odex_status); + + // We ignore odex_status because it is not meaningful. It can only be either "up-to-date", + // "apk-more-recent", or "io-error-no-oat", which means it doesn't give us information in addition + // to what we can learn from compiler_filter because compiler_filter will be the actual compiler + // filter, "run-from-apk-fallback", and "run-from-apk" in those three cases respectively. + DCHECK(ignored_odex_status == "up-to-date" || ignored_odex_status == "apk-more-recent" || + ignored_odex_status == "io-error-no-oat"); + + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile, + const std::string& in_dexFile, + bool* _aidl_return) { + std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + + FdLogger fd_logger; + + CmdlineBuilder art_exec_args; + art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities"); + + CmdlineBuilder args; + args.Add(OR_RETURN_FATAL(GetProfman())); + + Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path); + if (!profile.ok()) { + if (profile.error().code() == ENOENT) { + *_aidl_return = false; + return ScopedAStatus::ok(); + } + return NonFatal( + ART_FORMAT("Failed to open profile '{}': {}", profile_path, profile.error().message())); + } + args.Add("--reference-profile-file-fd=%d", profile.value()->Fd()); + fd_logger.Add(*profile.value()); + + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); + args.Add("--apk-fd=%d", dex_file->Fd()); + fd_logger.Add(*dex_file); + + art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); + + LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") + << "\nOpened FDs: " << fd_logger; + + Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); + if (!result.ok()) { + return NonFatal("Failed to run profman: " + result.error().message()); + } + + LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); + + if (result.value() != ProfmanResult::kSkipCompilationSmallDelta && + result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) { + return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); + } + + *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta; + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, + OutputProfile* in_dst, + const std::string& in_dexFile, + bool* _aidl_return) { + std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src)); + std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath)); + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + + FdLogger fd_logger; + + CmdlineBuilder art_exec_args; + art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities"); + + CmdlineBuilder args; + args.Add(OR_RETURN_FATAL(GetProfman())).Add("--copy-and-update-profile-key"); + + Result<std::unique_ptr<File>> src = OpenFileForReading(src_path); + if (!src.ok()) { + if (src.error().code() == ENOENT) { + *_aidl_return = false; + return ScopedAStatus::ok(); + } + return NonFatal( + ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message())); + } + args.Add("--profile-file-fd=%d", src.value()->Fd()); + fd_logger.Add(*src.value()); + + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); + args.Add("--apk-fd=%d", dex_file->Fd()); + fd_logger.Add(*dex_file); + + std::unique_ptr<NewFile> dst = + OR_RETURN_NON_FATAL(NewFile::Create(dst_path, in_dst->fsPermission)); + args.Add("--reference-profile-file-fd=%d", dst->Fd()); + fd_logger.Add(*dst); + + art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); + + LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") + << "\nOpened FDs: " << fd_logger; + + Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); + if (!result.ok()) { + return NonFatal("Failed to run profman: " + result.error().message()); + } + + LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); + + if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch) { + *_aidl_return = false; + return ScopedAStatus::ok(); + } + + if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) { + return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); + } + + OR_RETURN_NON_FATAL(dst->Keep()); + *_aidl_return = true; + in_dst->profilePath.id = dst->TempId(); + in_dst->profilePath.tmpPath = dst->TempPath(); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) { + std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile)); + std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile)); + + std::error_code ec; + std::filesystem::rename(tmp_profile_path, ref_profile_path, ec); + if (ec) { + return NonFatal(ART_FORMAT( + "Failed to move '{}' to '{}': {}", tmp_profile_path, ref_profile_path, ec.message())); + } + + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) { + std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); + + std::error_code ec; + std::filesystem::remove(profile_path, ec); + if (ec) { + LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", profile_path, ec.message()); + } + + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile, + FileVisibility* _aidl_return) { + std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); + *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path)); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath, + FileVisibility* _aidl_return) { + std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath)); + *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path)); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile, + FileVisibility* _aidl_return) { + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile)); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile, + FileVisibility* _aidl_return) { + std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile)); + *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path)); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles, + const std::optional<ProfilePath>& in_referenceProfile, + OutputProfile* in_outputProfile, + const std::vector<std::string>& in_dexFiles, + const MergeProfileOptions& in_options, + bool* _aidl_return) { + std::vector<std::string> profile_paths; + for (const ProfilePath& profile : in_profiles) { + std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(profile)); + if (profile.getTag() == ProfilePath::dexMetadataPath) { + return Fatal(ART_FORMAT("Does not support DM file, got '{}'", profile_path)); + } + profile_paths.push_back(std::move(profile_path)); + } + std::string output_profile_path = + OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath)); + for (const std::string& dex_file : in_dexFiles) { + OR_RETURN_FATAL(ValidateDexPath(dex_file)); + } + if (in_options.forceMerge + in_options.dumpOnly + in_options.dumpClassesAndMethods > 1) { + return Fatal("Only one of 'forceMerge', 'dumpOnly', and 'dumpClassesAndMethods' can be set"); + } + + FdLogger fd_logger; + + CmdlineBuilder art_exec_args; + art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities"); + + CmdlineBuilder args; + args.Add(OR_RETURN_FATAL(GetProfman())); + + std::vector<std::unique_ptr<File>> profile_files; + for (const std::string& profile_path : profile_paths) { + Result<std::unique_ptr<File>> profile_file = OpenFileForReading(profile_path); + if (!profile_file.ok()) { + if (profile_file.error().code() == ENOENT) { + // Skip non-existing file. + continue; + } + return NonFatal(ART_FORMAT( + "Failed to open profile '{}': {}", profile_path, profile_file.error().message())); + } + args.Add("--profile-file-fd=%d", profile_file.value()->Fd()); + fd_logger.Add(*profile_file.value()); + profile_files.push_back(std::move(profile_file.value())); + } + + if (profile_files.empty()) { + LOG(INFO) << "Merge skipped because there are no existing profiles"; + *_aidl_return = false; + return ScopedAStatus::ok(); + } + + std::unique_ptr<NewFile> output_profile_file = + OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission)); + + if (in_referenceProfile.has_value()) { + if (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) { + return Fatal( + "Reference profile must not be set when 'forceMerge', 'dumpOnly', or " + "'dumpClassesAndMethods' is set"); + } + std::string reference_profile_path = + OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile)); + if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) { + return Fatal(ART_FORMAT("Does not support DM file, got '{}'", reference_profile_path)); + } + OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file)); + } + + if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { + args.Add("--dump-output-to-fd=%d", output_profile_file->Fd()); + } else { + // profman is ok with this being an empty file when in_referenceProfile isn't set. + args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd()); + } + fd_logger.Add(*output_profile_file); + + std::vector<std::unique_ptr<File>> dex_files; + for (const std::string& dex_path : in_dexFiles) { + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path)); + args.Add("--apk-fd=%d", dex_file->Fd()); + fd_logger.Add(*dex_file); + dex_files.push_back(std::move(dex_file)); + } + + if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { + args.Add(in_options.dumpOnly ? "--dump-only" : "--dump-classes-and-methods"); + } else { + args.AddIfNonEmpty("--min-new-classes-percent-change=%s", + props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent")) + .AddIfNonEmpty("--min-new-methods-percent-change=%s", + props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent")) + .AddIf(in_options.forceMerge, "--force-merge") + .AddIf(in_options.forBootImage, "--boot-image-merge"); + } + + art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); + + LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") + << "\nOpened FDs: " << fd_logger; + + Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); + if (!result.ok()) { + return NonFatal("Failed to run profman: " + result.error().message()); + } + + LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); + + if (result.value() == ProfmanResult::kSkipCompilationSmallDelta || + result.value() == ProfmanResult::kSkipCompilationEmptyProfiles) { + *_aidl_return = false; + return ScopedAStatus::ok(); + } + + ProfmanResult::ProcessingResult expected_result = + (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) ? + ProfmanResult::kSuccess : + ProfmanResult::kCompile; + if (result.value() != expected_result) { + return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); + } + + OR_RETURN_NON_FATAL(output_profile_file->Keep()); + *_aidl_return = true; + in_outputProfile->profilePath.id = output_profile_file->TempId(); + in_outputProfile->profilePath.tmpPath = output_profile_file->TempPath(); + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + const std::string& in_compilerFilter, + int32_t in_dexoptTrigger, + GetDexoptNeededResult* _aidl_return) { + Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext(); + if (!ofa_context.ok()) { + return NonFatal("Failed to get runtime options: " + ofa_context.error().message()); + } + + std::unique_ptr<ClassLoaderContext> context; + std::string error_msg; + auto oat_file_assistant = OatFileAssistant::Create(in_dexFile, + in_instructionSet, + in_classLoaderContext, + /*load_executable=*/false, + /*only_load_trusted_executable=*/true, + ofa_context.value(), + &context, + &error_msg); + if (oat_file_assistant == nullptr) { + return NonFatal("Failed to create OatFileAssistant: " + error_msg); + } + + OatFileAssistant::DexOptStatus status; + _aidl_return->isDexoptNeeded = + oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)), + DexOptTriggerFromAidl(in_dexoptTrigger), + &status); + _aidl_return->isVdexUsable = status.IsVdexUsable(); + _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation()); + + return ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Artd::dexopt( + const OutputArtifacts& in_outputArtifacts, + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + const std::string& in_compilerFilter, + const std::optional<ProfilePath>& in_profile, + const std::optional<VdexPath>& in_inputVdex, + const std::optional<DexMetadataPath>& in_dmFile, + PriorityClass in_priorityClass, + const DexoptOptions& in_dexoptOptions, + const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal, + ArtdDexoptResult* _aidl_return) { + _aidl_return->cancelled = false; + + std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath)); + std::string vdex_path = OatPathToVdexPath(oat_path); + std::string art_path = OatPathToArtPath(oat_path); + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + std::optional<std::string> profile_path = + in_profile.has_value() ? + std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) : + std::nullopt; + ArtdCancellationSignal* cancellation_signal = + OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get())); + + std::unique_ptr<ClassLoaderContext> context = nullptr; + if (in_classLoaderContext.has_value()) { + context = ClassLoaderContext::Create(in_classLoaderContext.value()); + if (context == nullptr) { + return Fatal( + ART_FORMAT("Class loader context '{}' is invalid", in_classLoaderContext.value())); + } + } + + std::string oat_dir_path; // For restorecon, can be empty if the artifacts are in dalvik-cache. + OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts, &oat_dir_path)); + + // First-round restorecon. artd doesn't have the permission to create files with the + // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will + // inherit `dalvikcache_data_file` rather than `apk_data_file`. + if (!in_outputArtifacts.artifactsPath.isInDalvikCache) { + OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext)); + } + + FdLogger fd_logger; + + CmdlineBuilder art_exec_args; + art_exec_args.Add(OR_RETURN_FATAL(GetArtExec())).Add("--drop-capabilities"); + + CmdlineBuilder args; + args.Add(OR_RETURN_FATAL(GetDex2Oat())); + + const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission; + + std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); + args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile); + fd_logger.Add(*dex_file); + struct stat dex_st = OR_RETURN_NON_FATAL(Fstat(*dex_file)); + if ((dex_st.st_mode & S_IROTH) == 0) { + if (fs_permission.isOtherReadable) { + return NonFatal(ART_FORMAT( + "Outputs cannot be other-readable because the dex file '{}' is not other-readable", + dex_file->GetPath())); + } + // Negative numbers mean no `chown`. 0 means root. + // Note: this check is more strict than it needs to be. For example, it doesn't allow the + // outputs to belong to a group that is a subset of the dex file's group. This is for + // simplicity, and it's okay as we don't have to handle such complicated cases in practice. + if ((fs_permission.uid > 0 && static_cast<uid_t>(fs_permission.uid) != dex_st.st_uid) || + (fs_permission.gid > 0 && static_cast<gid_t>(fs_permission.gid) != dex_st.st_uid && + static_cast<gid_t>(fs_permission.gid) != dex_st.st_gid)) { + return NonFatal(ART_FORMAT( + "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})", + dex_file->GetPath(), + fs_permission.uid, + fs_permission.gid, + dex_st.st_uid, + dex_st.st_gid)); + } + } + + std::unique_ptr<NewFile> oat_file = OR_RETURN_NON_FATAL(NewFile::Create(oat_path, fs_permission)); + args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", oat_path); + fd_logger.Add(*oat_file); + + std::unique_ptr<NewFile> vdex_file = + OR_RETURN_NON_FATAL(NewFile::Create(vdex_path, fs_permission)); + args.Add("--output-vdex-fd=%d", vdex_file->Fd()); + fd_logger.Add(*vdex_file); + + std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()}; + std::vector<std::string_view> files_to_delete; + + std::unique_ptr<NewFile> art_file = nullptr; + if (in_dexoptOptions.generateAppImage) { + art_file = OR_RETURN_NON_FATAL(NewFile::Create(art_path, fs_permission)); + args.Add("--app-image-fd=%d", art_file->Fd()); + args.AddIfNonEmpty("--image-format=%s", props_->GetOrEmpty("dalvik.vm.appimageformat")); + fd_logger.Add(*art_file); + files_to_commit.push_back(art_file.get()); + } else { + files_to_delete.push_back(art_path); + } + + std::unique_ptr<NewFile> swap_file = nullptr; + if (ShouldCreateSwapFileForDexopt()) { + std::string swap_file_path = ART_FORMAT("{}.swap", oat_path); + swap_file = + OR_RETURN_NON_FATAL(NewFile::Create(swap_file_path, FsPermission{.uid = -1, .gid = -1})); + args.Add("--swap-fd=%d", swap_file->Fd()); + fd_logger.Add(*swap_file); + } + + std::vector<std::unique_ptr<File>> context_files; + if (context != nullptr) { + std::vector<std::string> flattened_context = context->FlattenDexPaths(); + std::string dex_dir = Dirname(in_dexFile); + std::vector<int> context_fds; + for (const std::string& context_element : flattened_context) { + std::string context_path = std::filesystem::path(dex_dir).append(context_element); + OR_RETURN_FATAL(ValidateDexPath(context_path)); + std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path)); + context_fds.push_back(context_file->Fd()); + fd_logger.Add(*context_file); + context_files.push_back(std::move(context_file)); + } + args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':')) + .Add("--class-loader-context=%s", in_classLoaderContext.value()) + .Add("--classpath-dir=%s", dex_dir); + } + + std::unique_ptr<File> input_vdex_file = nullptr; + if (in_inputVdex.has_value()) { + std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value())); + input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path)); + args.Add("--input-vdex-fd=%d", input_vdex_file->Fd()); + fd_logger.Add(*input_vdex_file); + } + + std::unique_ptr<File> dm_file = nullptr; + if (in_dmFile.has_value()) { + std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value())); + dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path)); + args.Add("--dm-fd=%d", dm_file->Fd()); + fd_logger.Add(*dm_file); + } + + std::unique_ptr<File> profile_file = nullptr; + if (profile_path.has_value()) { + profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value())); + args.Add("--profile-file-fd=%d", profile_file->Fd()); + fd_logger.Add(*profile_file); + struct stat profile_st = OR_RETURN_NON_FATAL(Fstat(*profile_file)); + if (fs_permission.isOtherReadable && (profile_st.st_mode & S_IROTH) == 0) { + return NonFatal(ART_FORMAT( + "Outputs cannot be other-readable because the profile '{}' is not other-readable", + profile_file->GetPath())); + } + // TODO(b/260228411): Check uid and gid. + } + + // Second-round restorecon. Restorecon recursively after the output files are created, so that the + // SELinux context is applied to all of them. The SELinux context of a file is mostly inherited + // from the parent directory upon creation, but the MLS label is not inherited, so we need to + // restorecon every file so that they have the right MLS label. If the files are in dalvik-cache, + // there's no need to restorecon because they inherits the SELinux context of the dalvik-cache + // directory and they don't need to have MLS labels. + if (!in_outputArtifacts.artifactsPath.isInDalvikCache) { + OR_RETURN_NON_FATAL(Restorecon(oat_dir_path, in_outputArtifacts.permissionSettings.seContext)); + } + + AddBootImageFlags(args); + AddCompilerConfigFlags( + in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args); + AddPerfConfigFlags(in_priorityClass, art_exec_args, args); + + // For being surfaced in crash reports on crashes. + args.Add("--comments=%s", in_dexoptOptions.comments); + + art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); + + LOG(INFO) << "Running dex2oat: " << Join(art_exec_args.Get(), /*separator=*/" ") + << "\nOpened FDs: " << fd_logger; + + ExecCallbacks callbacks{ + .on_start = + [&](pid_t pid) { + std::lock_guard<std::mutex> lock(cancellation_signal->mu_); + cancellation_signal->pids_.insert(pid); + // Handle cancellation signals sent before the process starts. + if (cancellation_signal->is_cancelled_) { + int res = kill_(pid, SIGKILL); + DCHECK_EQ(res, 0); + } + }, + .on_end = + [&](pid_t pid) { + std::lock_guard<std::mutex> lock(cancellation_signal->mu_); + // The pid should no longer receive kill signals sent by `cancellation_signal`. + cancellation_signal->pids_.erase(pid); + }, + }; + + ProcessStat stat; + Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kLongTimeoutSec, callbacks, &stat); + _aidl_return->wallTimeMs = stat.wall_time_ms; + _aidl_return->cpuTimeMs = stat.cpu_time_ms; + if (!result.ok()) { + { + std::lock_guard<std::mutex> lock(cancellation_signal->mu_); + if (cancellation_signal->is_cancelled_) { + _aidl_return->cancelled = true; + return ScopedAStatus::ok(); + } + } + return NonFatal("Failed to run dex2oat: " + result.error().message()); + } + + LOG(INFO) << ART_FORMAT("dex2oat returned code {}", result.value()); + + if (result.value() != 0) { + return NonFatal(ART_FORMAT("dex2oat returned an unexpected code: {}", result.value())); + } + + int64_t size_bytes = 0; + int64_t size_before_bytes = 0; + for (const NewFile* file : files_to_commit) { + size_bytes += GetSize(file->TempPath()).value_or(0); + size_before_bytes += GetSize(file->FinalPath()).value_or(0); + } + for (std::string_view path : files_to_delete) { + size_before_bytes += GetSize(path).value_or(0); + } + OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete)); + + _aidl_return->sizeBytes = size_bytes; + _aidl_return->sizeBeforeBytes = size_before_bytes; + return ScopedAStatus::ok(); +} + +ScopedAStatus ArtdCancellationSignal::cancel() { + std::lock_guard<std::mutex> lock(mu_); + is_cancelled_ = true; + for (pid_t pid : pids_) { + int res = kill_(pid, SIGKILL); + DCHECK_EQ(res, 0); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) { + *_aidl_return = reinterpret_cast<intptr_t>(kArtdCancellationSignalType); + return ScopedAStatus::ok(); +} + +ScopedAStatus Artd::createCancellationSignal( + std::shared_ptr<IArtdCancellationSignal>* _aidl_return) { + *_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_); + return ScopedAStatus::ok(); +} + +ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep, + const std::vector<ArtifactsPath>& in_artifactsToKeep, + const std::vector<VdexPath>& in_vdexFilesToKeep, + int64_t* _aidl_return) { + std::unordered_set<std::string> files_to_keep; + for (const ProfilePath& profile : in_profilesToKeep) { + files_to_keep.insert(OR_RETURN_FATAL(BuildProfileOrDmPath(profile))); + } + for (const ArtifactsPath& artifacts : in_artifactsToKeep) { + std::string oat_path = OR_RETURN_FATAL(BuildOatPath(artifacts)); + files_to_keep.insert(OatPathToVdexPath(oat_path)); + files_to_keep.insert(OatPathToArtPath(oat_path)); + files_to_keep.insert(std::move(oat_path)); + } + for (const VdexPath& vdex : in_vdexFilesToKeep) { + files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex))); + } + *_aidl_return = 0; + for (const std::string& file : OR_RETURN_NON_FATAL(ListManagedFiles())) { + if (files_to_keep.find(file) == files_to_keep.end()) { + LOG(INFO) << ART_FORMAT("Cleaning up obsolete file '{}'", file); + *_aidl_return += GetSizeAndDeleteFile(file); + } + } + return ScopedAStatus::ok(); +} + +ScopedAStatus Artd::isIncrementalFsPath(const std::string& in_dexFile [[maybe_unused]], + bool* _aidl_return) { +#ifdef __BIONIC__ + OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); + struct statfs st; + if (statfs(in_dexFile.c_str(), &st) != 0) { + PLOG(ERROR) << ART_FORMAT("Failed to statfs '{}'", in_dexFile); + *_aidl_return = false; + return ScopedAStatus::ok(); + } + *_aidl_return = st.f_type == INCFS_MAGIC_NUMBER; + return ScopedAStatus::ok(); +#else + *_aidl_return = false; + return ScopedAStatus::ok(); +#endif +} + Result<void> Artd::Start() { + OR_RETURN(SetLogVerbosity()); + ScopedAStatus status = ScopedAStatus::fromStatus( AServiceManager_registerLazyService(this->asBinder().get(), kServiceName)); if (!status.isOk()) { @@ -58,5 +1100,232 @@ Result<void> Artd::Start() { return {}; } +Result<OatFileAssistantContext*> Artd::GetOatFileAssistantContext() { + std::lock_guard<std::mutex> lock(ofa_context_mu_); + + if (ofa_context_ == nullptr) { + ofa_context_ = std::make_unique<OatFileAssistantContext>( + std::make_unique<OatFileAssistantContext::RuntimeOptions>( + OatFileAssistantContext::RuntimeOptions{ + .image_locations = *OR_RETURN(GetBootImageLocations()), + .boot_class_path = *OR_RETURN(GetBootClassPath()), + .boot_class_path_locations = *OR_RETURN(GetBootClassPath()), + .deny_art_apex_data_files = DenyArtApexDataFiles(), + })); + std::string error_msg; + if (!ofa_context_->FetchAll(&error_msg)) { + return Error() << error_msg; + } + } + + return ofa_context_.get(); +} + +Result<const std::vector<std::string>*> Artd::GetBootImageLocations() { + std::lock_guard<std::mutex> lock(cache_mu_); + + if (!cached_boot_image_locations_.has_value()) { + std::string location_str; + + if (UseJitZygoteLocked()) { + location_str = GetJitZygoteBootImageLocation(); + } else if (std::string value = GetUserDefinedBootImageLocationsLocked(); !value.empty()) { + location_str = std::move(value); + } else { + std::string error_msg; + std::string android_root = GetAndroidRootSafe(&error_msg); + if (!error_msg.empty()) { + return Errorf("Failed to get ANDROID_ROOT: {}", error_msg); + } + location_str = GetDefaultBootImageLocation(android_root, DenyArtApexDataFilesLocked()); + } + + cached_boot_image_locations_ = Split(location_str, ":"); + } + + return &cached_boot_image_locations_.value(); +} + +Result<const std::vector<std::string>*> Artd::GetBootClassPath() { + std::lock_guard<std::mutex> lock(cache_mu_); + + if (!cached_boot_class_path_.has_value()) { + const char* env_value = getenv("BOOTCLASSPATH"); + if (env_value == nullptr || strlen(env_value) == 0) { + return Errorf("Failed to get environment variable 'BOOTCLASSPATH'"); + } + cached_boot_class_path_ = Split(env_value, ":"); + } + + return &cached_boot_class_path_.value(); +} + +bool Artd::UseJitZygote() { + std::lock_guard<std::mutex> lock(cache_mu_); + return UseJitZygoteLocked(); +} + +bool Artd::UseJitZygoteLocked() { + if (!cached_use_jit_zygote_.has_value()) { + cached_use_jit_zygote_ = + props_->GetBool("persist.device_config.runtime_native_boot.profilebootclasspath", + "dalvik.vm.profilebootclasspath", + /*default_value=*/false); + } + + return cached_use_jit_zygote_.value(); +} + +const std::string& Artd::GetUserDefinedBootImageLocations() { + std::lock_guard<std::mutex> lock(cache_mu_); + return GetUserDefinedBootImageLocationsLocked(); +} + +const std::string& Artd::GetUserDefinedBootImageLocationsLocked() { + if (!cached_user_defined_boot_image_locations_.has_value()) { + cached_user_defined_boot_image_locations_ = props_->GetOrEmpty("dalvik.vm.boot-image"); + } + + return cached_user_defined_boot_image_locations_.value(); +} + +bool Artd::DenyArtApexDataFiles() { + std::lock_guard<std::mutex> lock(cache_mu_); + return DenyArtApexDataFilesLocked(); +} + +bool Artd::DenyArtApexDataFilesLocked() { + if (!cached_deny_art_apex_data_files_.has_value()) { + cached_deny_art_apex_data_files_ = + !props_->GetBool("odsign.verification.success", /*default_value=*/false); + } + + return cached_deny_art_apex_data_files_.value(); +} + +Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); } + +Result<std::string> Artd::GetArtExec() { return BuildArtBinPath("art_exec"); } + +bool Artd::ShouldUseDex2Oat64() { + return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() && + props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false); +} + +Result<std::string> Artd::GetDex2Oat() { + std::string binary_name = ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32"; + // TODO(b/234351700): Should we use the "d" variant? + return BuildArtBinPath(binary_name); +} + +bool Artd::ShouldCreateSwapFileForDexopt() { + // Create a swap file by default. Dex2oat will decide whether to use it or not. + return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true); +} + +void Artd::AddBootImageFlags(/*out*/ CmdlineBuilder& args) { + if (UseJitZygote()) { + args.Add("--force-jit-zygote"); + } else { + args.AddIfNonEmpty("--boot-image=%s", GetUserDefinedBootImageLocations()); + } +} + +void Artd::AddCompilerConfigFlags(const std::string& instruction_set, + const std::string& compiler_filter, + PriorityClass priority_class, + const DexoptOptions& dexopt_options, + /*out*/ CmdlineBuilder& args) { + args.Add("--instruction-set=%s", instruction_set); + std::string features_prop = ART_FORMAT("dalvik.vm.isa.{}.features", instruction_set); + args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop)); + std::string variant_prop = ART_FORMAT("dalvik.vm.isa.{}.variant", instruction_set); + args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop)); + + args.Add("--compiler-filter=%s", compiler_filter) + .Add("--compilation-reason=%s", dexopt_options.compilationReason); + + args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none"); + + args.AddIfNonEmpty("--max-image-block-size=%s", + props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size")) + .AddIfNonEmpty("--very-large-app-threshold=%s", + props_->GetOrEmpty("dalvik.vm.dex2oat-very-large")) + .AddIfNonEmpty( + "--resolve-startup-const-strings=%s", + props_->GetOrEmpty("persist.device_config.runtime.dex2oat_resolve_startup_strings", + "dalvik.vm.dex2oat-resolve-startup-strings")); + + args.AddIf(dexopt_options.debuggable, "--debuggable") + .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false), + "--generate-debug-info") + .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false), + "--generate-mini-debug-info"); + + args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files") + .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion) + .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled"); +} + +void Artd::AddPerfConfigFlags(PriorityClass priority_class, + /*out*/ CmdlineBuilder& art_exec_args, + /*out*/ CmdlineBuilder& dex2oat_args) { + // CPU set and number of threads. + std::string default_cpu_set_prop = "dalvik.vm.dex2oat-cpu-set"; + std::string default_threads_prop = "dalvik.vm.dex2oat-threads"; + std::string cpu_set; + std::string threads; + if (priority_class >= PriorityClass::BOOT) { + cpu_set = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set"); + threads = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-threads"); + } else if (priority_class >= PriorityClass::INTERACTIVE_FAST) { + cpu_set = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-cpu-set", default_cpu_set_prop); + threads = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-threads", default_threads_prop); + } else if (priority_class <= PriorityClass::BACKGROUND) { + cpu_set = props_->GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", default_cpu_set_prop); + threads = props_->GetOrEmpty("dalvik.vm.background-dex2oat-threads", default_threads_prop); + } else { + cpu_set = props_->GetOrEmpty(default_cpu_set_prop); + threads = props_->GetOrEmpty(default_threads_prop); + } + dex2oat_args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads); + + if (priority_class < PriorityClass::BOOT) { + art_exec_args + .Add(priority_class <= PriorityClass::BACKGROUND ? "--set-task-profile=Dex2OatBackground" : + "--set-task-profile=Dex2OatBootComplete") + .Add("--set-priority=background"); + } + + dex2oat_args.AddRuntimeIfNonEmpty("-Xms%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xms")) + .AddRuntimeIfNonEmpty("-Xmx%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xmx")); + + // Enable compiling dex files in isolation on low ram devices. + // It takes longer but reduces the memory footprint. + dex2oat_args.AddIf(props_->GetBool("ro.config.low_ram", /*default_value=*/false), + "--compile-individually"); +} + +Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args, + int timeout_sec, + const ExecCallbacks& callbacks, + ProcessStat* stat) const { + std::string error_msg; + ExecResult result = + exec_utils_->ExecAndReturnResult(args, timeout_sec, callbacks, stat, &error_msg); + if (result.status != ExecResult::kExited) { + return Error() << error_msg; + } + return result.exit_code; +} + +Result<struct stat> Artd::Fstat(const File& file) const { + struct stat st; + if (fstat_(file.Fd(), &st) != 0) { + return Errorf("Unable to fstat file '{}'", file.GetPath()); + } + return st; +} + } // namespace artd } // namespace art diff --git a/artd/artd.h b/artd/artd.h index f01d9a8a23..f90110d13f 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -17,18 +17,219 @@ #ifndef ART_ARTD_ARTD_H_ #define ART_ARTD_ARTD_H_ +#include <sys/stat.h> +#include <sys/types.h> + +#include <csignal> +#include <cstdint> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + #include "aidl/com/android/server/art/BnArtd.h" +#include "aidl/com/android/server/art/BnArtdCancellationSignal.h" #include "android-base/result.h" +#include "android-base/thread_annotations.h" #include "android/binder_auto_utils.h" +#include "base/os.h" +#include "exec_utils.h" +#include "oat_file_assistant_context.h" +#include "tools/cmdline_builder.h" +#include "tools/system_properties.h" namespace art { namespace artd { +class ArtdCancellationSignal : public aidl::com::android::server::art::BnArtdCancellationSignal { + public: + explicit ArtdCancellationSignal(std::function<int(pid_t, int)> kill_func) + : kill_(std::move(kill_func)) {} + + ndk::ScopedAStatus cancel() override; + + ndk::ScopedAStatus getType(int64_t* _aidl_return) override; + + private: + std::mutex mu_; + // True if cancellation has been signaled. + bool is_cancelled_ GUARDED_BY(mu_) = false; + // The pids of currently running child processes that are bound to this signal. + std::unordered_set<pid_t> pids_ GUARDED_BY(mu_); + + std::function<int(pid_t, int)> kill_; + + friend class Artd; +}; + class Artd : public aidl::com::android::server::art::BnArtd { public: + explicit Artd(std::unique_ptr<art::tools::SystemProperties> props = + std::make_unique<art::tools::SystemProperties>(), + std::unique_ptr<ExecUtils> exec_utils = std::make_unique<ExecUtils>(), + std::function<int(pid_t, int)> kill_func = kill, + std::function<int(int, struct stat*)> fstat_func = fstat) + : props_(std::move(props)), + exec_utils_(std::move(exec_utils)), + kill_(std::move(kill_func)), + fstat_(std::move(fstat_func)) {} + ndk::ScopedAStatus isAlive(bool* _aidl_return) override; + ndk::ScopedAStatus deleteArtifacts( + const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath, + int64_t* _aidl_return) override; + + ndk::ScopedAStatus getDexoptStatus( + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + aidl::com::android::server::art::GetDexoptStatusResult* _aidl_return) override; + + ndk::ScopedAStatus isProfileUsable(const aidl::com::android::server::art::ProfilePath& in_profile, + const std::string& in_dexFile, + bool* _aidl_return) override; + + ndk::ScopedAStatus copyAndRewriteProfile( + const aidl::com::android::server::art::ProfilePath& in_src, + aidl::com::android::server::art::OutputProfile* in_dst, + const std::string& in_dexFile, + bool* _aidl_return) override; + + ndk::ScopedAStatus commitTmpProfile( + const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override; + + ndk::ScopedAStatus deleteProfile( + const aidl::com::android::server::art::ProfilePath& in_profile) override; + + ndk::ScopedAStatus getProfileVisibility( + const aidl::com::android::server::art::ProfilePath& in_profile, + aidl::com::android::server::art::FileVisibility* _aidl_return) override; + + ndk::ScopedAStatus mergeProfiles( + const std::vector<aidl::com::android::server::art::ProfilePath>& in_profiles, + const std::optional<aidl::com::android::server::art::ProfilePath>& in_referenceProfile, + aidl::com::android::server::art::OutputProfile* in_outputProfile, + const std::vector<std::string>& in_dexFiles, + const aidl::com::android::server::art::MergeProfileOptions& in_options, + bool* _aidl_return) override; + + ndk::ScopedAStatus getArtifactsVisibility( + const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath, + aidl::com::android::server::art::FileVisibility* _aidl_return) override; + + ndk::ScopedAStatus getDexFileVisibility( + const std::string& in_dexFile, + aidl::com::android::server::art::FileVisibility* _aidl_return) override; + + ndk::ScopedAStatus getDmFileVisibility( + const aidl::com::android::server::art::DexMetadataPath& in_dmFile, + aidl::com::android::server::art::FileVisibility* _aidl_return) override; + + ndk::ScopedAStatus getDexoptNeeded( + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + const std::string& in_compilerFilter, + int32_t in_dexoptTrigger, + aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override; + + ndk::ScopedAStatus dexopt( + const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts, + const std::string& in_dexFile, + const std::string& in_instructionSet, + const std::optional<std::string>& in_classLoaderContext, + const std::string& in_compilerFilter, + const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile, + const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex, + const std::optional<aidl::com::android::server::art::DexMetadataPath>& in_dmFile, + aidl::com::android::server::art::PriorityClass in_priorityClass, + const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions, + const std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>& + in_cancellationSignal, + aidl::com::android::server::art::ArtdDexoptResult* _aidl_return) override; + + ndk::ScopedAStatus createCancellationSignal( + std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>* _aidl_return) + override; + + ndk::ScopedAStatus cleanup( + const std::vector<aidl::com::android::server::art::ProfilePath>& in_profilesToKeep, + const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifactsToKeep, + const std::vector<aidl::com::android::server::art::VdexPath>& in_vdexFilesToKeep, + int64_t* _aidl_return) override; + + ndk::ScopedAStatus isIncrementalFsPath(const std::string& in_dexFile, + bool* _aidl_return) override; + android::base::Result<void> Start(); + + private: + android::base::Result<OatFileAssistantContext*> GetOatFileAssistantContext() + EXCLUDES(ofa_context_mu_); + + android::base::Result<const std::vector<std::string>*> GetBootImageLocations() + EXCLUDES(cache_mu_); + + android::base::Result<const std::vector<std::string>*> GetBootClassPath() EXCLUDES(cache_mu_); + + bool UseJitZygote() EXCLUDES(cache_mu_); + bool UseJitZygoteLocked() REQUIRES(cache_mu_); + + const std::string& GetUserDefinedBootImageLocations() EXCLUDES(cache_mu_); + const std::string& GetUserDefinedBootImageLocationsLocked() REQUIRES(cache_mu_); + + bool DenyArtApexDataFiles() EXCLUDES(cache_mu_); + bool DenyArtApexDataFilesLocked() REQUIRES(cache_mu_); + + android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector, + int timeout_sec, + const ExecCallbacks& callbacks = ExecCallbacks(), + ProcessStat* stat = nullptr) const; + + android::base::Result<std::string> GetProfman(); + + android::base::Result<std::string> GetArtExec(); + + bool ShouldUseDex2Oat64(); + + android::base::Result<std::string> GetDex2Oat(); + + bool ShouldCreateSwapFileForDexopt(); + + void AddBootImageFlags(/*out*/ art::tools::CmdlineBuilder& args); + + void AddCompilerConfigFlags(const std::string& instruction_set, + const std::string& compiler_filter, + aidl::com::android::server::art::PriorityClass priority_class, + const aidl::com::android::server::art::DexoptOptions& dexopt_options, + /*out*/ art::tools::CmdlineBuilder& args); + + void AddPerfConfigFlags(aidl::com::android::server::art::PriorityClass priority_class, + /*out*/ art::tools::CmdlineBuilder& art_exec_args, + /*out*/ art::tools::CmdlineBuilder& args); + + android::base::Result<struct stat> Fstat(const art::File& file) const; + + std::mutex cache_mu_; + std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_); + std::optional<std::vector<std::string>> cached_boot_class_path_ GUARDED_BY(cache_mu_); + std::optional<bool> cached_use_jit_zygote_ GUARDED_BY(cache_mu_); + std::optional<std::string> cached_user_defined_boot_image_locations_ GUARDED_BY(cache_mu_); + std::optional<bool> cached_deny_art_apex_data_files_ GUARDED_BY(cache_mu_); + + std::mutex ofa_context_mu_; + std::unique_ptr<OatFileAssistantContext> ofa_context_ GUARDED_BY(ofa_context_mu_); + + const std::unique_ptr<art::tools::SystemProperties> props_; + const std::unique_ptr<ExecUtils> exec_utils_; + const std::function<int(pid_t, int)> kill_; + const std::function<int(int, struct stat*)> fstat_; }; } // namespace artd diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 14bccc2999..44ddae967b 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -16,34 +16,1943 @@ #include "artd.h" +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <chrono> +#include <condition_variable> +#include <csignal> +#include <filesystem> +#include <functional> #include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <thread> +#include <type_traits> +#include <unordered_set> +#include <utility> +#include <vector> -#include "android/binder_interface_utils.h" +#include "aidl/com/android/server/art/ArtConstants.h" +#include "aidl/com/android/server/art/BnArtd.h" +#include "android-base/collections.h" +#include "android-base/errors.h" +#include "android-base/file.h" +#include "android-base/logging.h" +#include "android-base/parseint.h" +#include "android-base/result.h" +#include "android-base/scopeguard.h" +#include "android-base/strings.h" +#include "android/binder_auto_utils.h" +#include "android/binder_status.h" +#include "base/array_ref.h" #include "base/common_art_test.h" +#include "base/macros.h" +#include "exec_utils.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "oat_file.h" +#include "path_utils.h" +#include "profman/profman_result.h" +#include "testing.h" +#include "tools/system_properties.h" namespace art { namespace artd { namespace { +using ::aidl::com::android::server::art::ArtConstants; +using ::aidl::com::android::server::art::ArtdDexoptResult; +using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::DexoptOptions; +using ::aidl::com::android::server::art::FileVisibility; +using ::aidl::com::android::server::art::FsPermission; +using ::aidl::com::android::server::art::IArtdCancellationSignal; +using ::aidl::com::android::server::art::OutputArtifacts; +using ::aidl::com::android::server::art::OutputProfile; +using ::aidl::com::android::server::art::PriorityClass; +using ::aidl::com::android::server::art::ProfilePath; +using ::aidl::com::android::server::art::VdexPath; +using ::android::base::Append; +using ::android::base::Error; +using ::android::base::make_scope_guard; +using ::android::base::ParseInt; +using ::android::base::ReadFdToString; +using ::android::base::ReadFileToString; +using ::android::base::Result; +using ::android::base::ScopeGuard; +using ::android::base::Split; +using ::android::base::WriteStringToFd; +using ::android::base::WriteStringToFile; +using ::testing::_; +using ::testing::AllOf; +using ::testing::AnyNumber; +using ::testing::AnyOf; +using ::testing::Contains; +using ::testing::ContainsRegex; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::Field; +using ::testing::HasSubstr; +using ::testing::IsEmpty; +using ::testing::Matcher; +using ::testing::MockFunction; +using ::testing::Not; +using ::testing::Property; +using ::testing::ResultOf; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::UnorderedElementsAreArray; +using ::testing::WithArg; + +using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; +using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath; +using TmpProfilePath = ProfilePath::TmpProfilePath; + +ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) { + android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger)); + return make_scope_guard([old_logger = std::move(old_logger)]() mutable { + android::base::SetLogger(std::move(old_logger)); + }); +} + +void CheckContent(const std::string& path, const std::string& expected_content) { + std::string actual_content; + ASSERT_TRUE(ReadFileToString(path, &actual_content)); + EXPECT_EQ(actual_content, expected_content); +} + +void CheckOtherReadable(const std::string& path, bool expected_value) { + EXPECT_EQ((std::filesystem::status(path).permissions() & std::filesystem::perms::others_read) != + std::filesystem::perms::none, + expected_value); +} + +Result<std::vector<std::string>> GetFlagValues(ArrayRef<const std::string> args, + std::string_view flag) { + std::vector<std::string> values; + for (const std::string& arg : args) { + std::string_view value(arg); + if (android::base::ConsumePrefix(&value, flag)) { + values.emplace_back(value); + } + } + if (values.empty()) { + return Errorf("Flag '{}' not found", flag); + } + return values; +} + +Result<std::string> GetFlagValue(ArrayRef<const std::string> args, std::string_view flag) { + std::vector<std::string> flag_values = OR_RETURN(GetFlagValues(args, flag)); + if (flag_values.size() > 1) { + return Errorf("Duplicate flag '{}'", flag); + } + return flag_values[0]; +} + +void WriteToFdFlagImpl(const std::vector<std::string>& args, + std::string_view flag, + std::string_view content, + bool assume_empty) { + std::string value = OR_FAIL(GetFlagValue(ArrayRef<const std::string>(args), flag)); + ASSERT_NE(value, ""); + int fd; + ASSERT_TRUE(ParseInt(value, &fd)); + if (assume_empty) { + ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_CUR), 0); + } else { + ASSERT_EQ(ftruncate(fd, /*length=*/0), 0); + ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_SET), 0); + } + ASSERT_TRUE(WriteStringToFd(content, fd)); +} + +// Writes `content` to the FD specified by the `flag`. +ACTION_P(WriteToFdFlag, flag, content) { + WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/true); +} + +// Clears any existing content and writes `content` to the FD specified by the `flag`. +ACTION_P(ClearAndWriteToFdFlag, flag, content) { + WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/false); +} + +// Matches a flag that starts with `flag` and whose value matches `matcher`. +MATCHER_P2(Flag, flag, matcher, "") { + std::string_view value(arg); + if (!android::base::ConsumePrefix(&value, flag)) { + return false; + } + return ExplainMatchResult(matcher, std::string(value), result_listener); +} + +// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches +// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument. +MATCHER_P2(ListFlag, flag, matcher, "") { + return ExplainMatchResult( + Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)), + arg, + result_listener); +} + +// Matches an FD of a file whose path matches `matcher`. +MATCHER_P(FdOf, matcher, "") { + std::string proc_path = ART_FORMAT("/proc/self/fd/{}", arg); + char path[PATH_MAX]; + ssize_t len = readlink(proc_path.c_str(), path, sizeof(path)); + if (len < 0) { + return false; + } + return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener); +} + +// Matches an FD of a file whose content matches `matcher`. +MATCHER_P(FdHasContent, matcher, "") { + int fd; + if (!ParseInt(arg, &fd)) { + return false; + } + std::string actual_content; + if (!ReadFdToString(fd, &actual_content)) { + return false; + } + return ExplainMatchResult(matcher, actual_content, result_listener); +} + +template <typename T, typename U> +Result<std::pair<ArrayRef<const T>, ArrayRef<const T>>> SplitBy(const std::vector<T>& list, + const U& separator) { + auto it = std::find(list.begin(), list.end(), separator); + if (it == list.end()) { + return Errorf("'{}' not found", separator); + } + size_t pos = it - list.begin(); + return std::make_pair(ArrayRef<const T>(list).SubArray(0, pos), + ArrayRef<const T>(list).SubArray(pos + 1)); +} + +// Matches a container that, when split by `separator`, the first part matches `head_matcher`, and +// the second part matches `tail_matcher`. +MATCHER_P3(WhenSplitBy, separator, head_matcher, tail_matcher, "") { + auto [head, tail] = OR_MISMATCH(SplitBy(arg, separator)); + return ExplainMatchResult(head_matcher, head, result_listener) && + ExplainMatchResult(tail_matcher, tail, result_listener); +} + +MATCHER_P(HasKeepFdsForImpl, fd_flags, "") { + auto [head, tail] = OR_MISMATCH(SplitBy(arg, "--")); + std::string keep_fds_value = OR_MISMATCH(GetFlagValue(head, "--keep-fds=")); + std::vector<std::string> keep_fds = Split(keep_fds_value, ":"); + std::vector<std::string> fd_flag_values; + for (std::string_view fd_flag : fd_flags) { + for (const std::string& fd_flag_value : OR_MISMATCH(GetFlagValues(tail, fd_flag))) { + for (std::string& fd : Split(fd_flag_value, ":")) { + fd_flag_values.push_back(std::move(fd)); + } + } + } + return ExplainMatchResult(UnorderedElementsAreArray(fd_flag_values), keep_fds, result_listener); +} + +// Matches an argument list that has the "--keep-fds=" flag before "--", whose value is a +// semicolon-separated list that contains exactly the values of the given flags after "--". +// +// E.g., if the flags after "--" are "--foo=1", "--bar=2:3", "--baz=4", "--baz=5", and the matcher +// is `HasKeepFdsFor("--foo=", "--bar=", "--baz=")`, then it requires the "--keep-fds=" flag before +// "--" to contain exactly 1, 2, 3, 4, and 5. +template <typename... Args> +auto HasKeepFdsFor(Args&&... args) { + std::vector<std::string_view> fd_flags; + Append(fd_flags, std::forward<Args>(args)...); + return HasKeepFdsForImpl(fd_flags); +} + +class MockSystemProperties : public tools::SystemProperties { + public: + MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override)); +}; + +class MockExecUtils : public ExecUtils { + public: + // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead + // to a conflict between gmock and android-base/logging.h (b/132668253). + ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector, + int, + const ExecCallbacks& callbacks, + ProcessStat* stat, + std::string*) const override { + Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat); + if (code.ok()) { + return {.status = ExecResult::kExited, .exit_code = code.value()}; + } + return {.status = ExecResult::kUnknown}; + } + + MOCK_METHOD(Result<int>, + DoExecAndReturnCode, + (const std::vector<std::string>& arg_vector, + const ExecCallbacks& callbacks, + ProcessStat* stat), + (const)); +}; + class ArtdTest : public CommonArtTest { protected: void SetUp() override { CommonArtTest::SetUp(); - artd_ = ndk::SharedRefBase::make<Artd>(); + auto mock_props = std::make_unique<MockSystemProperties>(); + mock_props_ = mock_props.get(); + EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return("")); + auto mock_exec_utils = std::make_unique<MockExecUtils>(); + mock_exec_utils_ = mock_exec_utils.get(); + artd_ = ndk::SharedRefBase::make<Artd>(std::move(mock_props), + std::move(mock_exec_utils), + mock_kill_.AsStdFunction(), + mock_fstat_.AsStdFunction()); + scratch_dir_ = std::make_unique<ScratchDir>(); + scratch_path_ = scratch_dir_->GetPath(); + // Remove the trailing '/'; + scratch_path_.resize(scratch_path_.length() - 1); + + ON_CALL(mock_fstat_, Call).WillByDefault(fstat); + + // Use an arbitrary existing directory as ART root. + art_root_ = scratch_path_ + "/com.android.art"; + std::filesystem::create_directories(art_root_); + setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1); + + // Use an arbitrary existing directory as Android data. + android_data_ = scratch_path_ + "/data"; + std::filesystem::create_directories(android_data_); + setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1); + + // Use an arbitrary existing directory as Android expand. + android_expand_ = scratch_path_ + "/mnt/expand"; + std::filesystem::create_directories(android_expand_); + setenv("ANDROID_EXPAND", android_expand_.c_str(), /*overwrite=*/1); + + dex_file_ = scratch_path_ + "/a/b.apk"; + isa_ = "arm64"; + artifacts_path_ = ArtifactsPath{ + .dexPath = dex_file_, + .isa = isa_, + .isInDalvikCache = false, + }; + struct stat st; + ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0); + output_artifacts_ = OutputArtifacts{ + .artifactsPath = artifacts_path_, + .permissionSettings = + OutputArtifacts::PermissionSettings{ + .dirFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + .isOtherExecutable = true, + }, + .fileFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + }, + }, + }; + clc_1_ = GetTestDexFileName("Main"); + clc_2_ = GetTestDexFileName("Nested"); + class_loader_context_ = ART_FORMAT("PCL[{}:{}]", clc_1_, clc_2_); + compiler_filter_ = "speed"; + TmpProfilePath tmp_profile_path{ + .finalPath = + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}, + .id = "12345"}; + profile_path_ = tmp_profile_path; + vdex_path_ = artifacts_path_; + dm_path_ = DexMetadataPath{.dexPath = dex_file_}; + std::filesystem::create_directories( + std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path()); } - void TearDown() override { CommonArtTest::TearDown(); } + void TearDown() override { + scratch_dir_.reset(); + CommonArtTest::TearDown(); + } + + void RunDexopt(binder_exception_t expected_status = EX_NONE, + Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled, + false), + std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) { + RunDexopt(Property(&ndk::ScopedAStatus::getExceptionCode, expected_status), + std::move(aidl_return_matcher), + std::move(cancellation_signal)); + } + + void RunDexopt(Matcher<ndk::ScopedAStatus> status_matcher, + Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled, + false), + std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) { + InitFilesBeforeDexopt(); + if (cancellation_signal == nullptr) { + ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk()); + } + ArtdDexoptResult aidl_return; + ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_, + dex_file_, + isa_, + class_loader_context_, + compiler_filter_, + profile_path_, + vdex_path_, + dm_path_, + priority_class_, + dexopt_options_, + cancellation_signal, + &aidl_return); + ASSERT_THAT(status, std::move(status_matcher)) << status.getMessage(); + if (status.isOk()) { + ASSERT_THAT(aidl_return, std::move(aidl_return_matcher)); + } + } + + void CreateFile(const std::string& filename, const std::string& content = "") { + std::filesystem::path path(filename); + std::filesystem::create_directories(path.parent_path()); + ASSERT_TRUE(WriteStringToFile(content, filename)); + } std::shared_ptr<Artd> artd_; + std::unique_ptr<ScratchDir> scratch_dir_; + std::string scratch_path_; + std::string art_root_; + std::string android_data_; + std::string android_expand_; + MockFunction<android::base::LogFunction> mock_logger_; + ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT"); + ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA"); + ScopedUnsetEnvironmentVariable android_expand_env_ = + ScopedUnsetEnvironmentVariable("ANDROID_EXPAND"); + MockSystemProperties* mock_props_; + MockExecUtils* mock_exec_utils_; + MockFunction<int(pid_t, int)> mock_kill_; + MockFunction<int(int, struct stat*)> mock_fstat_; + + std::string dex_file_; + std::string isa_; + ArtifactsPath artifacts_path_; + OutputArtifacts output_artifacts_; + std::string clc_1_; + std::string clc_2_; + std::optional<std::string> class_loader_context_; + std::string compiler_filter_; + std::optional<VdexPath> vdex_path_; + std::optional<DexMetadataPath> dm_path_; + PriorityClass priority_class_ = PriorityClass::BACKGROUND; + DexoptOptions dexopt_options_; + std::optional<ProfilePath> profile_path_; + bool dex_file_other_readable_ = true; + bool profile_other_readable_ = true; + + private: + void InitFilesBeforeDexopt() { + // Required files. + CreateFile(dex_file_); + std::filesystem::permissions(dex_file_, + std::filesystem::perms::others_read, + dex_file_other_readable_ ? std::filesystem::perm_options::add : + std::filesystem::perm_options::remove); + + // Optional files. + if (vdex_path_.has_value()) { + CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex"); + } + if (dm_path_.has_value()) { + CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value()))); + } + if (profile_path_.has_value()) { + std::string path = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + CreateFile(path); + std::filesystem::permissions(path, + std::filesystem::perms::others_read, + profile_other_readable_ ? std::filesystem::perm_options::add : + std::filesystem::perm_options::remove); + } + + // Files to be replaced. + std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_)); + CreateFile(oat_path, "old_oat"); + CreateFile(OatPathToVdexPath(oat_path), "old_vdex"); + CreateFile(OatPathToArtPath(oat_path), "old_art"); + } }; +TEST_F(ArtdTest, ConstantsAreInSync) { EXPECT_EQ(ArtConstants::REASON_VDEX, kReasonVdex); } + TEST_F(ArtdTest, isAlive) { bool result = false; artd_->isAlive(&result); EXPECT_TRUE(result); } +TEST_F(ArtdTest, deleteArtifacts) { + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; + std::filesystem::create_directories(oat_dir); + ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes. + ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex")); // 2 bytes. + ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte. + + int64_t result = -1; + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); + EXPECT_EQ(result, 4 + 2 + 1); + + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex")); + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.vdex")); + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art")); +} + +TEST_F(ArtdTest, deleteArtifactsMissingFile) { + // Missing VDEX file. + std::string oat_dir = android_data_ + "/dalvik-cache/arm64"; + std::filesystem::create_directories(oat_dir); + ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex")); // 4 bytes. + ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art")); // 1 byte. + + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0); + + int64_t result = -1; + EXPECT_TRUE(artd_ + ->deleteArtifacts( + ArtifactsPath{ + .dexPath = "/a/b.apk", + .isa = "arm64", + .isInDalvikCache = true, + }, + &result) + .isOk()); + EXPECT_EQ(result, 4 + 1); + + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.dex")); + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.art")); +} + +TEST_F(ArtdTest, deleteArtifactsNoFile) { + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0); + + int64_t result = -1; + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); + EXPECT_EQ(result, 0); +} + +TEST_F(ArtdTest, deleteArtifactsPermissionDenied) { + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; + std::filesystem::create_directories(oat_dir); + ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes. + ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex")); // 2 bytes. + ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte. + + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(3); + + auto scoped_inaccessible = ScopedInaccessible(oat_dir); + auto scoped_unroot = ScopedUnroot(); + + int64_t result = -1; + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); + EXPECT_EQ(result, 0); +} + +TEST_F(ArtdTest, deleteArtifactsFileIsDir) { + // VDEX file is a directory. + std::string oat_dir = scratch_path_ + "/a/oat/arm64"; + std::filesystem::create_directories(oat_dir); + std::filesystem::create_directories(oat_dir + "/b.vdex"); + ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes. + ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte. + + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL(mock_logger_, + Call(_, _, _, _, _, ContainsRegex(R"re(Failed to get the file size.*b\.vdex)re"))) + .Times(1); + + int64_t result = -1; + EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk()); + EXPECT_EQ(result, 4 + 1); + + // The directory is kept because getting the file size failed. + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex")); + EXPECT_TRUE(std::filesystem::exists(oat_dir + "/b.vdex")); + EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art")); +} + +TEST_F(ArtdTest, dexopt) { + dexopt_options_.generateAppImage = true; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/dex2oat32"), + Contains(Flag("--zip-fd=", FdOf(dex_file_))), + Contains(Flag("--zip-location=", dex_file_)), + Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")), + Contains(Flag("--instruction-set=", "arm64")), + Contains(Flag("--compiler-filter=", "speed")), + Contains(Flag( + "--profile-file-fd=", + FdOf(android_data_ + + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))), + Contains(Flag("--input-vdex-fd=", + FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))), + Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))))), + HasKeepFdsFor("--zip-fd=", + "--profile-file-fd=", + "--input-vdex-fd=", + "--dm-fd=", + "--oat-fd=", + "--output-vdex-fd=", + "--app-image-fd=", + "--class-loader-context-fds=", + "--swap-fd=")), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")), + WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), + SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}), + Return(0))); + RunDexopt( + EX_NONE, + AllOf(Field(&ArtdDexoptResult::cancelled, false), + Field(&ArtdDexoptResult::wallTimeMs, 100), + Field(&ArtdDexoptResult::cpuTimeMs, 400), + Field(&ArtdDexoptResult::sizeBytes, strlen("art") + strlen("oat") + strlen("vdex")), + Field(&ArtdDexoptResult::sizeBeforeBytes, + strlen("old_art") + strlen("old_oat") + strlen("old_vdex")))); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art"); + CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", true); + CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", true); + CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.art", true); +} + +TEST_F(ArtdTest, dexoptClassLoaderContext) { + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains(ListFlag("--class-loader-context-fds=", + ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))), + Contains(Flag("--class-loader-context=", class_loader_context_)), + Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptClassLoaderContextNull) { + class_loader_context_ = std::nullopt; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Not(Contains(Flag("--class-loader-context-fds=", _))), + Not(Contains(Flag("--class-loader-context=", _))), + Not(Contains(Flag("--classpath-dir=", _))))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptNoOptionalInputFiles) { + profile_path_ = std::nullopt; + vdex_path_ = std::nullopt; + dm_path_ = std::nullopt; + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Not(Contains(Flag("--profile-file-fd=", _))), + Not(Contains(Flag("--input-vdex-fd=", _))), + Not(Contains(Flag("--dm-fd=", _))))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassBoot) { + priority_class_ = PriorityClass::BOOT; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + AllOf(Not(Contains(Flag("--set-task-profile=", _))), + Not(Contains(Flag("--set-priority=", _)))), + Contains(Flag("--compact-dex-level=", "none"))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassInteractive) { + priority_class_ = PriorityClass::INTERACTIVE; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")), + Contains(Flag("--set-priority=", "background"))), + Contains(Flag("--compact-dex-level=", "none"))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassInteractiveFast) { + priority_class_ = PriorityClass::INTERACTIVE_FAST; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")), + Contains(Flag("--set-priority=", "background"))), + Contains(Flag("--compact-dex-level=", "none"))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptPriorityClassBackground) { + priority_class_ = PriorityClass::BACKGROUND; + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBackground")), + Contains(Flag("--set-priority=", "background"))), + Not(Contains(Flag("--compact-dex-level=", _)))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDexoptOptions) { + dexopt_options_ = DexoptOptions{ + .compilationReason = "install", + .targetSdkVersion = 123, + .debuggable = false, + .generateAppImage = false, + .hiddenApiPolicyEnabled = false, + .comments = "my-comments", + }; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Contains(Flag("--compilation-reason=", "install")), + Contains(Flag("-Xtarget-sdk-version:", "123")), + Not(Contains("--debuggable")), + Not(Contains(Flag("--app-image-fd=", _))), + Not(Contains(Flag("-Xhidden-api-policy:", _))), + Contains(Flag("--comments=", "my-comments")))), + _, + _)) + .WillOnce(Return(0)); + + // `sizeBeforeBytes` should include the size of the old ART file even if no new ART file is + // generated. + RunDexopt(EX_NONE, + Field(&ArtdDexoptResult::sizeBeforeBytes, + strlen("old_art") + strlen("old_oat") + strlen("old_vdex"))); +} + +TEST_F(ArtdTest, dexoptDexoptOptions2) { + dexopt_options_ = DexoptOptions{ + .compilationReason = "bg-dexopt", + .targetSdkVersion = 456, + .debuggable = true, + .generateAppImage = true, + .hiddenApiPolicyEnabled = true, + }; + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")), + Contains(Flag("-Xtarget-sdk-version:", "456")), + Contains("--debuggable"), + Contains(Flag("--app-image-fd=", _)), + Contains(Flag("-Xhidden-api-policy:", "enabled")))), + _, + _)) + .WillOnce(Return(0)); + + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDefaultFlagsWhenNoSystemProps) { + dexopt_options_.generateAppImage = true; + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains(Flag("--swap-fd=", FdOf(_))), + Not(Contains(Flag("--instruction-set-features=", _))), + Not(Contains(Flag("--instruction-set-variant=", _))), + Not(Contains(Flag("--max-image-block-size=", _))), + Not(Contains(Flag("--very-large-app-threshold=", _))), + Not(Contains(Flag("--resolve-startup-const-strings=", _))), + Not(Contains("--generate-debug-info")), + Not(Contains("--generate-mini-debug-info")), + Contains("-Xdeny-art-apex-data-files"), + Not(Contains(Flag("--cpu-set=", _))), + Not(Contains(Flag("-j", _))), + Not(Contains(Flag("-Xms", _))), + Not(Contains(Flag("-Xmx", _))), + Not(Contains("--compile-individually")), + Not(Contains(Flag("--image-format=", _))), + Not(Contains("--force-jit-zygote")), + Not(Contains(Flag("--boot-image=", _))))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptFlagsFromSystemProps) { + dexopt_options_.generateAppImage = true; + + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-swap")).WillOnce(Return("0")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.features")) + .WillOnce(Return("features")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.variant")).WillOnce(Return("variant")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-max-image-block-size")) + .WillOnce(Return("size")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-very-large")) + .WillOnce(Return("threshold")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-resolve-startup-strings")) + .WillOnce(Return("strings")); + EXPECT_CALL(*mock_props_, GetProperty("debug.generate-debug-info")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-minidebuginfo")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("odsign.verification.success")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xms")).WillOnce(Return("xms")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xmx")).WillOnce(Return("xmx")); + EXPECT_CALL(*mock_props_, GetProperty("ro.config.low_ram")).WillOnce(Return("1")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.appimageformat")).WillOnce(Return("imgfmt")); + EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillOnce(Return("boot-image")); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Not(Contains(Flag("--swap-fd=", _))), + Contains(Flag("--instruction-set-features=", "features")), + Contains(Flag("--instruction-set-variant=", "variant")), + Contains(Flag("--max-image-block-size=", "size")), + Contains(Flag("--very-large-app-threshold=", "threshold")), + Contains(Flag("--resolve-startup-const-strings=", "strings")), + Contains("--generate-debug-info"), + Contains("--generate-mini-debug-info"), + Not(Contains("-Xdeny-art-apex-data-files")), + Contains(Flag("-Xms", "xms")), + Contains(Flag("-Xmx", "xmx")), + Contains("--compile-individually"), + Contains(Flag("--image-format=", "imgfmt")), + Not(Contains("--force-jit-zygote")), + Contains(Flag("--boot-image=", "boot-image")))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptFlagsForceJitZygote) { + EXPECT_CALL(*mock_props_, + GetProperty("persist.device_config.runtime_native_boot.profilebootclasspath")) + .WillOnce(Return("true")); + ON_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillByDefault(Return("boot-image")); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode(WhenSplitBy("--", + _, + AllOf(Contains("--force-jit-zygote"), + Not(Contains(Flag("--boot-image=", _))))), + _, + _)) + .WillOnce(Return(0)); + RunDexopt(); +} + +static void SetDefaultResourceControlProps(MockSystemProperties* mock_props) { + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4")); +} + +TEST_F(ArtdTest, dexoptDefaultResourceControlBoot) { + SetDefaultResourceControlProps(mock_props_); + + // The default resource control properties don't apply to BOOT. + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy( + "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))), + _, + _)) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BOOT; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptDefaultResourceControlOther) { + SetDefaultResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))), + _, + _)) + .Times(3) + .WillRepeatedly(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE_FAST; + RunDexopt(); + priority_class_ = PriorityClass::INTERACTIVE; + RunDexopt(); + priority_class_ = PriorityClass::BACKGROUND; + RunDexopt(); +} + +static void SetAllResourceControlProps(MockSystemProperties* mock_props) { + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-cpu-set")) + .WillRepeatedly(Return("0,1,2,3")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-threads")) + .WillRepeatedly(Return("8")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-cpu-set")) + .WillRepeatedly(Return("0,2,3")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-threads")) + .WillRepeatedly(Return("6")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-cpu-set")) + .WillRepeatedly(Return("0")); + EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-threads")) + .WillRepeatedly(Return("2")); +} + +TEST_F(ArtdTest, dexoptAllResourceControlBoot) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))), + _, + _)) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BOOT; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlInteractiveFast) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))), + _, + _)) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE_FAST; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlInteractive) { + SetAllResourceControlProps(mock_props_); + + // INTERACTIVE always uses the default resource control properties. + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy( + "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))), + _, + _)) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::INTERACTIVE; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptAllResourceControlBackground) { + SetAllResourceControlProps(mock_props_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))), + _, + _)) + .WillOnce(Return(0)); + priority_class_ = PriorityClass::BACKGROUND; + RunDexopt(); +} + +TEST_F(ArtdTest, dexoptFailed) { + dexopt_options_.generateAppImage = true; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")), + WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")), + Return(1))); + RunDexopt(EX_SERVICE_SPECIFIC); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art"); +} + +TEST_F(ArtdTest, dexoptFailedToCommit) { + std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_inaccessible; + std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_unroot; + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")), + [&](auto, auto, auto) { + scoped_inaccessible = std::make_unique<ScopeGuard<std::function<void()>>>( + ScopedInaccessible(scratch_path_ + "/a/oat/arm64")); + scoped_unroot = + std::make_unique<ScopeGuard<std::function<void()>>>(ScopedUnroot()); + return 0; + })); + + RunDexopt( + EX_SERVICE_SPECIFIC, + AllOf(Field(&ArtdDexoptResult::sizeBytes, 0), Field(&ArtdDexoptResult::sizeBeforeBytes, 0))); +} + +TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) { + std::shared_ptr<IArtdCancellationSignal> cancellation_signal; + ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk()); + + constexpr pid_t kPid = 123; + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) { + callbacks.on_start(kPid); + callbacks.on_end(kPid); + return Error(); + }); + EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)); + + cancellation_signal->cancel(); + + RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art"); +} + +TEST_F(ArtdTest, dexoptCancelledDuringDex2oat) { + std::shared_ptr<IArtdCancellationSignal> cancellation_signal; + ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk()); + + constexpr pid_t kPid = 123; + constexpr std::chrono::duration<int> kTimeout = std::chrono::seconds(1); + + std::condition_variable process_started_cv, process_killed_cv; + std::mutex mu; + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) { + std::unique_lock<std::mutex> lock(mu); + // Step 2. + callbacks.on_start(kPid); + process_started_cv.notify_one(); + EXPECT_EQ(process_killed_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout); + // Step 5. + callbacks.on_end(kPid); + return Error(); + }); + + EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)).WillOnce([&](auto, auto) { + // Step 4. + process_killed_cv.notify_one(); + return 0; + }); + + std::thread t; + { + std::unique_lock<std::mutex> lock(mu); + // Step 1. + t = std::thread([&] { + RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, true), cancellation_signal); + }); + EXPECT_EQ(process_started_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout); + // Step 3. + cancellation_signal->cancel(); + } + + t.join(); + + // Step 6. + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art"); +} + +TEST_F(ArtdTest, dexoptCancelledAfterDex2oat) { + std::shared_ptr<IArtdCancellationSignal> cancellation_signal; + ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk()); + + constexpr pid_t kPid = 123; + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")), + WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")), + [&](auto, const ExecCallbacks& callbacks, auto) { + callbacks.on_start(kPid); + callbacks.on_end(kPid); + return 0; + })); + EXPECT_CALL(mock_kill_, Call).Times(0); + + RunDexopt(EX_NONE, Field(&ArtdDexoptResult::cancelled, false), cancellation_signal); + + // This signal should be ignored. + cancellation_signal->cancel(); + + CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "new_oat"); + CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "new_vdex"); + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art")); +} + +TEST_F(ArtdTest, dexoptDexFileNotOtherReadable) { + dex_file_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr("Outputs cannot be other-readable because the dex file")))); +} + +TEST_F(ArtdTest, dexoptProfileNotOtherReadable) { + profile_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr("Outputs cannot be other-readable because the profile")))); +} + +TEST_F(ArtdTest, dexoptOutputNotOtherReadable) { + output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false; + dex_file_other_readable_ = false; + profile_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0)); + RunDexopt(); + CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", false); + CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", false); +} + +TEST_F(ArtdTest, dexoptUidMismatch) { + output_artifacts_.permissionSettings.fileFsPermission.uid = 12345; + output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false; + dex_file_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr("Outputs' owner doesn't match the dex file")))); +} + +TEST_F(ArtdTest, dexoptGidMismatch) { + output_artifacts_.permissionSettings.fileFsPermission.gid = 12345; + output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false; + dex_file_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr("Outputs' owner doesn't match the dex file")))); +} + +TEST_F(ArtdTest, dexoptGidMatchesUid) { + output_artifacts_.permissionSettings.fileFsPermission = { + .uid = 123, .gid = 123, .isOtherReadable = false}; + EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat); // For profile. + EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _)) + .WillOnce(DoAll(SetArgPointee<1>((struct stat){ + .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}), + Return(0))); + ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0)); + // It's okay to fail on chown. This happens when the test is not run as root. + RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE), + AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown"))))); +} + +TEST_F(ArtdTest, dexoptGidMatchesGid) { + output_artifacts_.permissionSettings.fileFsPermission = { + .uid = 123, .gid = 456, .isOtherReadable = false}; + EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat); // For profile. + EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _)) + .WillOnce(DoAll(SetArgPointee<1>((struct stat){ + .st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}), + Return(0))); + ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0)); + // It's okay to fail on chown. This happens when the test is not run as root. + RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE), + AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown"))))); +} + +TEST_F(ArtdTest, dexoptUidGidChangeOk) { + // The dex file is other-readable, so we don't check uid and gid. + output_artifacts_.permissionSettings.fileFsPermission = { + .uid = 12345, .gid = 12345, .isOtherReadable = false}; + ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0)); + // It's okay to fail on chown. This happens when the test is not run as root. + RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE), + AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown"))))); +} + +TEST_F(ArtdTest, dexoptNoUidGidChange) { + output_artifacts_.permissionSettings.fileFsPermission = { + .uid = -1, .gid = -1, .isOtherReadable = false}; + dex_file_other_readable_ = false; + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0)); + RunDexopt(); +} + +TEST_F(ArtdTest, isProfileUsable) { + std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + CreateFile(profile_file); + CreateFile(dex_file_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/profman"), + Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))), + Contains(Flag("--apk-fd=", FdOf(dex_file_))))), + HasKeepFdsFor("--reference-profile-file-fd=", "--apk-fd=")), + _, + _)) + .WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta)); + + bool result; + EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk()); + EXPECT_TRUE(result); +} + +TEST_F(ArtdTest, isProfileUsableFalse) { + std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + CreateFile(profile_file); + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles)); + + bool result; + EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk()); + EXPECT_FALSE(result); +} + +TEST_F(ArtdTest, isProfileUsableNotFound) { + CreateFile(dex_file_); + + bool result; + EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk()); + EXPECT_FALSE(result); +} + +TEST_F(ArtdTest, isProfileUsableFailed) { + std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + CreateFile(profile_file); + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100)); + + bool result; + ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result); + + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100")); +} + +TEST_F(ArtdTest, copyAndRewriteProfile) { + const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string src_file = OR_FATAL(BuildTmpProfilePath(src)); + CreateFile(src_file, "abc"); + OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + dst.profilePath.id = ""; + dst.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/profman"), + Contains("--copy-and-update-profile-key"), + Contains(Flag("--profile-file-fd=", FdOf(src_file))), + Contains(Flag("--apk-fd=", FdOf(dex_file_))))), + HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")), + Return(ProfmanResult::kCopyAndUpdateSuccess))); + + bool result; + EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(dst.profilePath.id, Not(IsEmpty())); + std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath)); + EXPECT_EQ(dst.profilePath.tmpPath, real_path); + CheckContent(real_path, "def"); +} + +TEST_F(ArtdTest, copyAndRewriteProfileFalse) { + const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string src_file = OR_FATAL(BuildTmpProfilePath(src)); + CreateFile(src_file, "abc"); + OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + dst.profilePath.id = ""; + dst.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch)); + + bool result; + EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk()); + EXPECT_FALSE(result); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +TEST_F(ArtdTest, copyAndRewriteProfileNotFound) { + CreateFile(dex_file_); + + const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>(); + OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + dst.profilePath.id = ""; + dst.profilePath.tmpPath = ""; + + bool result; + EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk()); + EXPECT_FALSE(result); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +TEST_F(ArtdTest, copyAndRewriteProfileFailed) { + const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string src_file = OR_FATAL(BuildTmpProfilePath(src)); + CreateFile(src_file, "abc"); + OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + dst.profilePath.id = ""; + dst.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100)); + + bool result; + ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result); + + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100")); + EXPECT_THAT(dst.profilePath.id, IsEmpty()); + EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty()); +} + +TEST_F(ArtdTest, commitTmpProfile) { + const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path)); + CreateFile(tmp_profile_file); + + EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk()); + + EXPECT_FALSE(std::filesystem::exists(tmp_profile_file)); + EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path)))); +} + +TEST_F(ArtdTest, commitTmpProfileFailed) { + const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>(); + ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path); + + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_THAT( + status.getMessage(), + ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re")); + + EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path)))); +} + +TEST_F(ArtdTest, deleteProfile) { + std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + CreateFile(profile_file); + + EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk()); + + EXPECT_FALSE(std::filesystem::exists(profile_file)); +} + +TEST_F(ArtdTest, deleteProfileDoesNotExist) { + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL(mock_logger_, Call).Times(0); + + EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk()); +} + +TEST_F(ArtdTest, deleteProfileFailed) { + auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction()); + EXPECT_CALL( + mock_logger_, + Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re"))); + + std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value())); + auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path()); + auto scoped_unroot = ScopedUnroot(); + + EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk()); +} + +class ArtdGetVisibilityTest : public ArtdTest { + protected: + template <typename PathType> + using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*); + + template <typename PathType> + void TestGetVisibilityOtherReadable(Method<PathType> method, + const PathType& input, + const std::string& path) { + CreateFile(path); + std::filesystem::permissions( + path, std::filesystem::perms::others_read, std::filesystem::perm_options::add); + + FileVisibility result; + ASSERT_TRUE(((*artd_).*method)(input, &result).isOk()); + EXPECT_EQ(result, FileVisibility::OTHER_READABLE); + } + + template <typename PathType> + void TestGetVisibilityNotOtherReadable(Method<PathType> method, + const PathType& input, + const std::string& path) { + CreateFile(path); + std::filesystem::permissions( + path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove); + + FileVisibility result; + ASSERT_TRUE(((*artd_).*method)(input, &result).isOk()); + EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE); + } + + template <typename PathType> + void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) { + FileVisibility result; + ASSERT_TRUE(((*artd_).*method)(input, &result).isOk()); + EXPECT_EQ(result, FileVisibility::NOT_FOUND); + } + + template <typename PathType> + void TestGetVisibilityPermissionDenied(Method<PathType> method, + const PathType& input, + const std::string& path) { + CreateFile(path); + + auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path()); + auto scoped_unroot = ScopedUnroot(); + + FileVisibility result; + ndk::ScopedAStatus status = ((*artd_).*method)(input, &result); + EXPECT_FALSE(status.isOk()); + EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC); + EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of")); + } +}; + +TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) { + TestGetVisibilityOtherReadable(&Artd::getProfileVisibility, + profile_path_.value(), + OR_FATAL(BuildProfileOrDmPath(profile_path_.value()))); +} + +TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) { + TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility, + profile_path_.value(), + OR_FATAL(BuildProfileOrDmPath(profile_path_.value()))); +} + +TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) { + TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value()); +} + +TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) { + TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility, + profile_path_.value(), + OR_FATAL(BuildProfileOrDmPath(profile_path_.value()))); +} + +TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) { + TestGetVisibilityOtherReadable( + &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_))); +} + +TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) { + TestGetVisibilityNotOtherReadable( + &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_))); +} + +TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) { + TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_); +} + +TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) { + TestGetVisibilityPermissionDenied( + &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_))); +} + +TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) { + TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_); +} + +TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) { + TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_); +} + +TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) { + TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_); +} + +TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) { + TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_); +} + +TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) { + TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility, + dm_path_.value(), + OR_FATAL(BuildDexMetadataPath(dm_path_.value()))); +} + +TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) { + TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility, + dm_path_.value(), + OR_FATAL(BuildDexMetadataPath(dm_path_.value()))); +} + +TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) { + TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value()); +} + +TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) { + TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility, + dm_path_.value(), + OR_FATAL(BuildDexMetadataPath(dm_path_.value()))); +} + +TEST_F(ArtdTest, mergeProfiles) { + const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path)); + CreateFile(reference_profile_file, "abc"); + + // Doesn't exist. + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + + PrimaryCurProfilePath profile_1_path{ + .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path)); + CreateFile(profile_1_file, "def"); + + OutputProfile output_profile{.profilePath = reference_profile_path, + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + std::string dex_file_1 = scratch_path_ + "/a/b.apk"; + std::string dex_file_2 = scratch_path_ + "/a/c.apk"; + CreateFile(dex_file_1); + CreateFile(dex_file_2); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy( + "--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/profman"), + Not(Contains(Flag("--profile-file-fd=", FdOf(profile_0_file)))), + Contains(Flag("--profile-file-fd=", FdOf(profile_1_file))), + Contains(Flag("--reference-profile-file-fd=", FdHasContent("abc"))), + Contains(Flag("--apk-fd=", FdOf(dex_file_1))), + Contains(Flag("--apk-fd=", FdOf(dex_file_2))), + Not(Contains("--force-merge")), + Not(Contains("--boot-image-merge")))), + HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")), + _, + _)) + .WillOnce(DoAll(WithArg<0>(ClearAndWriteToFdFlag("--reference-profile-file-fd=", "merged")), + Return(ProfmanResult::kCompile))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path, profile_1_path}, + reference_profile_path, + &output_profile, + {dex_file_1, dex_file_2}, + /*in_options=*/{}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + std::string real_path = OR_FATAL(BuildTmpProfilePath(output_profile.profilePath)); + EXPECT_EQ(output_profile.profilePath.tmpPath, real_path); + CheckContent(real_path, "merged"); +} + +TEST_F(ArtdTest, mergeProfilesEmptyReferenceProfile) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")), + AllOf(Contains(art_root_ + "/bin/profman"), + Contains(Flag("--profile-file-fd=", FdOf(profile_0_file))), + Contains(Flag("--reference-profile-file-fd=", FdHasContent(""))), + Contains(Flag("--apk-fd=", FdOf(dex_file_))))), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "merged")), + Return(ProfmanResult::kCompile))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + /*in_options=*/{}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty())); +} + +TEST_F(ArtdTest, mergeProfilesProfilesDontExist) { + const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>(); + std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path)); + CreateFile(reference_profile_file, "abc"); + + // Doesn't exist. + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + + // Doesn't exist. + PrimaryCurProfilePath profile_1_path{ + .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path)); + + OutputProfile output_profile{.profilePath = reference_profile_path, + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode).Times(0); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + /*in_options=*/{}, + &result) + .isOk()); + EXPECT_FALSE(result); + EXPECT_THAT(output_profile.profilePath.id, IsEmpty()); + EXPECT_THAT(output_profile.profilePath.tmpPath, IsEmpty()); +} + +TEST_F(ArtdTest, mergeProfilesWithOptionsForceMerge) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL( + *mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", _, AllOf(Contains("--force-merge"), Contains("--boot-image-merge"))), + _, + _)) + .WillOnce(Return(ProfmanResult::kSuccess)); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + {.forceMerge = true, .forBootImage = true}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty())); +} + +TEST_F(ArtdTest, mergeProfilesWithOptionsDumpOnly) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + AllOf(WhenSplitBy("--", + _, + AllOf(Contains("--dump-only"), + Not(Contains(Flag("--reference-profile-file-fd=", _))))), + HasKeepFdsFor("--profile-file-fd=", "--apk-fd=", "--dump-output-to-fd=")), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")), + Return(ProfmanResult::kSuccess))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + {.dumpOnly = true}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + CheckContent(output_profile.profilePath.tmpPath, "dump"); +} + +TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains("--dump-classes-and-methods"), + Not(Contains(Flag("--reference-profile-file-fd=", _))))), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")), + Return(ProfmanResult::kSuccess))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + {.dumpClassesAndMethods = true}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + CheckContent(output_profile.profilePath.tmpPath, "dump"); +} + +TEST_F(ArtdTest, cleanup) { + std::vector<std::string> gc_removed_files; + std::vector<std::string> gc_kept_files; + + auto CreateGcRemovedFile = [&](const std::string& path) { + CreateFile(path); + gc_removed_files.push_back(path); + }; + + auto CreateGcKeptFile = [&](const std::string& path) { + CreateFile(path); + gc_kept_files.push_back(path); + }; + + // Unmanaged files. + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.txt"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.txt"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.tmp"); + + // Files to keep. + CreateGcKeptFile(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"); + CreateGcKeptFile(android_data_ + "/misc/profiles/cur/3/com.android.foo/primary.prof"); + CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex"); + CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex"); + CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex"); + CreateGcKeptFile( + android_expand_ + + "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex"); + CreateGcKeptFile( + android_expand_ + + "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.vdex"); + CreateGcKeptFile( + android_expand_ + + "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.art"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.vdex"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.art"); + + // Files to remove. + CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"); + CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/2/com.android.foo/primary.prof"); + CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/3/com.android.bar/primary.prof"); + CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/extra.odex"); + CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.dex"); + CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.vdex"); + CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.art"); + CreateGcRemovedFile( + android_expand_ + + "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.odex"); + CreateGcRemovedFile( + android_expand_ + + "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.vdex"); + CreateGcRemovedFile( + android_expand_ + + "/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.art"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof.123456.tmp"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.vdex"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.art"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex.123456.tmp"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/2.odex.123456.tmp"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.odex"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.art"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex.123456.tmp"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.odex"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.vdex"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art"); + CreateGcRemovedFile(android_data_ + + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art.123456.tmp"); + CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.bar/aaa/oat/arm64/1.vdex"); + + int64_t aidl_return; + ASSERT_TRUE( + artd_ + ->cleanup( + { + PrimaryCurProfilePath{ + .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}, + PrimaryCurProfilePath{ + .userId = 3, .packageName = "com.android.foo", .profileName = "primary"}, + }, + { + ArtifactsPath{.dexPath = "/system/app/Foo/Foo.apk", + .isa = "arm64", + .isInDalvikCache = true}, + ArtifactsPath{ + .dexPath = + android_expand_ + + "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/base.apk", + .isa = "arm64", + .isInDalvikCache = false}, + ArtifactsPath{.dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/2.apk", + .isa = "arm64", + .isInDalvikCache = false}, + }, + { + VdexPath{ArtifactsPath{ + .dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/1.apk", + .isa = "arm64", + .isInDalvikCache = false}}, + }, + &aidl_return) + .isOk()); + + for (const std::string& path : gc_removed_files) { + EXPECT_FALSE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be removed", path); + } + + for (const std::string& path : gc_kept_files) { + EXPECT_TRUE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be kept", path); + } +} + } // namespace } // namespace artd } // namespace art diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp index ad8474f369..b6fd5b8241 100644 --- a/artd/binder/Android.bp +++ b/artd/binder/Android.bp @@ -31,6 +31,10 @@ aidl_interface { backend: { java: { enabled: true, + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], }, cpp: { enabled: false, @@ -40,9 +44,7 @@ aidl_interface { apex_available: [ "com.android.art", "com.android.art.debug", - "com.android.compos", ], - min_sdk_version: "31", }, }, unstable: true, @@ -50,4 +52,5 @@ aidl_interface { "//system/tools/aidl/build", "//art:__subpackages__", ], + min_sdk_version: "31", } diff --git a/artd/binder/com/android/server/art/ArtConstants.aidl b/artd/binder/com/android/server/art/ArtConstants.aidl new file mode 100644 index 0000000000..e9f702ef07 --- /dev/null +++ b/artd/binder/com/android/server/art/ArtConstants.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Constants used by ART Service Java code that must be kept in sync with those in ART native code. + * + * @hide + */ +parcelable ArtConstants { + /** + * A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with + * {@code kReasonVdex} in art/runtime/oat_file.h. + * + * This isn't a valid reason to feed into DexoptParams. + */ + const @utf8InCpp String REASON_VDEX = "vdex"; +} diff --git a/artd/binder/com/android/server/art/ArtdDexoptResult.aidl b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl new file mode 100644 index 0000000000..6f031f28b6 --- /dev/null +++ b/artd/binder/com/android/server/art/ArtdDexoptResult.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * The result of {@code IArtd.dexopt}. + * + * @hide + */ +parcelable ArtdDexoptResult { + /** True if the operation is cancelled. */ + boolean cancelled; + /** + * The wall time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if + * failed to get the value. + */ + long wallTimeMs; + /** + * The CPU time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if + * failed to get the value. + */ + long cpuTimeMs; + /** + * The total size, in bytes, of the dexopt artifacts, or 0 if dex2oat fails, is cancelled, or + * is not run. + */ + long sizeBytes; + /** + * The total size, in bytes, of the previous dexopt artifacts that have been replaced, or + * 0 if there were no previous dexopt artifacts or dex2oat fails, is cancelled, or is not + * run. + */ + long sizeBeforeBytes; +} diff --git a/artd/binder/com/android/server/art/ArtifactsPath.aidl b/artd/binder/com/android/server/art/ArtifactsPath.aidl new file mode 100644 index 0000000000..3122f0f6b9 --- /dev/null +++ b/artd/binder/com/android/server/art/ArtifactsPath.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Represents the path to the dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files). + * + * @hide + */ +parcelable ArtifactsPath { + /** The absolute path starting with '/' to the dex file (i.e., APK or JAR file). */ + @utf8InCpp String dexPath; + /** The instruction set of the dexopt artifacts. */ + @utf8InCpp String isa; + /** Whether the dexopt artifacts are in the dalvik-cache folder. */ + boolean isInDalvikCache; +} diff --git a/artd/binder/com/android/server/art/DexMetadataPath.aidl b/artd/binder/com/android/server/art/DexMetadataPath.aidl new file mode 100644 index 0000000000..5f9ab81ece --- /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 0000000000..305445e361 --- /dev/null +++ b/artd/binder/com/android/server/art/DexoptOptions.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Miscellaneous options for performing dexopt. Every field corresponds to a dex2oat command line + * flag. + * + * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes + * static flags, and flags that only depend on system properties or other passed parameters, such as + * the priority class. + * + * All fields are required. + * + * @hide + */ +parcelable DexoptOptions { + /** --compilation-reason */ + @utf8InCpp String compilationReason; + /** -Xtarget-sdk-version */ + int targetSdkVersion; + /** --debuggable */ + boolean debuggable; + /** --app-image-fd */ + boolean generateAppImage; + /** -Xhidden-api-policy:enabled */ + boolean hiddenApiPolicyEnabled; + /** --comments */ + @utf8InCpp String comments; +} diff --git a/artd/binder/com/android/server/art/DexoptTrigger.aidl b/artd/binder/com/android/server/art/DexoptTrigger.aidl new file mode 100644 index 0000000000..79621a92da --- /dev/null +++ b/artd/binder/com/android/server/art/DexoptTrigger.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Represents the conditions where dexopt should be performed. + * See `OatFileAssistant::DexOptTrigger`. + * + * This is actually used as a bit field, but is declared as an enum because AIDL doesn't support bit + * fields. + * + * @hide + */ +@Backing(type="int") +enum DexoptTrigger { + COMPILER_FILTER_IS_BETTER = 1 << 0, + COMPILER_FILTER_IS_SAME = 1 << 1, + COMPILER_FILTER_IS_WORSE = 1 << 2, + PRIMARY_BOOT_IMAGE_BECOMES_USABLE = 1 << 3, + NEED_EXTRACTION = 1 << 4, +} diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl new file mode 100644 index 0000000000..ceaa8186d4 --- /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 0000000000..9c2ddb9b90 --- /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 0000000000..99c4951d10 --- /dev/null +++ b/artd/binder/com/android/server/art/GetDexoptNeededResult.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * The result of {@code IArtd.getDexoptNeeded}. + * + * @hide + */ +parcelable GetDexoptNeededResult { + /** Whether dexopt is needed. */ + boolean isDexoptNeeded; + /** Whether there is a usable VDEX file. Note that this can be true even if dexopt is needed. */ + boolean isVdexUsable; + /** The location of the best usable artifacts. */ + ArtifactsLocation artifactsLocation = ArtifactsLocation.NONE_OR_ERROR; + + enum ArtifactsLocation { + /** No usable artifacts. */ + NONE_OR_ERROR = 0, + /** In the global "dalvik-cache" folder. */ + DALVIK_CACHE = 1, + /** In the "oat" folder next to the dex file. */ + NEXT_TO_DEX = 2, + /** In the dex metadata file. This means the only usable artifact is the VDEX file. */ + DM = 3, + } +} diff --git a/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl new file mode 100644 index 0000000000..08786caa77 --- /dev/null +++ b/artd/binder/com/android/server/art/GetDexoptStatusResult.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * The result of {@code IArtd.getDexoptStatus}. Each field corresponds to a field in + * {@code com.android.server.art.model.DexoptStatus.DexFileDexoptStatus}. + * + * @hide + */ +parcelable GetDexoptStatusResult { + @utf8InCpp String compilerFilter; + @utf8InCpp String compilationReason; + @utf8InCpp String locationDebugString; +} diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 58b2aae3b9..a130e9603b 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -16,8 +16,164 @@ package com.android.server.art; -/** {@hide} */ +/** @hide */ interface IArtd { // Test to see if the artd service is available. boolean isAlive(); + + /** + * Deletes artifacts and returns the released space, in bytes. + * + * Throws fatal errors. Logs and ignores non-fatal errors. + */ + long deleteArtifacts(in com.android.server.art.ArtifactsPath artifactsPath); + + /** + * Returns the dexopt status of a dex file. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.GetDexoptStatusResult getDexoptStatus( + @utf8InCpp String dexFile, @utf8InCpp String instructionSet, + @nullable @utf8InCpp String classLoaderContext); + + /** + * Returns true if the profile exists and contains entries for the given dex file. + * + * Throws fatal and non-fatal errors. + */ + boolean isProfileUsable(in com.android.server.art.ProfilePath profile, + @utf8InCpp String dexFile); + + /** + * Copies the profile and rewrites it for the given dex file. Returns true and fills + * `dst.profilePath.id` if the operation succeeds and `src` exists and contains entries that + * match the given dex file. + * + * Throws fatal and non-fatal errors. + */ + boolean copyAndRewriteProfile(in com.android.server.art.ProfilePath src, + inout com.android.server.art.OutputProfile dst, @utf8InCpp String dexFile); + + /** + * Moves the temporary profile to the permanent location. + * + * Throws fatal and non-fatal errors. + */ + void commitTmpProfile(in com.android.server.art.ProfilePath.TmpProfilePath profile); + + /** + * Deletes the profile. Does nothing of the profile doesn't exist. + * + * Operates on the whole DM file if given one. + * + * Throws fatal errors. Logs and ignores non-fatal errors. + */ + void deleteProfile(in com.android.server.art.ProfilePath profile); + + /** + * Returns the visibility of the profile. + * + * Operates on the whole DM file if given one. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.FileVisibility getProfileVisibility( + in com.android.server.art.ProfilePath profile); + + /** + * Merges profiles. Both `profiles` and `referenceProfile` are inputs, while the difference is + * that `referenceProfile` is also used as the reference to calculate the diff. `profiles` that + * don't exist are skipped, while `referenceProfile`, if provided, must exist. Returns true, + * writes the merge result to `outputProfile` and fills `outputProfile.profilePath.id` and + * `outputProfile.profilePath.tmpPath` if a merge has been performed. + * + * When `options.forceMerge`, `options.dumpOnly`, or `options.dumpClassesAndMethods` is set, + * `referenceProfile` must not be set. I.e., all inputs must be provided by `profiles`. This is + * because the merge will always happen, and hence no reference profile is needed to calculate + * the diff. + * + * Throws fatal and non-fatal errors. + */ + boolean mergeProfiles(in List<com.android.server.art.ProfilePath> profiles, + in @nullable com.android.server.art.ProfilePath referenceProfile, + inout com.android.server.art.OutputProfile outputProfile, + in @utf8InCpp List<String> dexFiles, + in com.android.server.art.MergeProfileOptions options); + + /** + * Returns the visibility of the artifacts. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.FileVisibility getArtifactsVisibility( + in com.android.server.art.ArtifactsPath artifactsPath); + + /** + * Returns the visibility of the dex file. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.FileVisibility getDexFileVisibility(@utf8InCpp String dexFile); + + /** + * Returns the visibility of the DM file. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.FileVisibility getDmFileVisibility( + in com.android.server.art.DexMetadataPath dmFile); + + /** + * Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values + * defined in `com.android.server.art.DexoptTrigger`. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.GetDexoptNeededResult getDexoptNeeded( + @utf8InCpp String dexFile, @utf8InCpp String instructionSet, + @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter, + int dexoptTrigger); + + /** + * Dexopts a dex file for the given instruction set. + * + * Throws fatal and non-fatal errors. + */ + com.android.server.art.ArtdDexoptResult dexopt( + in com.android.server.art.OutputArtifacts outputArtifacts, + @utf8InCpp String dexFile, @utf8InCpp String instructionSet, + @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter, + in @nullable com.android.server.art.ProfilePath profile, + in @nullable com.android.server.art.VdexPath inputVdex, + in @nullable com.android.server.art.DexMetadataPath dmFile, + com.android.server.art.PriorityClass priorityClass, + in com.android.server.art.DexoptOptions dexoptOptions, + in com.android.server.art.IArtdCancellationSignal cancellationSignal); + + /** + * Returns a cancellation signal which can be used to cancel {@code dexopt} calls. + */ + com.android.server.art.IArtdCancellationSignal createCancellationSignal(); + + /** + * Deletes all files that are managed by artd, except those specified in the arguments. Returns + * the size of the freed space, in bytes. + * + * For each entry in `artifactsToKeep`, all three kinds of artifacts (ODEX, VDEX, ART) are + * kept. For each entry in `vdexFilesToKeep`, only the VDEX file will be kept. Note that VDEX + * files included in `artifactsToKeep` don't have to be listed in `vdexFilesToKeep`. + * + * Throws fatal errors. Logs and ignores non-fatal errors. + */ + long cleanup(in List<com.android.server.art.ProfilePath> profilesToKeep, + in List<com.android.server.art.ArtifactsPath> artifactsToKeep, + in List<com.android.server.art.VdexPath> vdexFilesToKeep); + + /** + * Returns whether the dex file is in Incremental FS. + * + * Throws fatal errors. On non-fatal errors, logs the error and returns false. + */ + boolean isIncrementalFsPath(@utf8InCpp String dexFile); } diff --git a/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl b/artd/binder/com/android/server/art/IArtdCancellationSignal.aidl new file mode 100644 index 0000000000..fb15e648a0 --- /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 0000000000..2d007f9406 --- /dev/null +++ b/artd/binder/com/android/server/art/MergeProfileOptions.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Miscellaneous options for merging profiles. Every field corresponds to a profman command line + * flag. + * + * DO NOT add fields for flags that artd can determine directly with trivial logic. That includes + * static flags, and flags that only depend on system properties or other passed parameters. + * + * All fields are required. + * + * @hide + */ +parcelable MergeProfileOptions { + /** --force-merge */ + boolean forceMerge; + /** --boot-image-merge */ + boolean forBootImage; + /** --dump-only */ + boolean dumpOnly; + /** --dump-classes-and-methods */ + boolean dumpClassesAndMethods; +} diff --git a/artd/binder/com/android/server/art/OutputArtifacts.aidl b/artd/binder/com/android/server/art/OutputArtifacts.aidl new file mode 100644 index 0000000000..9a53965a68 --- /dev/null +++ b/artd/binder/com/android/server/art/OutputArtifacts.aidl @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +/** + * Represents output dexopt artifacts of a dex file (i.e., ART, OAT, and VDEX files). + * + * @hide + */ +parcelable OutputArtifacts { + /** The path to the output. */ + com.android.server.art.ArtifactsPath artifactsPath; + + parcelable PermissionSettings { + /** + * The permission of the directories that contain the artifacts. Has no effect if + * `artifactsPath.isInDalvikCache` is true. + */ + com.android.server.art.FsPermission dirFsPermission; + + /** The permission of the files. */ + com.android.server.art.FsPermission fileFsPermission; + + /** The tuple used for looking up for the SELinux context. */ + parcelable SeContext { + /** The seinfo tag in SELinux policy. */ + @utf8InCpp String seInfo; + + /** The uid that represents the combination of the user id and the app id. */ + int uid; + } + + /** + * Determines the SELinux context of the directories and the files. If empty, the default + * context based on the file path will be used. Has no effect if + * `artifactsPath.isInDalvikCache` is true. + */ + @nullable SeContext seContext; + } + + PermissionSettings permissionSettings; +} diff --git a/artd/binder/com/android/server/art/OutputProfile.aidl b/artd/binder/com/android/server/art/OutputProfile.aidl new file mode 100644 index 0000000000..50efda2053 --- /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 0000000000..abea3f39d7 --- /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 0000000000..43df531d89 --- /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 0000000000..6112e7a490 --- /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 0000000000..f3558534ba --- /dev/null +++ b/artd/file_utils.cc @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "file_utils.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <filesystem> +#include <memory> +#include <string> +#include <string_view> +#include <system_error> +#include <utility> + +#include "aidl/com/android/server/art/FsPermission.h" +#include "android-base/errors.h" +#include "android-base/logging.h" +#include "android-base/result.h" +#include "android-base/scopeguard.h" +#include "base/macros.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" + +namespace art { +namespace artd { + +namespace { + +using ::aidl::com::android::server::art::FsPermission; +using ::android::base::make_scope_guard; +using ::android::base::Result; + +void UnlinkIfExists(const std::string& path) { + std::error_code ec; + std::filesystem::remove(path, ec); + if (ec) { + LOG(WARNING) << ART_FORMAT("Failed to remove file '{}': {}", path, ec.message()); + } +} + +} // namespace + +Result<std::unique_ptr<NewFile>> NewFile::Create(const std::string& path, + const FsPermission& fs_permission) { + std::unique_ptr<NewFile> output_file(new NewFile(path, fs_permission)); + OR_RETURN(output_file->Init()); + return output_file; +} + +NewFile::~NewFile() { Cleanup(); } + +Result<void> NewFile::Keep() { + if (close(std::exchange(fd_, -1)) != 0) { + return ErrnoErrorf("Failed to close file '{}'", temp_path_); + } + return {}; +} + +Result<void> NewFile::CommitOrAbandon() { + auto cleanup = make_scope_guard([this] { Unlink(); }); + OR_RETURN(Keep()); + std::error_code ec; + std::filesystem::rename(temp_path_, final_path_, ec); + if (ec) { + // If this fails because the temp file doesn't exist, it could be that the file is deleted by + // `Artd::cleanup` if that method is run simultaneously. At the time of writing, this should + // never happen because `Artd::cleanup` is only called at the end of the backgrond dexopt job. + return Errorf( + "Failed to move new file '{}' to path '{}': {}", temp_path_, final_path_, ec.message()); + } + cleanup.Disable(); + committed_ = true; + return {}; +} + +void NewFile::Cleanup() { + if (fd_ >= 0) { + Unlink(); + if (close(std::exchange(fd_, -1)) != 0) { + // Nothing we can do. If the file is already unlinked, it will go away when the process exits. + PLOG(WARNING) << "Failed to close file '" << temp_path_ << "'"; + } + } +} + +Result<void> NewFile::Init() { + mode_t mode = FileFsPermissionToMode(fs_permission_); + // "<path_>.XXXXXX.tmp". + temp_path_ = BuildTempPath(final_path_, "XXXXXX"); + fd_ = mkstemps(temp_path_.data(), /*suffixlen=*/4); + if (fd_ < 0) { + return ErrnoErrorf("Failed to create temp file for '{}'", final_path_); + } + temp_id_ = temp_path_.substr(/*pos=*/final_path_.length() + 1, /*count=*/6); + if (fchmod(fd_, mode) != 0) { + return ErrnoErrorf("Failed to chmod file '{}'", temp_path_); + } + OR_RETURN(Chown(temp_path_, fs_permission_)); + return {}; +} + +void NewFile::Unlink() { + // This should never fail. We were able to create the file, so we should be able to remove it. + UnlinkIfExists(temp_path_); +} + +Result<void> NewFile::CommitAllOrAbandon(const std::vector<NewFile*>& files_to_commit, + const std::vector<std::string_view>& files_to_remove) { + std::vector<std::pair<std::string_view, std::string>> moved_files; + + auto cleanup = make_scope_guard([&]() { + // Clean up new files. + for (NewFile* new_file : files_to_commit) { + if (new_file->committed_) { + UnlinkIfExists(new_file->FinalPath()); + } else { + new_file->Cleanup(); + } + } + + // Move old files back. + for (const auto& [original_path, temp_path] : moved_files) { + std::error_code ec; + std::filesystem::rename(temp_path, original_path, ec); + if (ec) { + // This should never happen. We were able to move the file from `original_path` to + // `temp_path`. We should be able to move it back. + LOG(WARNING) << ART_FORMAT("Failed to move old file '{}' back from temporary path '{}': {}", + original_path, + temp_path, + ec.message()); + } + } + }); + + // Move old files to temporary locations. + std::vector<std::string_view> all_files_to_remove; + all_files_to_remove.reserve(files_to_commit.size() + files_to_remove.size()); + for (NewFile* file : files_to_commit) { + all_files_to_remove.push_back(file->FinalPath()); + } + all_files_to_remove.insert( + all_files_to_remove.end(), files_to_remove.begin(), files_to_remove.end()); + + for (std::string_view original_path : all_files_to_remove) { + std::error_code ec; + std::filesystem::file_status status = std::filesystem::status(original_path, ec); + if (!std::filesystem::status_known(status)) { + return Errorf("Failed to get status of old file '{}': {}", original_path, ec.message()); + } + if (std::filesystem::is_directory(status)) { + return ErrnoErrorf("Old file '{}' is a directory", original_path); + } + if (std::filesystem::exists(status)) { + std::string temp_path = BuildTempPath(original_path, "XXXXXX"); + int fd = mkstemps(temp_path.data(), /*suffixlen=*/4); + if (fd < 0) { + return ErrnoErrorf("Failed to create temporary path for old file '{}'", original_path); + } + close(fd); + + std::filesystem::rename(original_path, temp_path, ec); + if (ec) { + UnlinkIfExists(temp_path); + return Errorf("Failed to move old file '{}' to temporary path '{}': {}", + original_path, + temp_path, + ec.message()); + } + + moved_files.push_back({original_path, std::move(temp_path)}); + } + } + + // Commit new files. + for (NewFile* file : files_to_commit) { + OR_RETURN(file->CommitOrAbandon()); + } + + cleanup.Disable(); + + // Clean up old files. + for (const auto& [original_path, temp_path] : moved_files) { + // This should never fail. We were able to move the file to `temp_path`. We should be able to + // remove it. + UnlinkIfExists(temp_path); + } + + return {}; +} + +std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) { + return ART_FORMAT("{}.{}.tmp", final_path, id); +} + +Result<std::unique_ptr<File>> OpenFileForReading(const std::string& path) { + std::unique_ptr<File> file(OS::OpenFileForReading(path.c_str())); + if (file == nullptr) { + return ErrnoErrorf("Failed to open file '{}'", path); + } + return file; +} + +mode_t FileFsPermissionToMode(const FsPermission& fs_permission) { + return S_IRUSR | S_IWUSR | S_IRGRP | (fs_permission.isOtherReadable ? S_IROTH : 0) | + (fs_permission.isOtherExecutable ? S_IXOTH : 0); +} + +mode_t DirFsPermissionToMode(const FsPermission& fs_permission) { + return FileFsPermissionToMode(fs_permission) | S_IXUSR | S_IXGRP; +} + +Result<void> Chown(const std::string& path, const FsPermission& fs_permission) { + if (fs_permission.uid < 0 && fs_permission.gid < 0) { + // Keep the default owner. + } else if (fs_permission.uid < 0 || fs_permission.gid < 0) { + return Errorf("uid and gid must be both non-negative or both negative, got {} and {}.", + fs_permission.uid, + fs_permission.gid); + } + if (chown(path.c_str(), fs_permission.uid, fs_permission.gid) != 0) { + return ErrnoErrorf("Failed to chown '{}'", path); + } + return {}; +} + +} // namespace artd +} // namespace art diff --git a/artd/file_utils.h b/artd/file_utils.h new file mode 100644 index 0000000000..b5fd170676 --- /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 0000000000..8f79d5dce9 --- /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 0000000000..d504bb2ad0 --- /dev/null +++ b/artd/path_utils.cc @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "path_utils.h" + +#include <filesystem> +#include <string> +#include <vector> + +#include "aidl/com/android/server/art/BnArtd.h" +#include "android-base/errors.h" +#include "android-base/result.h" +#include "android-base/strings.h" +#include "arch/instruction_set.h" +#include "base/file_utils.h" +#include "base/macros.h" +#include "file_utils.h" +#include "oat_file_assistant.h" +#include "tools/tools.h" + +namespace art { +namespace artd { + +namespace { + +using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::ProfilePath; +using ::aidl::com::android::server::art::VdexPath; +using ::android::base::Error; +using ::android::base::Result; + +using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath; +using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; +using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath; +using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath; +using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath; +using TmpProfilePath = ProfilePath::TmpProfilePath; +using WritableProfilePath = ProfilePath::WritableProfilePath; + +Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) { + if (path_str.empty()) { + return Errorf("Path is empty"); + } + if (path_str.find('\0') != std::string::npos) { + return Errorf("Path '{}' has invalid character '\\0'", path_str); + } + std::filesystem::path path(path_str); + if (!path.is_absolute()) { + return Errorf("Path '{}' is not an absolute path", path_str); + } + if (path.lexically_normal() != path_str) { + return Errorf("Path '{}' is not in normal form", path_str); + } + return {}; +} + +Result<void> ValidatePathElementSubstring(const std::string& path_element_substring, + const std::string& name) { + if (path_element_substring.empty()) { + return Errorf("{} is empty", name); + } + if (path_element_substring.find('/') != std::string::npos) { + return Errorf("{} '{}' has invalid character '/'", name, path_element_substring); + } + if (path_element_substring.find('\0') != std::string::npos) { + return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring); + } + return {}; +} + +Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) { + OR_RETURN(ValidatePathElementSubstring(path_element, name)); + if (path_element == "." || path_element == "..") { + return Errorf("Invalid {} '{}'", name, path_element); + } + return {}; +} + +Result<std::string> GetAndroidDataOrError() { + std::string error_msg; + std::string result = GetAndroidDataSafe(&error_msg); + if (!error_msg.empty()) { + return Error() << error_msg; + } + return result; +} + +Result<std::string> GetAndroidExpandOrError() { + std::string error_msg; + std::string result = GetAndroidExpandSafe(&error_msg); + if (!error_msg.empty()) { + return Error() << error_msg; + } + return result; +} + +Result<std::string> GetArtRootOrError() { + std::string error_msg; + std::string result = GetArtRootSafe(&error_msg); + if (!error_msg.empty()) { + return Error() << error_msg; + } + return result; +} + +} // namespace + +Result<std::vector<std::string>> ListManagedFiles() { + std::string android_data = OR_RETURN(GetAndroidDataOrError()); + std::string android_expand = OR_RETURN(GetAndroidExpandOrError()); + + // See `art::tools::Glob` for the syntax. + std::vector<std::string> patterns = { + // Profiles for primary dex files. + android_data + "/misc/profiles/**", + // Artifacts for primary dex files. + android_data + "/dalvik-cache/**", + }; + + for (const std::string& data_root : {android_data, android_expand + "/*"}) { + // Artifacts for primary dex files. + patterns.push_back(data_root + "/app/*/*/oat/**"); + // Profiles and artifacts for secondary dex files. Those files are in app data directories, so + // we use more granular patterns to avoid accidentally deleting apps' files. + for (const char* user_dir : {"/user", "/user_de"}) { + std::string secondary_oat_dir = data_root + user_dir + "/*/*/**/oat"; + for (const char* maybe_tmp_suffix : {"", ".*.tmp"}) { + patterns.push_back(secondary_oat_dir + "/*.prof" + maybe_tmp_suffix); + patterns.push_back(secondary_oat_dir + "/*/*.odex" + maybe_tmp_suffix); + patterns.push_back(secondary_oat_dir + "/*/*.vdex" + maybe_tmp_suffix); + patterns.push_back(secondary_oat_dir + "/*/*.art" + maybe_tmp_suffix); + } + } + } + + return tools::Glob(patterns); +} + +Result<void> ValidateDexPath(const std::string& dex_path) { + OR_RETURN(ValidateAbsoluteNormalPath(dex_path)); + return {}; +} + +Result<std::string> BuildArtBinPath(const std::string& binary_name) { + return ART_FORMAT("{}/bin/{}", OR_RETURN(GetArtRootOrError()), binary_name); +} + +Result<std::string> BuildOatPath(const ArtifactsPath& artifacts_path) { + OR_RETURN(ValidateDexPath(artifacts_path.dexPath)); + + InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str()); + if (isa == InstructionSet::kNone) { + return Errorf("Instruction set '{}' is invalid", artifacts_path.isa); + } + + std::string error_msg; + std::string path; + if (artifacts_path.isInDalvikCache) { + // Apps' OAT files are never in ART APEX data. + if (!OatFileAssistant::DexLocationToOatFilename( + artifacts_path.dexPath, isa, /*deny_art_apex_data_files=*/true, &path, &error_msg)) { + return Error() << error_msg; + } + return path; + } else { + if (!OatFileAssistant::DexLocationToOdexFilename( + artifacts_path.dexPath, isa, &path, &error_msg)) { + return Error() << error_msg; + } + return path; + } +} + +Result<std::string> BuildPrimaryRefProfilePath( + const PrimaryRefProfilePath& primary_ref_profile_path) { + OR_RETURN(ValidatePathElement(primary_ref_profile_path.packageName, "packageName")); + OR_RETURN(ValidatePathElementSubstring(primary_ref_profile_path.profileName, "profileName")); + return ART_FORMAT("{}/misc/profiles/ref/{}/{}.prof", + OR_RETURN(GetAndroidDataOrError()), + primary_ref_profile_path.packageName, + primary_ref_profile_path.profileName); +} + +Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) { + OR_RETURN(ValidateDexPath(prebuilt_profile_path.dexPath)); + return prebuilt_profile_path.dexPath + ".prof"; +} + +Result<std::string> BuildPrimaryCurProfilePath( + const PrimaryCurProfilePath& primary_cur_profile_path) { + OR_RETURN(ValidatePathElement(primary_cur_profile_path.packageName, "packageName")); + OR_RETURN(ValidatePathElementSubstring(primary_cur_profile_path.profileName, "profileName")); + return ART_FORMAT("{}/misc/profiles/cur/{}/{}/{}.prof", + OR_RETURN(GetAndroidDataOrError()), + primary_cur_profile_path.userId, + primary_cur_profile_path.packageName, + primary_cur_profile_path.profileName); +} + +Result<std::string> BuildSecondaryRefProfilePath( + const SecondaryRefProfilePath& secondary_ref_profile_path) { + OR_RETURN(ValidateDexPath(secondary_ref_profile_path.dexPath)); + std::filesystem::path dex_path(secondary_ref_profile_path.dexPath); + return ART_FORMAT( + "{}/oat/{}.prof", dex_path.parent_path().string(), dex_path.filename().string()); +} + +Result<std::string> BuildSecondaryCurProfilePath( + const SecondaryCurProfilePath& secondary_cur_profile_path) { + OR_RETURN(ValidateDexPath(secondary_cur_profile_path.dexPath)); + std::filesystem::path dex_path(secondary_cur_profile_path.dexPath); + return ART_FORMAT( + "{}/oat/{}.cur.prof", dex_path.parent_path().string(), dex_path.filename().string()); +} + +Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) { + const WritableProfilePath& final_path = tmp_profile_path.finalPath; + switch (final_path.getTag()) { + case WritableProfilePath::forPrimary: + return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>()); + case WritableProfilePath::forSecondary: + return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>()); + // No default. All cases should be explicitly handled, or the compilation will fail. + } + // This should never happen. Just in case we get a non-enumerator value. + LOG(FATAL) << ART_FORMAT("Unexpected writable profile path type {}", final_path.getTag()); +} + +Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) { + OR_RETURN(ValidatePathElementSubstring(tmp_profile_path.id, "id")); + return NewFile::BuildTempPath(OR_RETURN(BuildFinalProfilePath(tmp_profile_path)), + tmp_profile_path.id); +} + +Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) { + OR_RETURN(ValidateDexPath(dex_metadata_path.dexPath)); + return ReplaceFileExtension(dex_metadata_path.dexPath, "dm"); +} + +Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) { + switch (profile_path.getTag()) { + case ProfilePath::primaryRefProfilePath: + return BuildPrimaryRefProfilePath(profile_path.get<ProfilePath::primaryRefProfilePath>()); + case ProfilePath::prebuiltProfilePath: + return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>()); + case ProfilePath::primaryCurProfilePath: + return BuildPrimaryCurProfilePath(profile_path.get<ProfilePath::primaryCurProfilePath>()); + case ProfilePath::secondaryRefProfilePath: + return BuildSecondaryRefProfilePath(profile_path.get<ProfilePath::secondaryRefProfilePath>()); + case ProfilePath::secondaryCurProfilePath: + return BuildSecondaryCurProfilePath(profile_path.get<ProfilePath::secondaryCurProfilePath>()); + case ProfilePath::tmpProfilePath: + return BuildTmpProfilePath(profile_path.get<ProfilePath::tmpProfilePath>()); + case ProfilePath::dexMetadataPath: + return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>()); + // No default. All cases should be explicitly handled, or the compilation will fail. + } + // This should never happen. Just in case we get a non-enumerator value. + LOG(FATAL) << ART_FORMAT("Unexpected profile path type {}", profile_path.getTag()); +} + +Result<std::string> BuildVdexPath(const VdexPath& vdex_path) { + DCHECK(vdex_path.getTag() == VdexPath::artifactsPath); + return OatPathToVdexPath(OR_RETURN(BuildOatPath(vdex_path.get<VdexPath::artifactsPath>()))); +} + +} // namespace artd +} // namespace art diff --git a/artd/path_utils.h b/artd/path_utils.h new file mode 100644 index 0000000000..1063f9118e --- /dev/null +++ b/artd/path_utils.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_ARTD_PATH_UTILS_H_ +#define ART_ARTD_PATH_UTILS_H_ + +#include <string> +#include <vector> + +#include "aidl/com/android/server/art/BnArtd.h" +#include "android-base/result.h" +#include "base/file_utils.h" + +namespace art { +namespace artd { + +// Returns all existing files that are managed by artd. +android::base::Result<std::vector<std::string>> ListManagedFiles(); + +android::base::Result<void> ValidateDexPath(const std::string& dex_path); + +android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name); + +// Returns the absolute path to the OAT file built from the `ArtifactsPath`. +android::base::Result<std::string> BuildOatPath( + const aidl::com::android::server::art::ArtifactsPath& artifacts_path); + +// Returns the path to the VDEX file that corresponds to the OAT file. +inline std::string OatPathToVdexPath(const std::string& oat_path) { + return ReplaceFileExtension(oat_path, "vdex"); +} + +// Returns the path to the ART file that corresponds to the OAT file. +inline std::string OatPathToArtPath(const std::string& oat_path) { + return ReplaceFileExtension(oat_path, "art"); +} + +android::base::Result<std::string> BuildPrimaryRefProfilePath( + const aidl::com::android::server::art::ProfilePath::PrimaryRefProfilePath& + primary_ref_profile_path); + +android::base::Result<std::string> BuildPrebuiltProfilePath( + const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path); + +android::base::Result<std::string> BuildPrimaryCurProfilePath( + const aidl::com::android::server::art::ProfilePath::PrimaryCurProfilePath& + primary_cur_profile_path); + +android::base::Result<std::string> BuildSecondaryRefProfilePath( + const aidl::com::android::server::art::ProfilePath::SecondaryRefProfilePath& + secondary_ref_profile_path); + +android::base::Result<std::string> BuildSecondaryCurProfilePath( + const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath& + secondary_cur_profile_path); + +android::base::Result<std::string> BuildFinalProfilePath( + const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path); + +android::base::Result<std::string> BuildTmpProfilePath( + const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path); + +android::base::Result<std::string> BuildDexMetadataPath( + const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path); + +android::base::Result<std::string> BuildProfileOrDmPath( + const aidl::com::android::server::art::ProfilePath& profile_path); + +android::base::Result<std::string> BuildVdexPath( + const aidl::com::android::server::art::VdexPath& vdex_path); + +} // namespace artd +} // namespace art + +#endif // ART_ARTD_PATH_UTILS_H_ diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc new file mode 100644 index 0000000000..77652f06e1 --- /dev/null +++ b/artd/path_utils_test.cc @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "path_utils.h" + +#include "aidl/com/android/server/art/BnArtd.h" +#include "android-base/result-gmock.h" +#include "base/common_art_test.h" +#include "gtest/gtest.h" + +namespace art { +namespace artd { +namespace { + +using ::aidl::com::android::server::art::ArtifactsPath; +using ::aidl::com::android::server::art::DexMetadataPath; +using ::aidl::com::android::server::art::ProfilePath; +using ::android::base::testing::HasError; +using ::android::base::testing::HasValue; +using ::android::base::testing::WithMessage; + +using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath; +using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; +using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath; +using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath; +using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath; +using TmpProfilePath = ProfilePath::TmpProfilePath; + +using std::literals::operator""s; // NOLINT + +class PathUtilsTest : public CommonArtTest {}; + +TEST_F(PathUtilsTest, BuildArtBinPath) { + auto scratch_dir = std::make_unique<ScratchDir>(); + auto art_root_env = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT"); + setenv("ANDROID_ART_ROOT", scratch_dir->GetPath().c_str(), /*overwrite=*/1); + EXPECT_THAT(BuildArtBinPath("foo"), HasValue(scratch_dir->GetPath() + "/bin/foo")); +} + +TEST_F(PathUtilsTest, BuildOatPath) { + EXPECT_THAT( + BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasValue("/a/oat/arm64/b.odex")); +} + +TEST_F(PathUtilsTest, BuildOatPathDalvikCache) { + EXPECT_THAT( + BuildOatPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = true}), + HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.dex")); +} + +TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) { + EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}), + HasError(WithMessage("Path is empty"))); +} + +TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) { + EXPECT_THAT( + BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasError(WithMessage("Path 'a/b.apk' is not an absolute path"))); +} + +TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) { + EXPECT_THAT(BuildOatPath(ArtifactsPath{ + .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form"))); +} + +TEST_F(PathUtilsTest, BuildOatPathNul) { + EXPECT_THAT(BuildOatPath(ArtifactsPath{ + .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}), + HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s))); +} + +TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) { + EXPECT_THAT(BuildOatPath( + ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}), + HasError(WithMessage("Instruction set 'invalid' is invalid"))); +} + +TEST_F(PathUtilsTest, OatPathToVdexPath) { + EXPECT_EQ(OatPathToVdexPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.vdex"); +} + +TEST_F(PathUtilsTest, OatPathToArtPath) { + EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art"); +} + +TEST_F(PathUtilsTest, BuildPrimaryRefProfilePath) { + EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof")); +} + +TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) { + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof")); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof")); +} + +TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) { + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}), + HasError(WithMessage("packageName is empty"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}), + HasError(WithMessage("Invalid packageName '.'"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}), + HasError(WithMessage("Invalid packageName '..'"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}), + HasError(WithMessage("packageName 'a/b' has invalid character '/'"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}), + HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s))); +} + +TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) { + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof")); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof")); + EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "!@#$%^&*()_+-="}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof")); +} + +TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) { + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}), + HasError(WithMessage("profileName is empty"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}), + HasError(WithMessage("profileName 'a/b' has invalid character '/'"))); + EXPECT_THAT(BuildPrimaryRefProfilePath( + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}), + HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s))); +} + +TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) { + EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{ + .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}, + .id = "12345"}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof")); +} + +TEST_F(PathUtilsTest, BuildFinalProfilePathForSecondary) { + EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{ + .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ + + "/user/0/com.android.foo/a.apk"}, + .id = "12345"}), + HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof")); +} + +TEST_F(PathUtilsTest, BuildTmpProfilePathForPrimary) { + EXPECT_THAT( + BuildTmpProfilePath(TmpProfilePath{ + .finalPath = + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}, + .id = "12345"}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp")); +} + +TEST_F(PathUtilsTest, BuildTmpProfilePathForSecondary) { + EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ + .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ + + "/user/0/com.android.foo/a.apk"}, + .id = "12345"}), + HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof.12345.tmp")); +} + +TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) { + EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ + .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}, + .id = ""}), + HasError(WithMessage("id is empty"))); + EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ + .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}, + .id = "123/45"}), + HasError(WithMessage("id '123/45' has invalid character '/'"))); + EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ + .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}, + .id = "123\0a"s}), + HasError(WithMessage("id '123\0a' has invalid character '\\0'"s))); +} + +TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) { + EXPECT_THAT(BuildPrebuiltProfilePath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}), + HasValue("/a/b.apk.prof")); +} + +TEST_F(PathUtilsTest, BuildPrimaryCurProfilePath) { + EXPECT_THAT(BuildPrimaryCurProfilePath(PrimaryCurProfilePath{ + .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof")); +} + +TEST_F(PathUtilsTest, BuildSecondaryRefProfilePath) { + EXPECT_THAT(BuildSecondaryRefProfilePath(SecondaryRefProfilePath{ + .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}), + HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof")); +} + +TEST_F(PathUtilsTest, BuildSecondaryCurProfilePath) { + EXPECT_THAT(BuildSecondaryCurProfilePath(SecondaryCurProfilePath{ + .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}), + HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.cur.prof")); +} + +TEST_F(PathUtilsTest, BuildDexMetadataPath) { + EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm")); +} + +TEST_F(PathUtilsTest, BuildProfilePath) { + EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo", + .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof")); + EXPECT_THAT( + BuildProfileOrDmPath(TmpProfilePath{ + .finalPath = + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}, + .id = "12345"}), + HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp")); + EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}), + HasValue("/a/b.apk.prof")); + EXPECT_THAT(BuildProfileOrDmPath(PrimaryCurProfilePath{ + .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}), + HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof")); + EXPECT_THAT(BuildProfileOrDmPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm")); +} + +TEST_F(PathUtilsTest, BuildVdexPath) { + EXPECT_THAT( + BuildVdexPath(ArtifactsPath{.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasValue("/a/oat/arm64/b.vdex")); +} + +} // namespace +} // namespace artd +} // namespace art diff --git a/artd/testing.h b/artd/testing.h new file mode 100644 index 0000000000..df01a9a814 --- /dev/null +++ b/artd/testing.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_ARTD_TESTING_H_ +#define ART_ARTD_TESTING_H_ + +// Returns the value of the given `android::base::Result`, or reports the error as a gMock matcher +// mismatch. This is only to be used in a gMock matcher. +#define OR_MISMATCH(expr) \ + ({ \ + decltype(expr)&& tmp__ = (expr); \ + if (!tmp__.ok()) { \ + *result_listener << tmp__.error().message(); \ + return false; \ + } \ + std::move(tmp__).value(); \ + }) + +// Returns the value of the given `android::base::Result`, or fails the GoogleTest. +#define OR_FAIL(expr) \ + ({ \ + decltype(expr)&& tmp__ = (expr); \ + ASSERT_TRUE(tmp__.ok()) << tmp__.error().message(); \ + std::move(tmp__).value(); \ + }) + +#endif // ART_ARTD_TESTING_H_ diff --git a/build/apex/Android.bp b/build/apex/Android.bp index 15d324fca5..46028169fa 100644 --- a/build/apex/Android.bp +++ b/build/apex/Android.bp @@ -250,6 +250,7 @@ apex_defaults { ], binaries: [ "art_boot", + "art_exec", "artd", ], multilib: { diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py index 8eb5d13855..3887b50084 100755 --- a/build/apex/art_apex_test.py +++ b/build/apex/art_apex_test.py @@ -551,6 +551,7 @@ class ReleaseTargetChecker: # Check binaries for ART. self._checker.check_executable('art_boot') + self._checker.check_executable('art_exec') self._checker.check_executable('artd') self._checker.check_executable('oatdump') self._checker.check_executable("odrefresh") diff --git a/build/boot/boot-image-profile.txt b/build/boot/boot-image-profile.txt index ca2f8f2cc6..a2828fe13d 100644 --- a/build/boot/boot-image-profile.txt +++ b/build/boot/boot-image-profile.txt @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +HSPLandroid/compat/Compatibility$BehaviorChangeDelegate;->isChangeEnabled(J)Z HSPLandroid/compat/Compatibility;->isChangeEnabled(J)Z HSPLandroid/compat/Compatibility;->setBehaviorChangeDelegate(Landroid/compat/Compatibility$BehaviorChangeDelegate;)V HSPLandroid/system/ErrnoException;-><init>(Ljava/lang/String;I)V @@ -34,7 +35,7 @@ HSPLandroid/system/Os;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructSta HSPLandroid/system/Os;->getpeername(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress; HSPLandroid/system/Os;->getpgid(I)I HSPLandroid/system/Os;->getpid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; -HSPLandroid/system/Os;->gettid()I +HSPLandroid/system/Os;->gettid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; HSPLandroid/system/Os;->getuid()I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; HSPLandroid/system/Os;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B HSPLandroid/system/Os;->ioctlInt(Ljava/io/FileDescriptor;I)I @@ -140,12 +141,12 @@ HSPLcom/android/okhttp/Headers$Builder;->addLenient(Ljava/lang/String;Ljava/lang HSPLcom/android/okhttp/Headers$Builder;->build()Lcom/android/okhttp/Headers; HSPLcom/android/okhttp/Headers$Builder;->checkNameAndValue(Ljava/lang/String;Ljava/lang/String;)V HSPLcom/android/okhttp/Headers$Builder;->get(Ljava/lang/String;)Ljava/lang/String; -HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder; +HSPLcom/android/okhttp/Headers$Builder;->removeAll(Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/List;Ljava/util/ArrayList; HSPLcom/android/okhttp/Headers$Builder;->set(Ljava/lang/String;Ljava/lang/String;)Lcom/android/okhttp/Headers$Builder; HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;)V HSPLcom/android/okhttp/Headers;-><init>(Lcom/android/okhttp/Headers$Builder;Lcom/android/okhttp/Headers$1;)V HSPLcom/android/okhttp/Headers;->get(Ljava/lang/String;)Ljava/lang/String; -HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +HSPLcom/android/okhttp/Headers;->get([Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; HSPLcom/android/okhttp/Headers;->name(I)Ljava/lang/String; HSPLcom/android/okhttp/Headers;->newBuilder()Lcom/android/okhttp/Headers$Builder; HSPLcom/android/okhttp/Headers;->size()I @@ -181,7 +182,7 @@ HSPLcom/android/okhttp/HttpUrl$Builder;->toString()Ljava/lang/String; HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;)V HSPLcom/android/okhttp/HttpUrl;-><init>(Lcom/android/okhttp/HttpUrl$Builder;Lcom/android/okhttp/HttpUrl$1;)V HSPLcom/android/okhttp/HttpUrl;->access$200(Ljava/lang/String;IILjava/lang/String;)I -HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String; +HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;IILjava/lang/String;ZZZZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; HSPLcom/android/okhttp/HttpUrl;->canonicalize(Ljava/lang/String;Ljava/lang/String;ZZZZ)Ljava/lang/String; HSPLcom/android/okhttp/HttpUrl;->decodeHexDigit(C)I HSPLcom/android/okhttp/HttpUrl;->defaultPort(Ljava/lang/String;)I @@ -201,7 +202,7 @@ HSPLcom/android/okhttp/HttpUrl;->namesAndValuesToQueryString(Ljava/lang/StringBu HSPLcom/android/okhttp/HttpUrl;->newBuilder()Lcom/android/okhttp/HttpUrl$Builder; HSPLcom/android/okhttp/HttpUrl;->pathSegmentsToString(Ljava/lang/StringBuilder;Ljava/util/List;)V HSPLcom/android/okhttp/HttpUrl;->percentDecode(Lcom/android/okhttp/okio/Buffer;Ljava/lang/String;IIZ)V -HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String; +HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;IIZ)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/lang/String;Z)Ljava/lang/String; HSPLcom/android/okhttp/HttpUrl;->percentDecode(Ljava/util/List;Z)Ljava/util/List; HSPLcom/android/okhttp/HttpUrl;->port()I @@ -470,8 +471,8 @@ HSPLcom/android/okhttp/internal/http/HttpEngine;->unzip(Lcom/android/okhttp/Resp HSPLcom/android/okhttp/internal/http/HttpEngine;->writingRequestHeaders()V HSPLcom/android/okhttp/internal/http/HttpMethod;->permitsRequestBody(Ljava/lang/String;)Z HSPLcom/android/okhttp/internal/http/HttpMethod;->requiresRequestBody(Ljava/lang/String;)Z -HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I -HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I +HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Lcom/android/okhttp/internal/http/OkHeaders$1;Lcom/android/okhttp/internal/http/OkHeaders$1; +HSPLcom/android/okhttp/internal/http/OkHeaders$1;->compare(Ljava/lang/String;Ljava/lang/String;)I+]Ljava/util/Comparator;Ljava/lang/String$CaseInsensitiveComparator; HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Headers;)J HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Request;)J HSPLcom/android/okhttp/internal/http/OkHeaders;->contentLength(Lcom/android/okhttp/Response;)J @@ -681,17 +682,17 @@ HSPLcom/android/okhttp/okio/Buffer;->readIntLe()I HSPLcom/android/okhttp/okio/Buffer;->readShort()S HSPLcom/android/okhttp/okio/Buffer;->readString(JLjava/nio/charset/Charset;)Ljava/lang/String; HSPLcom/android/okhttp/okio/Buffer;->readUtf8()Ljava/lang/String; -HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String; -HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String; +HSPLcom/android/okhttp/okio/Buffer;->readUtf8(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer; +HSPLcom/android/okhttp/okio/Buffer;->readUtf8Line(J)Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/Buffer;->size()J -HSPLcom/android/okhttp/okio/Buffer;->skip(J)V +HSPLcom/android/okhttp/okio/Buffer;->skip(J)V+]Lcom/android/okhttp/okio/Segment;Lcom/android/okhttp/okio/Segment; HSPLcom/android/okhttp/okio/Buffer;->writableSegment(I)Lcom/android/okhttp/okio/Segment; HSPLcom/android/okhttp/okio/Buffer;->write(Lcom/android/okhttp/okio/Buffer;J)V HSPLcom/android/okhttp/okio/Buffer;->write([BII)Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/Buffer;->writeByte(I)Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/Buffer;->writeHexadecimalUnsignedLong(J)Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;)Lcom/android/okhttp/okio/Buffer; -HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer; +HSPLcom/android/okhttp/okio/Buffer;->writeUtf8(Ljava/lang/String;II)Lcom/android/okhttp/okio/Buffer;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/Buffer;->writeUtf8CodePoint(I)Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/ByteString;-><init>([B)V HSPLcom/android/okhttp/okio/ByteString;->hex()Ljava/lang/String; @@ -756,15 +757,15 @@ HSPLcom/android/okhttp/okio/RealBufferedSource;->access$000(Lcom/android/okhttp/ HSPLcom/android/okhttp/okio/RealBufferedSource;->buffer()Lcom/android/okhttp/okio/Buffer; HSPLcom/android/okhttp/okio/RealBufferedSource;->close()V HSPLcom/android/okhttp/okio/RealBufferedSource;->exhausted()Z -HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J -HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J +HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(B)J+]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource; +HSPLcom/android/okhttp/okio/RealBufferedSource;->indexOf(BJ)J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2; HSPLcom/android/okhttp/okio/RealBufferedSource;->inputStream()Ljava/io/InputStream; HSPLcom/android/okhttp/okio/RealBufferedSource;->read(Lcom/android/okhttp/okio/Buffer;J)J -HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J +HSPLcom/android/okhttp/okio/RealBufferedSource;->readHexadecimalUnsignedLong()J+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource; HSPLcom/android/okhttp/okio/RealBufferedSource;->readIntLe()I HSPLcom/android/okhttp/okio/RealBufferedSource;->readShort()S -HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String; -HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z +HSPLcom/android/okhttp/okio/RealBufferedSource;->readUtf8LineStrict()Ljava/lang/String;+]Lcom/android/okhttp/okio/Buffer;Lcom/android/okhttp/okio/Buffer;]Lcom/android/okhttp/okio/RealBufferedSource;Lcom/android/okhttp/okio/RealBufferedSource; +HSPLcom/android/okhttp/okio/RealBufferedSource;->request(J)Z+]Lcom/android/okhttp/okio/Source;Lcom/android/okhttp/okio/AsyncTimeout$2; HSPLcom/android/okhttp/okio/RealBufferedSource;->require(J)V HSPLcom/android/okhttp/okio/RealBufferedSource;->skip(J)V HSPLcom/android/okhttp/okio/RealBufferedSource;->timeout()Lcom/android/okhttp/okio/Timeout; @@ -805,10 +806,10 @@ HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->buildObject(III)Lcom/and HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->createPrimitiveDERObject(ILcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)Lcom/android/org/bouncycastle/asn1/ASN1Primitive; HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->getBuffer(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;[[B)[B HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength()I -HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I+]Ljava/io/InputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;,Lcom/android/org/bouncycastle/asn1/ASN1InputStream; +HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readLength(Ljava/io/InputStream;IZ)I HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readObject()Lcom/android/org/bouncycastle/asn1/ASN1Primitive; HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readTagNumber(Ljava/io/InputStream;I)I -HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;]Lcom/android/org/bouncycastle/asn1/ASN1InputStream;Lcom/android/org/bouncycastle/asn1/ASN1InputStream;]Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector;Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector; +HSPLcom/android/org/bouncycastle/asn1/ASN1InputStream;->readVector(Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;)Lcom/android/org/bouncycastle/asn1/ASN1EncodableVector; HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>(Ljava/math/BigInteger;)V HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;-><init>([BZ)V HSPLcom/android/org/bouncycastle/asn1/ASN1Integer;->encode(Lcom/android/org/bouncycastle/asn1/ASN1OutputStream;Z)V @@ -880,7 +881,7 @@ HSPLcom/android/org/bouncycastle/asn1/DLSequence;-><init>(Lcom/android/org/bounc HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->getRemaining()I HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read()I HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->read([BII)I -HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V+]Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;Lcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream; +HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->readAllIntoByteArray([B)V HSPLcom/android/org/bouncycastle/asn1/DefiniteLengthInputStream;->toByteArray()[B HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;-><init>(Ljava/io/InputStream;I)V HSPLcom/android/org/bouncycastle/asn1/LimitedInputStream;->getLimit()I @@ -932,10 +933,10 @@ HSPLcom/android/org/bouncycastle/crypto/engines/DESEngine;-><init>()V HSPLcom/android/org/bouncycastle/crypto/engines/DESEngine;->generateWorkingKey(Z[B)[I HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->adjust([BI[B)V -HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B+]Lcom/android/org/bouncycastle/crypto/Digest;Lcom/android/org/bouncycastle/crypto/digests/OpenSSLDigest$SHA1;,Lcom/android/org/bouncycastle/crypto/digests/SHA1Digest; +HSPLcom/android/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator;->generateDerivedKey(II)[B HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;-><init>(Lcom/android/org/bouncycastle/crypto/Digest;)V -HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac; -HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B+]Lcom/android/org/bouncycastle/crypto/Mac;Lcom/android/org/bouncycastle/crypto/macs/HMac; +HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->F([BI[B[BI)V +HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedKey(I)[B HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedMacParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters; HSPLcom/android/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator;->generateDerivedParameters(I)Lcom/android/org/bouncycastle/crypto/CipherParameters; HSPLcom/android/org/bouncycastle/crypto/macs/HMac;-><clinit>()V @@ -960,7 +961,7 @@ HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->doF HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getOutputSize(I)I HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->getUpdateOutputSize(I)I HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->init(ZLcom/android/org/bouncycastle/crypto/CipherParameters;)V -HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I+]Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;Lcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;]Lcom/android/org/bouncycastle/crypto/BlockCipher;Lcom/android/org/bouncycastle/crypto/modes/CBCBlockCipher;,Lcom/android/org/bouncycastle/crypto/engines/AESEngine; +HSPLcom/android/org/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher;->processBytes([BII[BI)I HSPLcom/android/org/bouncycastle/crypto/params/AsymmetricKeyParameter;-><init>(Z)V HSPLcom/android/org/bouncycastle/crypto/params/DSAKeyParameters;-><init>(ZLcom/android/org/bouncycastle/crypto/params/DSAParameters;)V HSPLcom/android/org/bouncycastle/crypto/params/DSAParameters;-><init>(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)V @@ -1033,10 +1034,10 @@ HSPLcom/android/org/bouncycastle/util/Strings;->toUpperCase(Ljava/lang/String;)L HSPLcom/android/org/bouncycastle/util/io/Streams;->readFully(Ljava/io/InputStream;[B)I HSPLcom/android/org/bouncycastle/util/io/Streams;->readFully(Ljava/io/InputStream;[BII)I HSPLcom/android/org/kxml2/io/KXmlParser;-><init>()V -HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z +HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z+]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Ljava/lang/String;Ljava/lang/String; HSPLcom/android/org/kxml2/io/KXmlParser;->close()V HSPLcom/android/org/kxml2/io/KXmlParser;->ensureCapacity([Ljava/lang/String;I)[Ljava/lang/String; -HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z +HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z+]Ljava/io/Reader;Ljava/io/InputStreamReader; HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeCount()I HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String; HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String; @@ -1062,10 +1063,10 @@ HSPLcom/android/org/kxml2/io/KXmlParser;->read(C)V HSPLcom/android/org/kxml2/io/KXmlParser;->read([C)V HSPLcom/android/org/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String; HSPLcom/android/org/kxml2/io/KXmlParser;->readEndTag()V -HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V -HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Map;Ljava/util/HashMap; +HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool; HSPLcom/android/org/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String; -HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String;+]Llibcore/internal/StringPool;Llibcore/internal/StringPool; +HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/internal/StringPool;Llibcore/internal/StringPool; HSPLcom/android/org/kxml2/io/KXmlParser;->readXmlDeclaration()V HSPLcom/android/org/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V HSPLcom/android/org/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V @@ -1111,13 +1112,13 @@ HSPLdalvik/system/BlockGuard$3;->initialValue()Ldalvik/system/BlockGuard$Policy; HSPLdalvik/system/BlockGuard$3;->initialValue()Ljava/lang/Object; HSPLdalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy;+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3; HSPLdalvik/system/BlockGuard;->getVmPolicy()Ldalvik/system/BlockGuard$VmPolicy; -HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V +HSPLdalvik/system/BlockGuard;->setThreadPolicy(Ldalvik/system/BlockGuard$Policy;)V+]Ljava/lang/ThreadLocal;Ldalvik/system/BlockGuard$3; HSPLdalvik/system/BlockGuard;->setVmPolicy(Ldalvik/system/BlockGuard$VmPolicy;)V HSPLdalvik/system/CloseGuard;-><init>()V HSPLdalvik/system/CloseGuard;->close()V HSPLdalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard; HSPLdalvik/system/CloseGuard;->getReporter()Ldalvik/system/CloseGuard$Reporter; -HSPLdalvik/system/CloseGuard;->open(Ljava/lang/String;)V +HSPLdalvik/system/CloseGuard;->open(Ljava/lang/String;)V+]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard; HSPLdalvik/system/CloseGuard;->openWithCallSite(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLdalvik/system/CloseGuard;->setEnabled(Z)V HSPLdalvik/system/CloseGuard;->setReporter(Ldalvik/system/CloseGuard$Reporter;)V @@ -1173,8 +1174,10 @@ HSPLdalvik/system/SocketTagger;->get()Ldalvik/system/SocketTagger; HSPLdalvik/system/SocketTagger;->set(Ldalvik/system/SocketTagger;)V HSPLdalvik/system/SocketTagger;->tag(Ljava/net/Socket;)V HSPLdalvik/system/SocketTagger;->untag(Ljava/net/Socket;)V +HSPLdalvik/system/VMRuntime$SdkVersionContainer;->-$$Nest$sfgetsdkVersion()I HSPLdalvik/system/VMRuntime;->getInstructionSet(Ljava/lang/String;)Ljava/lang/String; HSPLdalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime; +HSPLdalvik/system/VMRuntime;->getSdkVersion()I HSPLdalvik/system/VMRuntime;->getTargetSdkVersion()I HSPLdalvik/system/VMRuntime;->hiddenApiUsed(ILjava/lang/String;Ljava/lang/String;IZ)V HSPLdalvik/system/VMRuntime;->notifyNativeAllocation()V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; @@ -1186,6 +1189,10 @@ HSPLdalvik/system/VMRuntime;->setDisabledCompatChanges([J)V HSPLdalvik/system/VMRuntime;->setHiddenApiUsageLogger(Ldalvik/system/VMRuntime$HiddenApiUsageLogger;)V HSPLdalvik/system/VMRuntime;->setNonSdkApiUsageConsumer(Ljava/util/function/Consumer;)V HSPLdalvik/system/VMRuntime;->setTargetSdkVersion(I)V +HSPLdalvik/system/ZipPathValidator$Callback;->onZipEntryAccess(Ljava/lang/String;)V +HSPLdalvik/system/ZipPathValidator;->clearCallback()V +HSPLdalvik/system/ZipPathValidator;->getInstance()Ldalvik/system/ZipPathValidator$Callback; +HSPLdalvik/system/ZipPathValidator;->setCallback(Ldalvik/system/ZipPathValidator$Callback;)V HSPLdalvik/system/ZygoteHooks;->cleanLocaleCaches()V HSPLdalvik/system/ZygoteHooks;->gcAndFinalize()V HSPLdalvik/system/ZygoteHooks;->isIndefiniteThreadSuspensionSafe()Z @@ -1206,25 +1213,24 @@ HSPLjava/io/Bits;->putFloat([BIF)V HSPLjava/io/Bits;->putInt([BII)V HSPLjava/io/Bits;->putLong([BIJ)V HSPLjava/io/Bits;->putShort([BIS)V -HSPLjava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;)V HSPLjava/io/BufferedInputStream;-><init>(Ljava/io/InputStream;I)V HSPLjava/io/BufferedInputStream;->available()I HSPLjava/io/BufferedInputStream;->close()V -HSPLjava/io/BufferedInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/FileInputStream; +HSPLjava/io/BufferedInputStream;->fill()V HSPLjava/io/BufferedInputStream;->getBufIfOpen()[B HSPLjava/io/BufferedInputStream;->getInIfOpen()Ljava/io/InputStream; HSPLjava/io/BufferedInputStream;->mark(I)V HSPLjava/io/BufferedInputStream;->markSupported()Z HSPLjava/io/BufferedInputStream;->read()I -HSPLjava/io/BufferedInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream; +HSPLjava/io/BufferedInputStream;->read([BII)I HSPLjava/io/BufferedInputStream;->read1([BII)I HSPLjava/io/BufferedInputStream;->reset()V HSPLjava/io/BufferedInputStream;->skip(J)J HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;)V HSPLjava/io/BufferedOutputStream;-><init>(Ljava/io/OutputStream;I)V -HSPLjava/io/BufferedOutputStream;->flush()V -HSPLjava/io/BufferedOutputStream;->flushBuffer()V +HSPLjava/io/BufferedOutputStream;->flush()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream; +HSPLjava/io/BufferedOutputStream;->flushBuffer()V+]Ljava/io/OutputStream;Ljava/io/FileOutputStream; HSPLjava/io/BufferedOutputStream;->write(I)V HSPLjava/io/BufferedOutputStream;->write([BII)V HSPLjava/io/BufferedReader;-><init>(Ljava/io/Reader;)V @@ -1247,7 +1253,7 @@ HSPLjava/io/BufferedWriter;->min(II)I HSPLjava/io/BufferedWriter;->newLine()V HSPLjava/io/BufferedWriter;->write(I)V HSPLjava/io/BufferedWriter;->write(Ljava/lang/String;II)V -HSPLjava/io/BufferedWriter;->write([CII)V +HSPLjava/io/BufferedWriter;->write([CII)V+]Ljava/io/BufferedWriter;Ljava/io/BufferedWriter; HSPLjava/io/ByteArrayInputStream;-><init>([B)V HSPLjava/io/ByteArrayInputStream;-><init>([BII)V HSPLjava/io/ByteArrayInputStream;->available()I @@ -1285,23 +1291,23 @@ HSPLjava/io/DataInputStream;->read([BII)I HSPLjava/io/DataInputStream;->readBoolean()Z HSPLjava/io/DataInputStream;->readByte()B HSPLjava/io/DataInputStream;->readFully([B)V -HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/FileInputStream; +HSPLjava/io/DataInputStream;->readFully([BII)V+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/DataInputStream;->readInt()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream; HSPLjava/io/DataInputStream;->readLong()J HSPLjava/io/DataInputStream;->readShort()S HSPLjava/io/DataInputStream;->readUTF()Ljava/lang/String; -HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String;+]Ljava/io/DataInput;Ljava/io/DataInputStream; +HSPLjava/io/DataInputStream;->readUTF(Ljava/io/DataInput;)Ljava/lang/String; HSPLjava/io/DataInputStream;->readUnsignedByte()I -HSPLjava/io/DataInputStream;->readUnsignedShort()I+]Ljava/io/DataInputStream;Ljava/io/DataInputStream; +HSPLjava/io/DataInputStream;->readUnsignedShort()I HSPLjava/io/DataInputStream;->skipBytes(I)I HSPLjava/io/DataOutputStream;-><init>(Ljava/io/OutputStream;)V HSPLjava/io/DataOutputStream;->flush()V HSPLjava/io/DataOutputStream;->incCount(I)V HSPLjava/io/DataOutputStream;->write(I)V -HSPLjava/io/DataOutputStream;->write([BII)V +HSPLjava/io/DataOutputStream;->write([BII)V+]Ljava/io/OutputStream;missing_types HSPLjava/io/DataOutputStream;->writeBoolean(Z)V HSPLjava/io/DataOutputStream;->writeByte(I)V -HSPLjava/io/DataOutputStream;->writeInt(I)V +HSPLjava/io/DataOutputStream;->writeInt(I)V+]Ljava/io/OutputStream;missing_types HSPLjava/io/DataOutputStream;->writeLong(J)V HSPLjava/io/DataOutputStream;->writeShort(I)V HSPLjava/io/DataOutputStream;->writeUTF(Ljava/lang/String;)V @@ -1311,14 +1317,14 @@ HSPLjava/io/EOFException;-><init>(Ljava/lang/String;)V HSPLjava/io/ExpiringCache;->clear()V HSPLjava/io/File$TempDirectory;->generateFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File; HSPLjava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; -HSPLjava/io/File;-><init>(Ljava/lang/String;)V +HSPLjava/io/File;-><init>(Ljava/lang/String;)V+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; HSPLjava/io/File;-><init>(Ljava/lang/String;I)V HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/io/File;)V HSPLjava/io/File;-><init>(Ljava/lang/String;Ljava/lang/String;)V HSPLjava/io/File;->canExecute()Z HSPLjava/io/File;->canRead()Z HSPLjava/io/File;->canWrite()Z -HSPLjava/io/File;->compareTo(Ljava/io/File;)I +HSPLjava/io/File;->compareTo(Ljava/io/File;)I+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; HSPLjava/io/File;->compareTo(Ljava/lang/Object;)I HSPLjava/io/File;->createNewFile()Z HSPLjava/io/File;->createTempFile(Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Ljava/io/File; @@ -1331,19 +1337,19 @@ HSPLjava/io/File;->getCanonicalFile()Ljava/io/File; HSPLjava/io/File;->getCanonicalPath()Ljava/lang/String; HSPLjava/io/File;->getFreeSpace()J HSPLjava/io/File;->getName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; -HSPLjava/io/File;->getParent()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; -HSPLjava/io/File;->getParentFile()Ljava/io/File;+]Ljava/io/File;Ljava/io/File; +HSPLjava/io/File;->getParent()Ljava/lang/String; +HSPLjava/io/File;->getParentFile()Ljava/io/File; HSPLjava/io/File;->getPath()Ljava/lang/String; HSPLjava/io/File;->getPrefixLength()I HSPLjava/io/File;->getTotalSpace()J HSPLjava/io/File;->getUsableSpace()J -HSPLjava/io/File;->hashCode()I +HSPLjava/io/File;->hashCode()I+]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; HSPLjava/io/File;->isAbsolute()Z -HSPLjava/io/File;->isDirectory()Z+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; +HSPLjava/io/File;->isDirectory()Z HSPLjava/io/File;->isFile()Z HSPLjava/io/File;->isInvalid()Z HSPLjava/io/File;->lastModified()J -HSPLjava/io/File;->length()J+]Ljava/io/File;Ljava/io/File;]Ljava/io/FileSystem;Ljava/io/UnixFileSystem; +HSPLjava/io/File;->length()J HSPLjava/io/File;->list()[Ljava/lang/String; HSPLjava/io/File;->list(Ljava/io/FilenameFilter;)[Ljava/lang/String; HSPLjava/io/File;->listFiles()[Ljava/io/File; @@ -1373,7 +1379,7 @@ HSPLjava/io/FileDescriptor;->release$()Ljava/io/FileDescriptor; HSPLjava/io/FileDescriptor;->setInt$(I)V HSPLjava/io/FileDescriptor;->setOwnerId$(J)V HSPLjava/io/FileDescriptor;->valid()Z -HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V +HSPLjava/io/FileInputStream;-><init>(Ljava/io/File;)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard; HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;)V HSPLjava/io/FileInputStream;-><init>(Ljava/io/FileDescriptor;Z)V HSPLjava/io/FileInputStream;-><init>(Ljava/lang/String;)V @@ -1383,17 +1389,17 @@ HSPLjava/io/FileInputStream;->finalize()V HSPLjava/io/FileInputStream;->getChannel()Ljava/nio/channels/FileChannel; HSPLjava/io/FileInputStream;->getFD()Ljava/io/FileDescriptor; HSPLjava/io/FileInputStream;->read()I -HSPLjava/io/FileInputStream;->read([B)I +HSPLjava/io/FileInputStream;->read([B)I+]Ljava/io/FileInputStream;Ljava/io/FileInputStream; HSPLjava/io/FileInputStream;->read([BII)I+]Llibcore/io/IoTracker;Llibcore/io/IoTracker; HSPLjava/io/FileInputStream;->skip(J)J HSPLjava/io/FileNotFoundException;-><init>(Ljava/lang/String;)V HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;)V -HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard; +HSPLjava/io/FileOutputStream;-><init>(Ljava/io/File;Z)V HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;)V HSPLjava/io/FileOutputStream;-><init>(Ljava/io/FileDescriptor;Z)V HSPLjava/io/FileOutputStream;-><init>(Ljava/lang/String;)V HSPLjava/io/FileOutputStream;-><init>(Ljava/lang/String;Z)V -HSPLjava/io/FileOutputStream;->close()V+]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard; +HSPLjava/io/FileOutputStream;->close()V HSPLjava/io/FileOutputStream;->finalize()V HSPLjava/io/FileOutputStream;->getChannel()Ljava/nio/channels/FileChannel; HSPLjava/io/FileOutputStream;->getFD()Ljava/io/FileDescriptor; @@ -1409,7 +1415,7 @@ HSPLjava/io/FilterInputStream;->available()I HSPLjava/io/FilterInputStream;->close()V HSPLjava/io/FilterInputStream;->mark(I)V HSPLjava/io/FilterInputStream;->markSupported()Z -HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/PushbackInputStream;,Ljava/io/ByteArrayInputStream; +HSPLjava/io/FilterInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/BufferedInputStream;,Ljava/io/PushbackInputStream;,Ljava/io/ByteArrayInputStream;,Ljava/io/FileInputStream; HSPLjava/io/FilterInputStream;->read([B)I HSPLjava/io/FilterInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types HSPLjava/io/FilterInputStream;->reset()V @@ -1441,15 +1447,15 @@ HSPLjava/io/InputStreamReader;->ready()Z HSPLjava/io/InterruptedIOException;-><init>()V HSPLjava/io/InterruptedIOException;-><init>(Ljava/lang/String;)V HSPLjava/io/ObjectInputStream$BlockDataInputStream;-><init>(Ljava/io/ObjectInputStream;Ljava/io/InputStream;)V -HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; +HSPLjava/io/ObjectInputStream$BlockDataInputStream;->close()V HSPLjava/io/ObjectInputStream$BlockDataInputStream;->currentBlockRemaining()I HSPLjava/io/ObjectInputStream$BlockDataInputStream;->getBlockDataMode()Z HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peek()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; HSPLjava/io/ObjectInputStream$BlockDataInputStream;->peekByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read()I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BII)I -HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BIIZ)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; -HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; +HSPLjava/io/ObjectInputStream$BlockDataInputStream;->read([BIIZ)I +HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBlockHeader(Z)I HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readBoolean()Z HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readByte()B+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream$BlockDataInputStream;->readFloat()F @@ -1466,13 +1472,14 @@ HSPLjava/io/ObjectInputStream$BlockDataInputStream;->refill()V+]Ljava/io/ObjectI HSPLjava/io/ObjectInputStream$BlockDataInputStream;->setBlockDataMode(Z)Z HSPLjava/io/ObjectInputStream$BlockDataInputStream;->skipBlockData()V HSPLjava/io/ObjectInputStream$GetField;-><init>()V -HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass; +HSPLjava/io/ObjectInputStream$GetFieldImpl;-><init>(Ljava/io/ObjectInputStream;Ljava/io/ObjectStreamClass;)V +HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;D)D HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;I)I HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;J)J -HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; +HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/io/ObjectInputStream$GetFieldImpl;->get(Ljava/lang/String;Z)Z HSPLjava/io/ObjectInputStream$GetFieldImpl;->getFieldOffset(Ljava/lang/String;Ljava/lang/Class;)I -HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField; +HSPLjava/io/ObjectInputStream$GetFieldImpl;->readFields()V HSPLjava/io/ObjectInputStream$HandleTable$HandleList;-><init>()V HSPLjava/io/ObjectInputStream$HandleTable$HandleList;->add(I)V HSPLjava/io/ObjectInputStream$HandleTable;-><init>(I)V @@ -1482,62 +1489,62 @@ HSPLjava/io/ObjectInputStream$HandleTable;->finish(I)V HSPLjava/io/ObjectInputStream$HandleTable;->grow()V HSPLjava/io/ObjectInputStream$HandleTable;->lookupException(I)Ljava/lang/ClassNotFoundException; HSPLjava/io/ObjectInputStream$HandleTable;->lookupObject(I)Ljava/lang/Object; -HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V +HSPLjava/io/ObjectInputStream$HandleTable;->markDependency(II)V+]Ljava/io/ObjectInputStream$HandleTable$HandleList;Ljava/io/ObjectInputStream$HandleTable$HandleList; HSPLjava/io/ObjectInputStream$HandleTable;->setObject(ILjava/lang/Object;)V HSPLjava/io/ObjectInputStream$HandleTable;->size()I HSPLjava/io/ObjectInputStream$PeekInputStream;-><init>(Ljava/io/InputStream;)V -HSPLjava/io/ObjectInputStream$PeekInputStream;->close()V -HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream; +HSPLjava/io/ObjectInputStream$PeekInputStream;->close()V+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream; +HSPLjava/io/ObjectInputStream$PeekInputStream;->peek()I+]Ljava/io/InputStream;missing_types HSPLjava/io/ObjectInputStream$PeekInputStream;->read()I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream; -HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/ByteArrayInputStream; +HSPLjava/io/ObjectInputStream$PeekInputStream;->read([BII)I+]Ljava/io/InputStream;missing_types HSPLjava/io/ObjectInputStream$PeekInputStream;->readFully([BII)V+]Ljava/io/ObjectInputStream$PeekInputStream;Ljava/io/ObjectInputStream$PeekInputStream; HSPLjava/io/ObjectInputStream$ValidationList;-><init>()V HSPLjava/io/ObjectInputStream$ValidationList;->clear()V HSPLjava/io/ObjectInputStream$ValidationList;->doCallbacks()V -HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;,Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; +HSPLjava/io/ObjectInputStream;-><init>(Ljava/io/InputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->checkResolve(Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/io/ObjectInputStream;->clear()V +HSPLjava/io/ObjectInputStream;->clear()V+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; HSPLjava/io/ObjectInputStream;->close()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->defaultReadFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/io/ObjectInputStream;->defaultReadObject()V HSPLjava/io/ObjectInputStream;->isCustomSubclass()Z HSPLjava/io/ObjectInputStream;->latestUserDefinedLoader()Ljava/lang/ClassLoader; -HSPLjava/io/ObjectInputStream;->readArray(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/io/ObjectInputStream;->readArray(Z)Ljava/lang/Object; HSPLjava/io/ObjectInputStream;->readBoolean()Z HSPLjava/io/ObjectInputStream;->readByte()B -HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; +HSPLjava/io/ObjectInputStream;->readClassDesc(Z)Ljava/io/ObjectStreamClass; HSPLjava/io/ObjectInputStream;->readClassDescriptor()Ljava/io/ObjectStreamClass; HSPLjava/io/ObjectInputStream;->readEnum(Z)Ljava/lang/Enum; -HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField;+]Ljava/io/ObjectInputStream$GetFieldImpl;Ljava/io/ObjectInputStream$GetFieldImpl;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext; +HSPLjava/io/ObjectInputStream;->readFields()Ljava/io/ObjectInputStream$GetField; HSPLjava/io/ObjectInputStream;->readFloat()F HSPLjava/io/ObjectInputStream;->readHandle(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; -HSPLjava/io/ObjectInputStream;->readInt()I +HSPLjava/io/ObjectInputStream;->readInt()I+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->readLong()J -HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; +HSPLjava/io/ObjectInputStream;->readNonProxyDesc(Z)Ljava/io/ObjectStreamClass;+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; HSPLjava/io/ObjectInputStream;->readNull()Ljava/lang/Object; -HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object;+]Ljava/io/ObjectInputStream$ValidationList;Ljava/io/ObjectInputStream$ValidationList;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; +HSPLjava/io/ObjectInputStream;->readObject()Ljava/lang/Object; HSPLjava/io/ObjectInputStream;->readObject0(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->readOrdinaryObject(Z)Ljava/lang/Object;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; HSPLjava/io/ObjectInputStream;->readSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable;]Ljava/io/SerialCallbackContext;Ljava/io/SerialCallbackContext; HSPLjava/io/ObjectInputStream;->readShort()S -HSPLjava/io/ObjectInputStream;->readStreamHeader()V +HSPLjava/io/ObjectInputStream;->readStreamHeader()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->readString(Z)Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream;]Ljava/io/ObjectInputStream$HandleTable;Ljava/io/ObjectInputStream$HandleTable; -HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; -HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String; +HSPLjava/io/ObjectInputStream;->readTypeString()Ljava/lang/String; +HSPLjava/io/ObjectInputStream;->readUTF()Ljava/lang/String;+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->resolveClass(Ljava/io/ObjectStreamClass;)Ljava/lang/Class; HSPLjava/io/ObjectInputStream;->skipCustomData()V+]Ljava/io/ObjectInputStream$BlockDataInputStream;Ljava/io/ObjectInputStream$BlockDataInputStream; HSPLjava/io/ObjectInputStream;->verifySubclass()V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;-><init>(Ljava/io/OutputStream;)V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->close()V -HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->drain()V +HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->drain()V+]Ljava/io/OutputStream;Ljava/io/ByteArrayOutputStream; HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->flush()V -HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->getUTFLength(Ljava/lang/String;)J +HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->getUTFLength(Ljava/lang/String;)J+]Ljava/lang/String;Ljava/lang/String; HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->setBlockDataMode(Z)Z HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->warnIfClosed()V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->write([BIIZ)V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBlockHeader(I)V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeByte(I)V -HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBytes(Ljava/lang/String;)V +HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeBytes(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream; HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeFloat(F)V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeInt(I)V HSPLjava/io/ObjectOutputStream$BlockDataOutputStream;->writeLong(J)V @@ -1567,7 +1574,7 @@ HSPLjava/io/ObjectOutputStream$ReplaceTable;->lookup(Ljava/lang/Object;)Ljava/la HSPLjava/io/ObjectOutputStream;-><init>(Ljava/io/OutputStream;)V HSPLjava/io/ObjectOutputStream;->annotateClass(Ljava/lang/Class;)V HSPLjava/io/ObjectOutputStream;->close()V -HSPLjava/io/ObjectOutputStream;->defaultWriteFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V +HSPLjava/io/ObjectOutputStream;->defaultWriteFields(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/io/ObjectOutputStream;->defaultWriteObject()V HSPLjava/io/ObjectOutputStream;->flush()V HSPLjava/io/ObjectOutputStream;->isCustomSubclass()Z @@ -1580,18 +1587,18 @@ HSPLjava/io/ObjectOutputStream;->writeClassDescriptor(Ljava/io/ObjectStreamClass HSPLjava/io/ObjectOutputStream;->writeEnum(Ljava/lang/Enum;Ljava/io/ObjectStreamClass;Z)V HSPLjava/io/ObjectOutputStream;->writeFields()V HSPLjava/io/ObjectOutputStream;->writeFloat(F)V -HSPLjava/io/ObjectOutputStream;->writeHandle(I)V +HSPLjava/io/ObjectOutputStream;->writeHandle(I)V+]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream; HSPLjava/io/ObjectOutputStream;->writeInt(I)V HSPLjava/io/ObjectOutputStream;->writeLong(J)V HSPLjava/io/ObjectOutputStream;->writeNonProxyDesc(Ljava/io/ObjectStreamClass;Z)V HSPLjava/io/ObjectOutputStream;->writeNull()V HSPLjava/io/ObjectOutputStream;->writeObject(Ljava/lang/Object;)V -HSPLjava/io/ObjectOutputStream;->writeObject0(Ljava/lang/Object;Z)V -HSPLjava/io/ObjectOutputStream;->writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V +HSPLjava/io/ObjectOutputStream;->writeObject0(Ljava/lang/Object;Z)V+]Ljava/io/ObjectOutputStream$ReplaceTable;Ljava/io/ObjectOutputStream$ReplaceTable;]Ljava/lang/Object;megamorphic_types]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream;]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/io/ObjectOutputStream;->writeOrdinaryObject(Ljava/lang/Object;Ljava/io/ObjectStreamClass;Z)V+]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream; HSPLjava/io/ObjectOutputStream;->writeSerialData(Ljava/lang/Object;Ljava/io/ObjectStreamClass;)V HSPLjava/io/ObjectOutputStream;->writeShort(I)V HSPLjava/io/ObjectOutputStream;->writeStreamHeader()V -HSPLjava/io/ObjectOutputStream;->writeString(Ljava/lang/String;Z)V +HSPLjava/io/ObjectOutputStream;->writeString(Ljava/lang/String;Z)V+]Ljava/io/ObjectOutputStream$HandleTable;Ljava/io/ObjectOutputStream$HandleTable;]Ljava/io/ObjectOutputStream$BlockDataOutputStream;Ljava/io/ObjectOutputStream$BlockDataOutputStream; HSPLjava/io/ObjectOutputStream;->writeTypeString(Ljava/lang/String;)V HSPLjava/io/ObjectOutputStream;->writeUTF(Ljava/lang/String;)V HSPLjava/io/ObjectStreamClass$1;-><init>(Ljava/io/ObjectStreamClass;)V @@ -1623,7 +1630,7 @@ HSPLjava/io/ObjectStreamClass$FieldReflector;->getFields()[Ljava/io/ObjectStream HSPLjava/io/ObjectStreamClass$FieldReflector;->getObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V HSPLjava/io/ObjectStreamClass$FieldReflector;->getPrimFieldValues(Ljava/lang/Object;[B)V HSPLjava/io/ObjectStreamClass$FieldReflector;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class; -HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V +HSPLjava/io/ObjectStreamClass$FieldReflector;->setPrimFieldValues(Ljava/lang/Object;[B)V+]Lsun/misc/Unsafe;Lsun/misc/Unsafe; HSPLjava/io/ObjectStreamClass$FieldReflectorKey;-><init>(Ljava/lang/Class;[Ljava/io/ObjectStreamField;Ljava/lang/ref/ReferenceQueue;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField; HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->equals(Ljava/lang/Object;)Z+]Ljava/io/ObjectStreamClass$FieldReflectorKey;Ljava/io/ObjectStreamClass$FieldReflectorKey; HSPLjava/io/ObjectStreamClass$FieldReflectorKey;->hashCode()I @@ -1666,7 +1673,7 @@ HSPLjava/io/ObjectStreamClass;->getClassSignature(Ljava/lang/Class;)Ljava/lang/S HSPLjava/io/ObjectStreamClass;->getDeclaredSUID(Ljava/lang/Class;)Ljava/lang/Long; HSPLjava/io/ObjectStreamClass;->getDeclaredSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField; HSPLjava/io/ObjectStreamClass;->getDefaultSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField; -HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField;+]Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamField;]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/io/ObjectStreamClass;->getField(Ljava/lang/String;Ljava/lang/Class;)Ljava/io/ObjectStreamField; HSPLjava/io/ObjectStreamClass;->getFields(Z)[Ljava/io/ObjectStreamField; HSPLjava/io/ObjectStreamClass;->getInheritableMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method; HSPLjava/io/ObjectStreamClass;->getMethodSignature([Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/String; @@ -1677,7 +1684,7 @@ HSPLjava/io/ObjectStreamClass;->getPackageName(Ljava/lang/Class;)Ljava/lang/Stri HSPLjava/io/ObjectStreamClass;->getPrimDataSize()I HSPLjava/io/ObjectStreamClass;->getPrimFieldValues(Ljava/lang/Object;[B)V HSPLjava/io/ObjectStreamClass;->getPrivateMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Method; -HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/io/ObjectStreamClass$EntryFuture;Ljava/io/ObjectStreamClass$EntryFuture; +HSPLjava/io/ObjectStreamClass;->getReflector([Ljava/io/ObjectStreamField;Ljava/io/ObjectStreamClass;)Ljava/io/ObjectStreamClass$FieldReflector;+]Ljava/lang/ref/Reference;Ljava/lang/ref/SoftReference;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap; HSPLjava/io/ObjectStreamClass;->getResolveException()Ljava/lang/ClassNotFoundException; HSPLjava/io/ObjectStreamClass;->getSerialFields(Ljava/lang/Class;)[Ljava/io/ObjectStreamField; HSPLjava/io/ObjectStreamClass;->getSerialVersionUID()J+]Ljava/lang/Long;Ljava/lang/Long; @@ -1689,7 +1696,7 @@ HSPLjava/io/ObjectStreamClass;->hasReadResolveMethod()Z HSPLjava/io/ObjectStreamClass;->hasWriteObjectData()Z HSPLjava/io/ObjectStreamClass;->hasWriteObjectMethod()Z HSPLjava/io/ObjectStreamClass;->hasWriteReplaceMethod()Z -HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/io/ObjectStreamClass;->initNonProxy(Ljava/io/ObjectStreamClass;Ljava/lang/Class;Ljava/lang/ClassNotFoundException;Ljava/io/ObjectStreamClass;)V+]Ljava/io/ObjectStreamClass$FieldReflector;Ljava/io/ObjectStreamClass$FieldReflector;]Ljava/io/ObjectStreamClass;Ljava/io/ObjectStreamClass;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/io/ObjectStreamClass;->invokeReadObject(Ljava/lang/Object;Ljava/io/ObjectInputStream;)V HSPLjava/io/ObjectStreamClass;->invokeReadResolve(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/io/ObjectStreamClass;->invokeWriteObject(Ljava/lang/Object;Ljava/io/ObjectOutputStream;)V @@ -1703,7 +1710,7 @@ HSPLjava/io/ObjectStreamClass;->matchFields([Ljava/io/ObjectStreamField;Ljava/io HSPLjava/io/ObjectStreamClass;->newInstance()Ljava/lang/Object; HSPLjava/io/ObjectStreamClass;->packageEquals(Ljava/lang/Class;Ljava/lang/Class;)Z HSPLjava/io/ObjectStreamClass;->processQueue(Ljava/lang/ref/ReferenceQueue;Ljava/util/concurrent/ConcurrentMap;)V -HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2; +HSPLjava/io/ObjectStreamClass;->readNonProxy(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Ljava/io/ObjectInputStream;,Landroid/os/Parcel$2; HSPLjava/io/ObjectStreamClass;->requireInitialized()V HSPLjava/io/ObjectStreamClass;->setObjFieldValues(Ljava/lang/Object;[Ljava/lang/Object;)V HSPLjava/io/ObjectStreamClass;->setPrimFieldValues(Ljava/lang/Object;[B)V @@ -1748,25 +1755,25 @@ HSPLjava/io/PrintWriter;-><init>(Ljava/io/OutputStream;Z)V HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;)V HSPLjava/io/PrintWriter;-><init>(Ljava/io/Writer;Z)V HSPLjava/io/PrintWriter;->append(C)Ljava/io/PrintWriter; -HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter; +HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/io/PrintWriter;+]Ljava/io/PrintWriter;Lcom/android/internal/util/FastPrintWriter; HSPLjava/io/PrintWriter;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable; HSPLjava/io/PrintWriter;->close()V HSPLjava/io/PrintWriter;->ensureOpen()V HSPLjava/io/PrintWriter;->flush()V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter; HSPLjava/io/PrintWriter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter; -HSPLjava/io/PrintWriter;->newLine()V +HSPLjava/io/PrintWriter;->newLine()V+]Ljava/io/Writer;missing_types HSPLjava/io/PrintWriter;->print(C)V HSPLjava/io/PrintWriter;->print(I)V HSPLjava/io/PrintWriter;->print(J)V -HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter; +HSPLjava/io/PrintWriter;->print(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter; HSPLjava/io/PrintWriter;->print(Z)V HSPLjava/io/PrintWriter;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintWriter; HSPLjava/io/PrintWriter;->println()V HSPLjava/io/PrintWriter;->println(I)V HSPLjava/io/PrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter; -HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter; +HSPLjava/io/PrintWriter;->println(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter; HSPLjava/io/PrintWriter;->write(I)V -HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Ljava/io/PrintWriter;,Lcom/android/internal/util/LineBreakBufferedWriter; +HSPLjava/io/PrintWriter;->write(Ljava/lang/String;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Ljava/io/PrintWriter; HSPLjava/io/PrintWriter;->write(Ljava/lang/String;II)V HSPLjava/io/PrintWriter;->write([CII)V+]Ljava/io/Writer;Landroid/util/Log$ImmediateLogWriter; HSPLjava/io/PushbackInputStream;-><init>(Ljava/io/InputStream;I)V @@ -1797,7 +1804,7 @@ HSPLjava/io/RandomAccessFile;->readBytes([BII)I+]Llibcore/io/IoTracker;Llibcore/ HSPLjava/io/RandomAccessFile;->readFully([B)V HSPLjava/io/RandomAccessFile;->readFully([BII)V HSPLjava/io/RandomAccessFile;->readInt()I -HSPLjava/io/RandomAccessFile;->seek(J)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Llibcore/io/IoTracker;Llibcore/io/IoTracker; +HSPLjava/io/RandomAccessFile;->seek(J)V HSPLjava/io/RandomAccessFile;->setLength(J)V HSPLjava/io/RandomAccessFile;->write(I)V HSPLjava/io/RandomAccessFile;->write([B)V @@ -1824,33 +1831,33 @@ HSPLjava/io/StringReader;-><init>(Ljava/lang/String;)V HSPLjava/io/StringReader;->close()V HSPLjava/io/StringReader;->ensureOpen()V HSPLjava/io/StringReader;->read()I -HSPLjava/io/StringReader;->read([CII)I +HSPLjava/io/StringReader;->read([CII)I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/io/StringWriter;-><init>()V HSPLjava/io/StringWriter;-><init>(I)V HSPLjava/io/StringWriter;->append(C)Ljava/io/StringWriter; HSPLjava/io/StringWriter;->append(C)Ljava/io/Writer; -HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter; -HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer; +HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/StringWriter;+]Ljava/io/StringWriter;Ljava/io/StringWriter; +HSPLjava/io/StringWriter;->append(Ljava/lang/CharSequence;)Ljava/io/Writer;+]Ljava/io/StringWriter;Ljava/io/StringWriter; HSPLjava/io/StringWriter;->close()V HSPLjava/io/StringWriter;->flush()V HSPLjava/io/StringWriter;->getBuffer()Ljava/lang/StringBuffer; HSPLjava/io/StringWriter;->toString()Ljava/lang/String; -HSPLjava/io/StringWriter;->write(I)V -HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V -HSPLjava/io/StringWriter;->write(Ljava/lang/String;II)V +HSPLjava/io/StringWriter;->write(I)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer; +HSPLjava/io/StringWriter;->write(Ljava/lang/String;)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer; +HSPLjava/io/StringWriter;->write(Ljava/lang/String;II)V+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer; HSPLjava/io/StringWriter;->write([CII)V HSPLjava/io/UnixFileSystem;->canonicalize(Ljava/lang/String;)Ljava/lang/String; HSPLjava/io/UnixFileSystem;->checkAccess(Ljava/io/File;I)Z+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; -HSPLjava/io/UnixFileSystem;->compare(Ljava/io/File;Ljava/io/File;)I +HSPLjava/io/UnixFileSystem;->compare(Ljava/io/File;Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File; HSPLjava/io/UnixFileSystem;->createDirectory(Ljava/io/File;)Z HSPLjava/io/UnixFileSystem;->createFileExclusively(Ljava/lang/String;)Z HSPLjava/io/UnixFileSystem;->delete(Ljava/io/File;)Z -HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; +HSPLjava/io/UnixFileSystem;->getBooleanAttributes(Ljava/io/File;)I+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; HSPLjava/io/UnixFileSystem;->getDefaultParent()Ljava/lang/String; HSPLjava/io/UnixFileSystem;->getLastModifiedTime(Ljava/io/File;)J -HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J+]Ljava/io/File;Ljava/io/File;]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; +HSPLjava/io/UnixFileSystem;->getLength(Ljava/io/File;)J HSPLjava/io/UnixFileSystem;->getSpace(Ljava/io/File;I)J -HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I +HSPLjava/io/UnixFileSystem;->hashCode(Ljava/io/File;)I+]Ljava/lang/String;Ljava/lang/String;]Ljava/io/File;Ljava/io/File; HSPLjava/io/UnixFileSystem;->isAbsolute(Ljava/io/File;)Z HSPLjava/io/UnixFileSystem;->list(Ljava/io/File;)[Ljava/lang/String;+]Ljava/io/File;Ljava/io/File;]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; HSPLjava/io/UnixFileSystem;->normalize(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; @@ -1866,49 +1873,58 @@ HSPLjava/io/Writer;->append(C)Ljava/io/Writer; HSPLjava/io/Writer;->append(Ljava/lang/CharSequence;)Ljava/io/Writer; HSPLjava/io/Writer;->write(Ljava/lang/String;)V HSPLjava/lang/AbstractStringBuilder;-><init>(I)V -HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/AbstractStringBuilder;->append(C)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; HSPLjava/lang/AbstractStringBuilder;->append(D)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->append(F)Ljava/lang/AbstractStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/AbstractStringBuilder;->append(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; +HSPLjava/lang/AbstractStringBuilder;->append(J)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/AbstractStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer;]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; +HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/CharSequence;Ljava/lang/String;,Landroid/icu/impl/FormattedStringBuilder;,Ljava/nio/HeapCharBuffer; +HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->append(Ljava/lang/StringBuffer;)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->append(Z)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->append([CII)Ljava/lang/AbstractStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->appendCodePoint(I)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/AbstractStringBuilder;->appendChars(Ljava/lang/CharSequence;II)V+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/nio/HeapCharBuffer;,Landroid/icu/impl/FormattedStringBuilder;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; +HSPLjava/lang/AbstractStringBuilder;->appendChars([CII)V+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; +HSPLjava/lang/AbstractStringBuilder;->appendCodePoint(I)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/AbstractStringBuilder;->appendNull()Ljava/lang/AbstractStringBuilder; -HSPLjava/lang/AbstractStringBuilder;->charAt(I)C +HSPLjava/lang/AbstractStringBuilder;->charAt(I)C+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; +HSPLjava/lang/AbstractStringBuilder;->checkRange(III)V +HSPLjava/lang/AbstractStringBuilder;->checkRangeSIOOBE(III)V HSPLjava/lang/AbstractStringBuilder;->codePointAt(I)I HSPLjava/lang/AbstractStringBuilder;->delete(II)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->deleteCharAt(I)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->ensureCapacity(I)V HSPLjava/lang/AbstractStringBuilder;->ensureCapacityInternal(I)V +HSPLjava/lang/AbstractStringBuilder;->getBytes([BIB)V HSPLjava/lang/AbstractStringBuilder;->getChars(II[CI)V +HSPLjava/lang/AbstractStringBuilder;->getCoder()B HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;)I HSPLjava/lang/AbstractStringBuilder;->indexOf(Ljava/lang/String;I)I -HSPLjava/lang/AbstractStringBuilder;->insert(IC)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/AbstractStringBuilder;->insert(IC)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/AbstractStringBuilder;->insert(II)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->insert(ILjava/lang/String;)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/AbstractStringBuilder;->isLatin1()Z HSPLjava/lang/AbstractStringBuilder;->lastIndexOf(Ljava/lang/String;I)I HSPLjava/lang/AbstractStringBuilder;->length()I HSPLjava/lang/AbstractStringBuilder;->newCapacity(I)I +HSPLjava/lang/AbstractStringBuilder;->putStringAt(ILjava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder;,Ljava/lang/StringBuffer; HSPLjava/lang/AbstractStringBuilder;->replace(IILjava/lang/String;)Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->reverse()Ljava/lang/AbstractStringBuilder; HSPLjava/lang/AbstractStringBuilder;->setCharAt(IC)V HSPLjava/lang/AbstractStringBuilder;->setLength(I)V +HSPLjava/lang/AbstractStringBuilder;->shift(II)V HSPLjava/lang/AbstractStringBuilder;->subSequence(II)Ljava/lang/CharSequence; HSPLjava/lang/AbstractStringBuilder;->substring(I)Ljava/lang/String; -HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String; +HSPLjava/lang/AbstractStringBuilder;->substring(II)Ljava/lang/String;+]Ljava/lang/AbstractStringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/ArrayIndexOutOfBoundsException;-><init>(Ljava/lang/String;)V HSPLjava/lang/Boolean;-><init>(Z)V HSPLjava/lang/Boolean;->booleanValue()Z HSPLjava/lang/Boolean;->compare(ZZ)I HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Boolean;)I HSPLjava/lang/Boolean;->compareTo(Ljava/lang/Object;)I -HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z +HSPLjava/lang/Boolean;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Boolean;Ljava/lang/Boolean; HSPLjava/lang/Boolean;->getBoolean(Ljava/lang/String;)Z HSPLjava/lang/Boolean;->hashCode()I HSPLjava/lang/Boolean;->hashCode(Z)I @@ -1944,10 +1960,10 @@ HSPLjava/lang/Character$UnicodeBlock;->of(I)Ljava/lang/Character$UnicodeBlock; HSPLjava/lang/Character;-><init>(C)V HSPLjava/lang/Character;->charCount(I)I HSPLjava/lang/Character;->charValue()C -HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;Ljava/lang/String;,Landroid/icu/impl/StringSegment; +HSPLjava/lang/Character;->codePointAt(Ljava/lang/CharSequence;I)I+]Ljava/lang/CharSequence;megamorphic_types HSPLjava/lang/Character;->codePointAtImpl([CII)I HSPLjava/lang/Character;->codePointBefore(Ljava/lang/CharSequence;I)I -HSPLjava/lang/Character;->codePointCount(Ljava/lang/CharSequence;II)I+]Ljava/lang/CharSequence;Ljava/lang/String; +HSPLjava/lang/Character;->codePointCount(Ljava/lang/CharSequence;II)I HSPLjava/lang/Character;->digit(CI)I HSPLjava/lang/Character;->digit(II)I HSPLjava/lang/Character;->equals(Ljava/lang/Object;)Z @@ -2005,12 +2021,12 @@ HSPLjava/lang/Class;->findInterfaceMethod(Ljava/lang/String;[Ljava/lang/Class;)L HSPLjava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class; HSPLjava/lang/Class;->forName(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class; HSPLjava/lang/Class;->getAccessFlags()I -HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; +HSPLjava/lang/Class;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->getCanonicalName()Ljava/lang/String; HSPLjava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->getComponentType()Ljava/lang/Class; HSPLjava/lang/Class;->getConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor; -HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor; +HSPLjava/lang/Class;->getConstructor0([Ljava/lang/Class;I)Ljava/lang/reflect/Constructor;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor; HSPLjava/lang/Class;->getConstructors()[Ljava/lang/reflect/Constructor; HSPLjava/lang/Class;->getDeclaredConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor; HSPLjava/lang/Class;->getDeclaredConstructors()[Ljava/lang/reflect/Constructor; @@ -2023,12 +2039,12 @@ HSPLjava/lang/Class;->getEnumConstantsShared()[Ljava/lang/Object; HSPLjava/lang/Class;->getField(Ljava/lang/String;)Ljava/lang/reflect/Field; HSPLjava/lang/Class;->getFields()[Ljava/lang/reflect/Field; HSPLjava/lang/Class;->getGenericInterfaces()[Ljava/lang/reflect/Type; -HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type; +HSPLjava/lang/Class;->getGenericSuperclass()Ljava/lang/reflect/Type;+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->getInterfaces()[Ljava/lang/Class; HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; HSPLjava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/reflect/Method; HSPLjava/lang/Class;->getMethods()[Ljava/lang/reflect/Method; -HSPLjava/lang/Class;->getModifiers()I +HSPLjava/lang/Class;->getModifiers()I+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->getName()Ljava/lang/String; HSPLjava/lang/Class;->getPackage()Ljava/lang/Package; HSPLjava/lang/Class;->getPackageName()Ljava/lang/String; @@ -2037,9 +2053,9 @@ HSPLjava/lang/Class;->getPublicFieldsRecursive(Ljava/util/List;)V HSPLjava/lang/Class;->getPublicMethodRecursive(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; HSPLjava/lang/Class;->getPublicMethodsInternal(Ljava/util/List;)V HSPLjava/lang/Class;->getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream; -HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String; +HSPLjava/lang/Class;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/Class;->getSimpleName()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Class;Ljava/lang/Class; -HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class; +HSPLjava/lang/Class;->getSuperclass()Ljava/lang/Class;+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->getTypeName()Ljava/lang/String; HSPLjava/lang/Class;->getTypeParameters()[Ljava/lang/reflect/TypeVariable; HSPLjava/lang/Class;->isAnnotation()Z @@ -2051,7 +2067,7 @@ HSPLjava/lang/Class;->isInstance(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing HSPLjava/lang/Class;->isInterface()Z HSPLjava/lang/Class;->isLocalClass()Z+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/Class;->isLocalOrAnonymousClass()Z -HSPLjava/lang/Class;->isMemberClass()Z+]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/lang/Class;->isMemberClass()Z HSPLjava/lang/Class;->isPrimitive()Z HSPLjava/lang/Class;->isProxy()Z HSPLjava/lang/Class;->resolveName(Ljava/lang/String;)Ljava/lang/String; @@ -2083,13 +2099,13 @@ HSPLjava/lang/Daemons$Daemon;->startPostZygoteFork()V HSPLjava/lang/Daemons$Daemon;->stop()V HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$fgetprogressCounter(Ljava/lang/Daemons$FinalizerDaemon;)Ljava/util/concurrent/atomic/AtomicInteger; HSPLjava/lang/Daemons$FinalizerDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerDaemon; -HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;megamorphic_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference; +HSPLjava/lang/Daemons$FinalizerDaemon;->doFinalize(Ljava/lang/ref/FinalizerReference;)V+]Ljava/lang/Object;missing_types]Ljava/lang/ref/FinalizerReference;Ljava/lang/ref/FinalizerReference; HSPLjava/lang/Daemons$FinalizerDaemon;->runInternal()V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$mmonitoringNotNeeded(Ljava/lang/Daemons$FinalizerWatchdogDaemon;I)V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$FinalizerWatchdogDaemon; HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->isActive(I)Z -HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V+]Ljava/lang/Object;Ljava/lang/Daemons$FinalizerWatchdogDaemon; +HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNeeded(I)V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->monitoringNotNeeded(I)V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->resetTimeouts()V HSPLjava/lang/Daemons$FinalizerWatchdogDaemon;->runInternal()V @@ -2102,7 +2118,6 @@ HSPLjava/lang/Daemons$ReferenceQueueDaemon;->-$$Nest$fgetprogressCounter(Ljava/l HSPLjava/lang/Daemons$ReferenceQueueDaemon;->-$$Nest$sfgetINSTANCE()Ljava/lang/Daemons$ReferenceQueueDaemon; HSPLjava/lang/Daemons$ReferenceQueueDaemon;->runInternal()V HSPLjava/lang/Daemons;->-$$Nest$sfgetPOST_ZYGOTE_START_LATCH()Ljava/util/concurrent/CountDownLatch; -HSPLjava/lang/Daemons;->-$$Nest$sfputMAX_FINALIZE_NANOS(J)V HSPLjava/lang/Daemons;->startPostZygoteFork()V HSPLjava/lang/Daemons;->stop()V HSPLjava/lang/Double;-><init>(D)V @@ -2117,6 +2132,7 @@ HSPLjava/lang/Double;->hashCode()I HSPLjava/lang/Double;->hashCode(D)I HSPLjava/lang/Double;->intValue()I HSPLjava/lang/Double;->isInfinite(D)Z +HSPLjava/lang/Double;->isNaN()Z HSPLjava/lang/Double;->isNaN(D)Z HSPLjava/lang/Double;->longValue()J HSPLjava/lang/Double;->parseDouble(Ljava/lang/String;)D @@ -2138,7 +2154,7 @@ HSPLjava/lang/Enum;->hashCode()I HSPLjava/lang/Enum;->name()Ljava/lang/String; HSPLjava/lang/Enum;->ordinal()I HSPLjava/lang/Enum;->toString()Ljava/lang/String; -HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; +HSPLjava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;+]Ljava/lang/Enum;missing_types HSPLjava/lang/Error;-><init>(Ljava/lang/String;)V HSPLjava/lang/Exception;-><init>()V HSPLjava/lang/Exception;-><init>(Ljava/lang/String;)V @@ -2148,7 +2164,7 @@ HSPLjava/lang/Exception;-><init>(Ljava/lang/Throwable;)V HSPLjava/lang/Float;-><init>(F)V HSPLjava/lang/Float;->compare(FF)I HSPLjava/lang/Float;->compareTo(Ljava/lang/Float;)I -HSPLjava/lang/Float;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Float;Ljava/lang/Float; +HSPLjava/lang/Float;->compareTo(Ljava/lang/Object;)I HSPLjava/lang/Float;->doubleValue()D HSPLjava/lang/Float;->equals(Ljava/lang/Object;)Z HSPLjava/lang/Float;->floatToIntBits(F)I @@ -2176,13 +2192,14 @@ HSPLjava/lang/IndexOutOfBoundsException;-><init>(Ljava/lang/String;)V HSPLjava/lang/InheritableThreadLocal;->childValue(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/lang/InheritableThreadLocal;->createMap(Ljava/lang/Thread;Ljava/lang/Object;)V HSPLjava/lang/InheritableThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap; +HSPLjava/lang/InstantiationException;-><init>(Ljava/lang/String;)V HSPLjava/lang/Integer;-><init>(I)V HSPLjava/lang/Integer;->bitCount(I)I HSPLjava/lang/Integer;->byteValue()B HSPLjava/lang/Integer;->compare(II)I HSPLjava/lang/Integer;->compareTo(Ljava/lang/Integer;)I -HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I -HSPLjava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer;+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/lang/Integer;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Integer;Ljava/lang/Integer; +HSPLjava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer; HSPLjava/lang/Integer;->divideUnsigned(II)I HSPLjava/lang/Integer;->doubleValue()D HSPLjava/lang/Integer;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Integer;Ljava/lang/Integer; @@ -2224,18 +2241,18 @@ HSPLjava/lang/Integer;->valueOf(I)Ljava/lang/Integer; HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;)Ljava/lang/Integer; HSPLjava/lang/Integer;->valueOf(Ljava/lang/String;I)Ljava/lang/Integer; HSPLjava/lang/InterruptedException;-><init>()V -HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/lang/Iterable;Ljava/util/HashSet;,Ljava/util/WeakHashMap$KeySet;,Ljava/util/WeakHashMap$EntrySet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/WeakHashMap$KeyIterator;,Ljava/util/WeakHashMap$EntryIterator;]Ljava/util/function/Consumer;missing_types +HSPLjava/lang/Iterable;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;missing_types]Ljava/lang/Iterable;missing_types HSPLjava/lang/LinkageError;-><init>(Ljava/lang/String;)V HSPLjava/lang/Long;-><init>(J)V HSPLjava/lang/Long;->bitCount(J)I HSPLjava/lang/Long;->compare(JJ)I HSPLjava/lang/Long;->compareTo(Ljava/lang/Long;)I -HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I +HSPLjava/lang/Long;->compareTo(Ljava/lang/Object;)I+]Ljava/lang/Long;Ljava/lang/Long; HSPLjava/lang/Long;->compareUnsigned(JJ)I HSPLjava/lang/Long;->decode(Ljava/lang/String;)Ljava/lang/Long; HSPLjava/lang/Long;->divideUnsigned(JJ)J HSPLjava/lang/Long;->doubleValue()D -HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Long;Ljava/lang/Long; +HSPLjava/lang/Long;->equals(Ljava/lang/Object;)Z HSPLjava/lang/Long;->formatUnsignedLong0(JI[BII)V HSPLjava/lang/Long;->getChars(JI[B)I HSPLjava/lang/Long;->getChars(JI[C)I @@ -2249,6 +2266,7 @@ HSPLjava/lang/Long;->longValue()J HSPLjava/lang/Long;->lowestOneBit(J)J HSPLjava/lang/Long;->numberOfLeadingZeros(J)I HSPLjava/lang/Long;->numberOfTrailingZeros(J)I +HSPLjava/lang/Long;->parseLong(Ljava/lang/CharSequence;III)J HSPLjava/lang/Long;->parseLong(Ljava/lang/String;)J HSPLjava/lang/Long;->parseLong(Ljava/lang/String;I)J HSPLjava/lang/Long;->reverse(J)J @@ -2297,7 +2315,7 @@ HSPLjava/lang/Math;->multiplyHigh(JJ)J HSPLjava/lang/Math;->nextAfter(DD)D HSPLjava/lang/Math;->powerOfTwoD(I)D HSPLjava/lang/Math;->powerOfTwoF(I)F -HSPLjava/lang/Math;->random()D +HSPLjava/lang/Math;->random()D+]Ljava/util/Random;Ljava/util/Random; HSPLjava/lang/Math;->randomLongInternal()J HSPLjava/lang/Math;->round(D)J HSPLjava/lang/Math;->round(F)I @@ -2318,6 +2336,7 @@ HSPLjava/lang/NullPointerException;-><init>(Ljava/lang/String;)V HSPLjava/lang/Number;-><init>()V HSPLjava/lang/NumberFormatException;-><init>(Ljava/lang/String;)V HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;)Ljava/lang/NumberFormatException; +HSPLjava/lang/NumberFormatException;->forInputString(Ljava/lang/String;I)Ljava/lang/NumberFormatException; HSPLjava/lang/Object;-><init>()V HSPLjava/lang/Object;->clone()Ljava/lang/Object; HSPLjava/lang/Object;->equals(Ljava/lang/Object;)Z @@ -2381,12 +2400,15 @@ HSPLjava/lang/StackTraceElement;->getMethodName()Ljava/lang/String; HSPLjava/lang/StackTraceElement;->hashCode()I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/StackTraceElement;->isNativeMethod()Z HSPLjava/lang/StackTraceElement;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement; -HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I +HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/String$CaseInsensitiveComparator;Ljava/lang/String$CaseInsensitiveComparator; HSPLjava/lang/String$CaseInsensitiveComparator;->compare(Ljava/lang/String;Ljava/lang/String;)I HSPLjava/lang/String;->checkBoundsBeginEnd(III)V +HSPLjava/lang/String;->checkBoundsOffCount(III)V HSPLjava/lang/String;->checkIndex(II)V +HSPLjava/lang/String;->checkOffset(II)V HSPLjava/lang/String;->codePointAt(I)I HSPLjava/lang/String;->codePointCount(II)I +HSPLjava/lang/String;->coder()B HSPLjava/lang/String;->compareTo(Ljava/lang/Object;)I HSPLjava/lang/String;->compareToIgnoreCase(Ljava/lang/String;)I HSPLjava/lang/String;->contains(Ljava/lang/CharSequence;)Z+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/CharSequence;Ljava/lang/String; @@ -2395,11 +2417,12 @@ HSPLjava/lang/String;->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;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; -HSPLjava/lang/String;->getBytes()[B -HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String; -HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B+]Ljava/nio/charset/Charset;missing_types +HSPLjava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; +HSPLjava/lang/String;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;+]Ljava/util/Formatter;Ljava/util/Formatter; +HSPLjava/lang/String;->getBytes()[B+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/lang/String;->getBytes(Ljava/lang/String;)[B +HSPLjava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B +HSPLjava/lang/String;->getBytes([BIB)V+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/String;->getChars(II[CI)V HSPLjava/lang/String;->getChars([CI)V HSPLjava/lang/String;->hashCode()I @@ -2408,8 +2431,7 @@ HSPLjava/lang/String;->indexOf(II)I HSPLjava/lang/String;->indexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/String;->indexOf(Ljava/lang/String;I)I HSPLjava/lang/String;->indexOf(Ljava/lang/String;Ljava/lang/String;I)I -HSPLjava/lang/String;->indexOf([CIILjava/lang/String;I)I -HSPLjava/lang/String;->indexOf([CII[CIII)I +HSPLjava/lang/String;->indexOf([BBILjava/lang/String;I)I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/String;->isEmpty()Z HSPLjava/lang/String;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String; HSPLjava/lang/String;->join(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String; @@ -2418,14 +2440,14 @@ HSPLjava/lang/String;->lastIndexOf(II)I HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;)I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;I)I HSPLjava/lang/String;->lastIndexOf(Ljava/lang/String;Ljava/lang/String;I)I -HSPLjava/lang/String;->lastIndexOf([CIILjava/lang/String;I)I +HSPLjava/lang/String;->lastIndexOf([BBILjava/lang/String;I)I HSPLjava/lang/String;->lastIndexOf([CII[CIII)I HSPLjava/lang/String;->length()I HSPLjava/lang/String;->matches(Ljava/lang/String;)Z HSPLjava/lang/String;->regionMatches(ILjava/lang/String;II)Z -HSPLjava/lang/String;->regionMatches(ZILjava/lang/String;II)Z+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/lang/String;->regionMatches(ZILjava/lang/String;II)Z HSPLjava/lang/String;->replace(CC)Ljava/lang/String; -HSPLjava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String; +HSPLjava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/CharSequence;Ljava/lang/String; HSPLjava/lang/String;->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; HSPLjava/lang/String;->replaceFirst(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; HSPLjava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String; @@ -2435,7 +2457,7 @@ HSPLjava/lang/String;->startsWith(Ljava/lang/String;I)Z HSPLjava/lang/String;->subSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String; HSPLjava/lang/String;->substring(I)Ljava/lang/String; HSPLjava/lang/String;->substring(II)Ljava/lang/String; -HSPLjava/lang/String;->toLowerCase()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/lang/String;->toLowerCase()Ljava/lang/String; HSPLjava/lang/String;->toLowerCase(Ljava/util/Locale;)Ljava/lang/String; HSPLjava/lang/String;->toString()Ljava/lang/String; HSPLjava/lang/String;->toUpperCase()Ljava/lang/String; @@ -2446,7 +2468,7 @@ HSPLjava/lang/String;->valueOf(D)Ljava/lang/String; HSPLjava/lang/String;->valueOf(F)Ljava/lang/String; HSPLjava/lang/String;->valueOf(I)Ljava/lang/String; HSPLjava/lang/String;->valueOf(J)Ljava/lang/String; -HSPLjava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/Object;missing_types +HSPLjava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/Object;megamorphic_types HSPLjava/lang/String;->valueOf(Z)Ljava/lang/String; HSPLjava/lang/String;->valueOf([C)Ljava/lang/String; HSPLjava/lang/StringBuffer;-><init>()V @@ -2455,6 +2477,7 @@ HSPLjava/lang/StringBuffer;-><init>(Ljava/lang/String;)V HSPLjava/lang/StringBuffer;->append(C)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->append(I)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->append(J)Ljava/lang/StringBuffer; +HSPLjava/lang/StringBuffer;->append(Ljava/lang/AbstractStringBuilder;)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable; HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder; @@ -2467,10 +2490,11 @@ HSPLjava/lang/StringBuffer;->append(Z)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->append([CII)Ljava/lang/StringBuffer; HSPLjava/lang/StringBuffer;->charAt(I)C HSPLjava/lang/StringBuffer;->codePointAt(I)I +HSPLjava/lang/StringBuffer;->getBytes([BIB)V HSPLjava/lang/StringBuffer;->getChars(II[CI)V HSPLjava/lang/StringBuffer;->length()I HSPLjava/lang/StringBuffer;->setLength(I)V -HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String; +HSPLjava/lang/StringBuffer;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer; HSPLjava/lang/StringBuilder;-><init>()V HSPLjava/lang/StringBuilder;-><init>(I)V HSPLjava/lang/StringBuilder;-><init>(Ljava/lang/CharSequence;)V @@ -2483,8 +2507,8 @@ HSPLjava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder; -HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; -HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable; +HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/AbstractStringBuilder; +HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/Appendable;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/AbstractStringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; @@ -2513,7 +2537,7 @@ HSPLjava/lang/StringBuilder;->setLength(I)V HSPLjava/lang/StringBuilder;->subSequence(II)Ljava/lang/CharSequence; HSPLjava/lang/StringBuilder;->substring(I)Ljava/lang/String; HSPLjava/lang/StringBuilder;->substring(II)Ljava/lang/String; -HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String; +HSPLjava/lang/StringBuilder;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/StringFactory;->newEmptyString()Ljava/lang/String; HSPLjava/lang/StringFactory;->newStringFromBytes([B)Ljava/lang/String; HSPLjava/lang/StringFactory;->newStringFromBytes([BI)Ljava/lang/String; @@ -2524,6 +2548,20 @@ HSPLjava/lang/StringFactory;->newStringFromBytes([BLjava/lang/String;)Ljava/lang HSPLjava/lang/StringFactory;->newStringFromBytes([BLjava/nio/charset/Charset;)Ljava/lang/String; HSPLjava/lang/StringFactory;->newStringFromChars([C)Ljava/lang/String; HSPLjava/lang/StringFactory;->newStringFromChars([CII)Ljava/lang/String; +HSPLjava/lang/StringLatin1;->canEncode(I)Z +HSPLjava/lang/StringLatin1;->indexOf([BILjava/lang/String;II)I +HSPLjava/lang/StringLatin1;->inflate([BI[BII)V +HSPLjava/lang/StringLatin1;->lastIndexOf([BILjava/lang/String;II)I +HSPLjava/lang/StringLatin1;->newString([BII)Ljava/lang/String; +HSPLjava/lang/StringUTF16;->checkBoundsBeginEnd(II[B)V +HSPLjava/lang/StringUTF16;->checkBoundsOffCount(II[B)V +HSPLjava/lang/StringUTF16;->getChar([BI)C +HSPLjava/lang/StringUTF16;->getChars(II[B)I +HSPLjava/lang/StringUTF16;->getChars([BII[CI)V +HSPLjava/lang/StringUTF16;->inflate([BI[BII)V +HSPLjava/lang/StringUTF16;->length([B)I +HSPLjava/lang/StringUTF16;->newBytesFor(I)[B +HSPLjava/lang/StringUTF16;->putChar([BII)V HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/lang/System$PropertiesWithNonOverrideableDefaults;->remove(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/lang/System;->arraycopy([BI[BII)V @@ -2573,7 +2611,7 @@ HSPLjava/lang/Thread;->getStackTrace()[Ljava/lang/StackTraceElement; HSPLjava/lang/Thread;->getState()Ljava/lang/Thread$State; HSPLjava/lang/Thread;->getThreadGroup()Ljava/lang/ThreadGroup; HSPLjava/lang/Thread;->getUncaughtExceptionHandler()Ljava/lang/Thread$UncaughtExceptionHandler; -HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V+]Ljava/lang/Thread;missing_types +HSPLjava/lang/Thread;->init2(Ljava/lang/Thread;Z)V HSPLjava/lang/Thread;->interrupt()V HSPLjava/lang/Thread;->isAlive()Z HSPLjava/lang/Thread;->isDaemon()Z @@ -2581,6 +2619,7 @@ HSPLjava/lang/Thread;->join()V HSPLjava/lang/Thread;->join(J)V HSPLjava/lang/Thread;->nextThreadID()J HSPLjava/lang/Thread;->nextThreadNum()I +HSPLjava/lang/Thread;->onSpinWait()V HSPLjava/lang/Thread;->run()V HSPLjava/lang/Thread;->setContextClassLoader(Ljava/lang/ClassLoader;)V HSPLjava/lang/Thread;->setDaemon(Z)V @@ -2604,7 +2643,7 @@ HSPLjava/lang/ThreadGroup;->addUnstarted()V HSPLjava/lang/ThreadGroup;->checkAccess()V HSPLjava/lang/ThreadGroup;->checkParentAccess(Ljava/lang/ThreadGroup;)Ljava/lang/Void; HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;)I -HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I +HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/Thread;IZ)I+]Ljava/lang/Thread;missing_types HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;)I HSPLjava/lang/ThreadGroup;->enumerate([Ljava/lang/ThreadGroup;IZ)I HSPLjava/lang/ThreadGroup;->getMaxPriority()I @@ -2621,13 +2660,13 @@ HSPLjava/lang/ThreadLocal$ThreadLocalMap;-><init>(Ljava/lang/ThreadLocal$ThreadL HSPLjava/lang/ThreadLocal$ThreadLocalMap;-><init>(Ljava/lang/ThreadLocal;Ljava/lang/Object;)V HSPLjava/lang/ThreadLocal$ThreadLocalMap;->cleanSomeSlots(II)Z HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntries()V -HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I +HSPLjava/lang/ThreadLocal$ThreadLocalMap;->expungeStaleEntry(I)I+]Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntry(Ljava/lang/ThreadLocal;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; HSPLjava/lang/ThreadLocal$ThreadLocalMap;->getEntryAfterMiss(Ljava/lang/ThreadLocal;ILjava/lang/ThreadLocal$ThreadLocalMap$Entry;)Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; HSPLjava/lang/ThreadLocal$ThreadLocalMap;->nextIndex(II)I HSPLjava/lang/ThreadLocal$ThreadLocalMap;->prevIndex(II)I HSPLjava/lang/ThreadLocal$ThreadLocalMap;->rehash()V -HSPLjava/lang/ThreadLocal$ThreadLocalMap;->remove(Ljava/lang/ThreadLocal;)V +HSPLjava/lang/ThreadLocal$ThreadLocalMap;->remove(Ljava/lang/ThreadLocal;)V+]Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; HSPLjava/lang/ThreadLocal$ThreadLocalMap;->replaceStaleEntry(Ljava/lang/ThreadLocal;Ljava/lang/Object;I)V HSPLjava/lang/ThreadLocal$ThreadLocalMap;->resize()V HSPLjava/lang/ThreadLocal$ThreadLocalMap;->set(Ljava/lang/ThreadLocal;Ljava/lang/Object;)V @@ -2636,13 +2675,13 @@ HSPLjava/lang/ThreadLocal;->-$$Nest$fgetthreadLocalHashCode(Ljava/lang/ThreadLoc HSPLjava/lang/ThreadLocal;-><init>()V HSPLjava/lang/ThreadLocal;->createInheritedMap(Ljava/lang/ThreadLocal$ThreadLocalMap;)Ljava/lang/ThreadLocal$ThreadLocalMap; HSPLjava/lang/ThreadLocal;->createMap(Ljava/lang/Thread;Ljava/lang/Object;)V -HSPLjava/lang/ThreadLocal;->get()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;missing_types +HSPLjava/lang/ThreadLocal;->get()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;megamorphic_types HSPLjava/lang/ThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap; HSPLjava/lang/ThreadLocal;->initialValue()Ljava/lang/Object; HSPLjava/lang/ThreadLocal;->nextHashCode()I -HSPLjava/lang/ThreadLocal;->remove()V -HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;megamorphic_types -HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object; +HSPLjava/lang/ThreadLocal;->remove()V+]Ljava/lang/ThreadLocal;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;,Ljava/lang/ThreadLocal; +HSPLjava/lang/ThreadLocal;->set(Ljava/lang/Object;)V+]Ljava/lang/ThreadLocal;missing_types +HSPLjava/lang/ThreadLocal;->setInitialValue()Ljava/lang/Object;+]Ljava/lang/ThreadLocal;missing_types HSPLjava/lang/ThreadLocal;->withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal; HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>()V HSPLjava/lang/Throwable$PrintStreamOrWriter;-><init>(Ljava/lang/Throwable$PrintStreamOrWriter-IA;)V @@ -2651,12 +2690,12 @@ HSPLjava/lang/Throwable$WrappedPrintStream;->lock()Ljava/lang/Object; HSPLjava/lang/Throwable$WrappedPrintStream;->println(Ljava/lang/Object;)V HSPLjava/lang/Throwable$WrappedPrintWriter;-><init>(Ljava/io/PrintWriter;)V HSPLjava/lang/Throwable$WrappedPrintWriter;->lock()Ljava/lang/Object; -HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter; -HSPLjava/lang/Throwable;-><init>()V +HSPLjava/lang/Throwable$WrappedPrintWriter;->println(Ljava/lang/Object;)V+]Ljava/io/PrintWriter;Lcom/android/internal/util/LineBreakBufferedWriter;,Lcom/android/internal/util/FastPrintWriter;,Ljava/io/PrintWriter; +HSPLjava/lang/Throwable;-><init>()V+]Ljava/lang/Throwable;missing_types HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;)V+]Ljava/lang/Throwable;missing_types -HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types +HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V HSPLjava/lang/Throwable;-><init>(Ljava/lang/String;Ljava/lang/Throwable;ZZ)V -HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V+]Ljava/lang/Throwable;missing_types +HSPLjava/lang/Throwable;-><init>(Ljava/lang/Throwable;)V HSPLjava/lang/Throwable;->addSuppressed(Ljava/lang/Throwable;)V HSPLjava/lang/Throwable;->fillInStackTrace()Ljava/lang/Throwable; HSPLjava/lang/Throwable;->getCause()Ljava/lang/Throwable; @@ -2664,9 +2703,9 @@ HSPLjava/lang/Throwable;->getLocalizedMessage()Ljava/lang/String; HSPLjava/lang/Throwable;->getMessage()Ljava/lang/String; HSPLjava/lang/Throwable;->getOurStackTrace()[Ljava/lang/StackTraceElement; HSPLjava/lang/Throwable;->getStackTrace()[Ljava/lang/StackTraceElement; -HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable;+]Ljava/util/List;Ljava/util/Collections$EmptyList; +HSPLjava/lang/Throwable;->getSuppressed()[Ljava/lang/Throwable; HSPLjava/lang/Throwable;->initCause(Ljava/lang/Throwable;)Ljava/lang/Throwable; -HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Throwable$PrintStreamOrWriter;Ljava/lang/Throwable$WrappedPrintWriter;]Ljava/lang/Throwable;Ljava/util/concurrent/CancellationException;,Ljava/io/IOException;,Ljava/lang/Throwable;]Ljava/lang/StackTraceElement;Ljava/lang/StackTraceElement;]Ljava/util/Set;Ljava/util/Collections$SetFromMap; +HSPLjava/lang/Throwable;->printEnclosedStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;[Ljava/lang/StackTraceElement;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V HSPLjava/lang/Throwable;->printStackTrace()V HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V HSPLjava/lang/Throwable;->printStackTrace(Ljava/io/PrintWriter;)V @@ -2699,7 +2738,7 @@ HSPLjava/lang/UnsatisfiedLinkError;-><init>(Ljava/lang/String;)V HSPLjava/lang/UnsupportedOperationException;-><init>()V HSPLjava/lang/UnsupportedOperationException;-><init>(Ljava/lang/String;)V HSPLjava/lang/VMClassLoader;->getResource(Ljava/lang/String;)Ljava/net/URL; -HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List; +HSPLjava/lang/VMClassLoader;->getResources(Ljava/lang/String;)Ljava/util/List;+]Llibcore/io/ClassPathURLStreamHandler;Llibcore/io/ClassPathURLStreamHandler; HSPLjava/lang/invoke/FieldVarHandle;-><init>(Ljava/lang/reflect/Field;Ljava/lang/Class;)V HSPLjava/lang/invoke/FieldVarHandle;->create(Ljava/lang/reflect/Field;)Ljava/lang/invoke/FieldVarHandle; HSPLjava/lang/invoke/MethodHandle;-><init>(JILjava/lang/invoke/MethodType;)V @@ -2790,7 +2829,7 @@ HSPLjava/lang/reflect/AccessibleObject;-><init>()V HSPLjava/lang/reflect/AccessibleObject;->getAnnotations()[Ljava/lang/annotation/Annotation; HSPLjava/lang/reflect/AccessibleObject;->isAccessible()Z HSPLjava/lang/reflect/AccessibleObject;->setAccessible(Z)V -HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V +HSPLjava/lang/reflect/AccessibleObject;->setAccessible0(Ljava/lang/reflect/AccessibleObject;Z)V+]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor; HSPLjava/lang/reflect/Array;->get(Ljava/lang/Object;I)Ljava/lang/Object; HSPLjava/lang/reflect/Array;->getLength(Ljava/lang/Object;)I HSPLjava/lang/reflect/Array;->newArray(Ljava/lang/Class;I)Ljava/lang/Object;+]Ljava/lang/Class;Ljava/lang/Class; @@ -2833,22 +2872,22 @@ HSPLjava/lang/reflect/Executable;->separateWithCommas([Ljava/lang/Class;Ljava/la HSPLjava/lang/reflect/Executable;->sharedToString(IZ[Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/String; HSPLjava/lang/reflect/Field;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; HSPLjava/lang/reflect/Field;->getDeclaringClass()Ljava/lang/Class; -HSPLjava/lang/reflect/Field;->getGenericType()Ljava/lang/reflect/Type; +HSPLjava/lang/reflect/Field;->getGenericType()Ljava/lang/reflect/Type;+]Ljava/lang/reflect/Field;Ljava/lang/reflect/Field;]Ljava/lang/Class;Ljava/lang/Class;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLjava/lang/reflect/Field;->getModifiers()I -HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String; +HSPLjava/lang/reflect/Field;->getName()Ljava/lang/String;+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/lang/reflect/Field;->getOffset()I -HSPLjava/lang/reflect/Field;->getSignatureAttribute()Ljava/lang/String; +HSPLjava/lang/reflect/Field;->getSignatureAttribute()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/lang/reflect/Field;->getType()Ljava/lang/Class; HSPLjava/lang/reflect/Field;->hashCode()I HSPLjava/lang/reflect/Field;->isAnnotationPresent(Ljava/lang/Class;)Z HSPLjava/lang/reflect/Field;->isEnumConstant()Z -HSPLjava/lang/reflect/Field;->isSynthetic()Z +HSPLjava/lang/reflect/Field;->isSynthetic()Z+]Ljava/lang/reflect/Field;Ljava/lang/reflect/Field; HSPLjava/lang/reflect/InvocationTargetException;-><init>(Ljava/lang/Throwable;)V HSPLjava/lang/reflect/InvocationTargetException;->getCause()Ljava/lang/Throwable; HSPLjava/lang/reflect/Method$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I HSPLjava/lang/reflect/Method$1;->compare(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)I HSPLjava/lang/reflect/Method;->equalNameAndParameters(Ljava/lang/reflect/Method;)Z -HSPLjava/lang/reflect/Method;->equals(Ljava/lang/Object;)Z +HSPLjava/lang/reflect/Method;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;Ljava/lang/Class;]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method; HSPLjava/lang/reflect/Method;->getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; HSPLjava/lang/reflect/Method;->getDeclaredAnnotations()[Ljava/lang/annotation/Annotation; HSPLjava/lang/reflect/Method;->getDeclaringClass()Ljava/lang/Class; @@ -2897,9 +2936,9 @@ HSPLjava/lang/reflect/Proxy;->getMethods([Ljava/lang/Class;)Ljava/util/List; HSPLjava/lang/reflect/Proxy;->getMethodsRecursive([Ljava/lang/Class;Ljava/util/List;)V HSPLjava/lang/reflect/Proxy;->getProxyClass0(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class; HSPLjava/lang/reflect/Proxy;->intersectExceptions([Ljava/lang/Class;[Ljava/lang/Class;)[Ljava/lang/Class; -HSPLjava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/InvocationHandler;Llibcore/reflect/AnnotationFactory; HSPLjava/lang/reflect/Proxy;->isProxyClass(Ljava/lang/Class;)Z -HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object; +HSPLjava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;+][Ljava/lang/Class;[Ljava/lang/Class;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor; HSPLjava/lang/reflect/Proxy;->validateReturnTypes(Ljava/util/List;)V HSPLjava/lang/reflect/WeakCache$CacheKey;-><init>(Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V HSPLjava/lang/reflect/WeakCache$CacheKey;->equals(Ljava/lang/Object;)Z @@ -2912,7 +2951,7 @@ HSPLjava/lang/reflect/WeakCache$Factory;->get()Ljava/lang/Object; HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetreverseMap(Ljava/lang/reflect/WeakCache;)Ljava/util/concurrent/ConcurrentMap; HSPLjava/lang/reflect/WeakCache;->-$$Nest$fgetvalueFactory(Ljava/lang/reflect/WeakCache;)Ljava/util/function/BiFunction; HSPLjava/lang/reflect/WeakCache;->expungeStaleEntries()V -HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/lang/reflect/WeakCache;->get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/function/BiFunction;Ljava/lang/reflect/Proxy$KeyFactory;]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/function/Supplier;Ljava/lang/reflect/WeakCache$CacheValue; HSPLjava/math/BigDecimal;-><init>(I)V HSPLjava/math/BigDecimal;-><init>(J)V HSPLjava/math/BigDecimal;-><init>(Ljava/lang/String;)V @@ -2937,7 +2976,7 @@ HSPLjava/math/BigDecimal;->divide(Ljava/math/BigDecimal;Ljava/math/RoundingMode; HSPLjava/math/BigDecimal;->divideAndRound(JJIII)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->getValueString(ILjava/lang/String;I)Ljava/lang/String; HSPLjava/math/BigDecimal;->inflated()Ljava/math/BigInteger; -HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/math/BigDecimal$StringBuilderHelper;Ljava/math/BigDecimal$StringBuilderHelper;]Ljava/lang/ThreadLocal;Ljava/math/BigDecimal$1;]Ljava/math/BigDecimal;Ljava/math/BigDecimal; +HSPLjava/math/BigDecimal;->layoutChars(Z)Ljava/lang/String; HSPLjava/math/BigDecimal;->longCompareMagnitude(JJ)I HSPLjava/math/BigDecimal;->longMultiplyPowerTen(JI)J HSPLjava/math/BigDecimal;->longValueExact()J @@ -2946,12 +2985,15 @@ HSPLjava/math/BigDecimal;->movePointRight(I)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->multiply(JJ)J HSPLjava/math/BigDecimal;->multiply(JJI)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal; +HSPLjava/math/BigDecimal;->needIncrement(JIIJJ)Z HSPLjava/math/BigDecimal;->scale()I +HSPLjava/math/BigDecimal;->scaleByPowerOfTen(I)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->setScale(II)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->setScale(ILjava/math/RoundingMode;)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->signum()I HSPLjava/math/BigDecimal;->stripTrailingZeros()Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->subtract(Ljava/math/BigDecimal;)Ljava/math/BigDecimal; +HSPLjava/math/BigDecimal;->toBigInteger()Ljava/math/BigInteger; HSPLjava/math/BigDecimal;->toBigIntegerExact()Ljava/math/BigInteger; HSPLjava/math/BigDecimal;->toPlainString()Ljava/lang/String; HSPLjava/math/BigDecimal;->toString()Ljava/lang/String; @@ -2959,8 +3001,6 @@ HSPLjava/math/BigDecimal;->valueOf(J)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->valueOf(JI)Ljava/math/BigDecimal; HSPLjava/math/BigDecimal;->zeroValueOf(I)Ljava/math/BigDecimal; HSPLjava/math/BigInteger$UnsafeHolder;-><clinit>()V -HSPLjava/math/BigInteger$UnsafeHolder;->putMag(Ljava/math/BigInteger;[I)V -HSPLjava/math/BigInteger$UnsafeHolder;->putSign(Ljava/math/BigInteger;I)V HSPLjava/math/BigInteger;-><init>(I[B)V HSPLjava/math/BigInteger;-><init>(I[BII)V HSPLjava/math/BigInteger;-><init>(I[I)V @@ -2996,8 +3036,9 @@ HSPLjava/math/BigInteger;->multiply(Ljava/math/BigInteger;)Ljava/math/BigInteger HSPLjava/math/BigInteger;->multiply(Ljava/math/BigInteger;Z)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->multiplyByInt([III)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->multiplyToLen([II[II[I)[I -HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger;+]Ljava/math/BigInteger;Ljava/math/BigInteger; -HSPLjava/math/BigInteger;->readObject(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$GetField;Ljava/io/ObjectInputStream$GetFieldImpl; +HSPLjava/math/BigInteger;->padWithZeros(Ljava/lang/StringBuilder;I)V +HSPLjava/math/BigInteger;->pow(I)Ljava/math/BigInteger; +HSPLjava/math/BigInteger;->readObject(Ljava/io/ObjectInputStream;)V HSPLjava/math/BigInteger;->remainder(Ljava/math/BigInteger;)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->remainderKnuth(Ljava/math/BigInteger;)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->reverse([I)[I @@ -3007,15 +3048,16 @@ HSPLjava/math/BigInteger;->shiftRight(I)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->shiftRightImpl(I)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->signInt()I HSPLjava/math/BigInteger;->signum()I -HSPLjava/math/BigInteger;->smallToString(I)Ljava/lang/String; +HSPLjava/math/BigInteger;->smallToString(ILjava/lang/StringBuilder;I)V HSPLjava/math/BigInteger;->stripLeadingZeroBytes([BII)[I HSPLjava/math/BigInteger;->stripLeadingZeroInts([I)[I HSPLjava/math/BigInteger;->subtract(Ljava/math/BigInteger;)Ljava/math/BigInteger; HSPLjava/math/BigInteger;->subtract([I[I)[I HSPLjava/math/BigInteger;->testBit(I)Z -HSPLjava/math/BigInteger;->toByteArray()[B+]Ljava/math/BigInteger;Ljava/math/BigInteger; +HSPLjava/math/BigInteger;->toByteArray()[B HSPLjava/math/BigInteger;->toString()Ljava/lang/String; HSPLjava/math/BigInteger;->toString(I)Ljava/lang/String; +HSPLjava/math/BigInteger;->toString(Ljava/math/BigInteger;Ljava/lang/StringBuilder;II)V HSPLjava/math/BigInteger;->trustedStripLeadingZeroInts([I)[I HSPLjava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger; HSPLjava/math/MathContext;->equals(Ljava/lang/Object;)Z @@ -3034,7 +3076,7 @@ HSPLjava/math/MutableBigInteger;->divide(Ljava/math/MutableBigInteger;Ljava/math HSPLjava/math/MutableBigInteger;->divide(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger; HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;)Ljava/math/MutableBigInteger; HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger; -HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger; +HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;+]Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger; HSPLjava/math/MutableBigInteger;->divideOneWord(ILjava/math/MutableBigInteger;)I HSPLjava/math/MutableBigInteger;->getLowestSetBit()I HSPLjava/math/MutableBigInteger;->getMagnitudeArray()[I @@ -3044,6 +3086,7 @@ HSPLjava/math/MutableBigInteger;->primitiveLeftShift(I)V HSPLjava/math/MutableBigInteger;->rightShift(I)V HSPLjava/math/MutableBigInteger;->toBigInteger(I)Ljava/math/BigInteger; HSPLjava/math/MutableBigInteger;->unsignedLongCompare(JJ)Z +HSPLjava/math/RoundingMode;->valueOf(I)Ljava/math/RoundingMode; HSPLjava/math/RoundingMode;->values()[Ljava/math/RoundingMode; HSPLjava/net/AbstractPlainDatagramSocketImpl;-><init>()V HSPLjava/net/AbstractPlainDatagramSocketImpl;->bind(ILjava/net/InetAddress;)V @@ -3071,7 +3114,7 @@ HSPLjava/net/AbstractPlainSocketImpl;->isClosedOrPending()Z HSPLjava/net/AbstractPlainSocketImpl;->isConnectionReset()Z HSPLjava/net/AbstractPlainSocketImpl;->isConnectionResetPending()Z HSPLjava/net/AbstractPlainSocketImpl;->listen(I)V -HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl; +HSPLjava/net/AbstractPlainSocketImpl;->releaseFD()V HSPLjava/net/AbstractPlainSocketImpl;->setOption(ILjava/lang/Object;)V HSPLjava/net/AbstractPlainSocketImpl;->socketClose()V HSPLjava/net/AbstractPlainSocketImpl;->socketPreClose()V @@ -3124,7 +3167,7 @@ HSPLjava/net/DatagramSocket;->createImpl()V HSPLjava/net/DatagramSocket;->getImpl()Ljava/net/DatagramSocketImpl; HSPLjava/net/DatagramSocket;->isBound()Z HSPLjava/net/DatagramSocket;->isClosed()Z -HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V +HSPLjava/net/DatagramSocket;->receive(Ljava/net/DatagramPacket;)V+]Ljava/net/DatagramSocket;Ljava/net/DatagramSocket;,Ljava/net/MulticastSocket;]Ljava/net/DatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl; HSPLjava/net/DatagramSocket;->send(Ljava/net/DatagramPacket;)V HSPLjava/net/DatagramSocket;->setReuseAddress(Z)V HSPLjava/net/DatagramSocket;->setSoTimeout(I)V @@ -3140,8 +3183,8 @@ HSPLjava/net/HttpCookie;-><init>(Ljava/lang/String;Ljava/lang/String;)V HSPLjava/net/HttpCookie;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V HSPLjava/net/HttpCookie;->assignAttribute(Ljava/net/HttpCookie;Ljava/lang/String;Ljava/lang/String;)V HSPLjava/net/HttpCookie;->domainMatches(Ljava/lang/String;Ljava/lang/String;)Z -HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z+]Ljava/net/HttpCookie;Ljava/net/HttpCookie; -HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/net/HttpCookie;->equals(Ljava/lang/Object;)Z +HSPLjava/net/HttpCookie;->equalsIgnoreCase(Ljava/lang/String;Ljava/lang/String;)Z HSPLjava/net/HttpCookie;->getDomain()Ljava/lang/String; HSPLjava/net/HttpCookie;->getMaxAge()J HSPLjava/net/HttpCookie;->getName()Ljava/lang/String; @@ -3176,7 +3219,7 @@ HSPLjava/net/IDN;->toASCII(Ljava/lang/String;)Ljava/lang/String; HSPLjava/net/IDN;->toASCII(Ljava/lang/String;I)Ljava/lang/String; HSPLjava/net/InMemoryCookieStore;-><init>()V HSPLjava/net/InMemoryCookieStore;-><init>(I)V -HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock; +HSPLjava/net/InMemoryCookieStore;->add(Ljava/net/URI;Ljava/net/HttpCookie;)V HSPLjava/net/InMemoryCookieStore;->addIndex(Ljava/util/Map;Ljava/lang/Object;Ljava/net/HttpCookie;)V HSPLjava/net/InMemoryCookieStore;->get(Ljava/net/URI;)Ljava/util/List; HSPLjava/net/InMemoryCookieStore;->getEffectiveURI(Ljava/net/URI;)Ljava/net/URI; @@ -3287,7 +3330,7 @@ HSPLjava/net/PlainDatagramSocketImpl;-><init>()V HSPLjava/net/PlainDatagramSocketImpl;->bind0(ILjava/net/InetAddress;)V HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketClose()V HSPLjava/net/PlainDatagramSocketImpl;->datagramSocketCreate()V -HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V +HSPLjava/net/PlainDatagramSocketImpl;->doRecv(Ljava/net/DatagramPacket;I)V+]Ljava/net/DatagramPacket;Ljava/net/DatagramPacket;]Ljava/net/PlainDatagramSocketImpl;Ljava/net/PlainDatagramSocketImpl; HSPLjava/net/PlainDatagramSocketImpl;->receive0(Ljava/net/DatagramPacket;)V HSPLjava/net/PlainDatagramSocketImpl;->send(Ljava/net/DatagramPacket;)V HSPLjava/net/PlainDatagramSocketImpl;->socketSetOption(ILjava/lang/Object;)V @@ -3379,12 +3422,12 @@ HSPLjava/net/SocketImpl;->setServerSocket(Ljava/net/ServerSocket;)V HSPLjava/net/SocketImpl;->setSocket(Ljava/net/Socket;)V HSPLjava/net/SocketInputStream;-><init>(Ljava/net/AbstractPlainSocketImpl;)V HSPLjava/net/SocketInputStream;->finalize()V -HSPLjava/net/SocketInputStream;->read([BII)I+]Ljava/net/SocketInputStream;Ljava/net/SocketInputStream;]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl; -HSPLjava/net/SocketInputStream;->read([BIII)I+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLjava/net/SocketInputStream;->read([BII)I +HSPLjava/net/SocketInputStream;->read([BIII)I HSPLjava/net/SocketInputStream;->socketRead(Ljava/io/FileDescriptor;[BIII)I HSPLjava/net/SocketOutputStream;-><init>(Ljava/net/AbstractPlainSocketImpl;)V HSPLjava/net/SocketOutputStream;->finalize()V -HSPLjava/net/SocketOutputStream;->socketWrite([BII)V+]Ljava/net/AbstractPlainSocketImpl;Ljava/net/SocksSocketImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLjava/net/SocketOutputStream;->socketWrite([BII)V HSPLjava/net/SocketOutputStream;->write([BII)V HSPLjava/net/SocketTimeoutException;-><init>(Ljava/lang/String;)V HSPLjava/net/SocksSocketImpl;-><init>()V @@ -3434,7 +3477,7 @@ HSPLjava/net/URI;->checkPath(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Stri HSPLjava/net/URI;->compare(Ljava/lang/String;Ljava/lang/String;)I HSPLjava/net/URI;->compareIgnoringCase(Ljava/lang/String;Ljava/lang/String;)I HSPLjava/net/URI;->compareTo(Ljava/lang/Object;)I -HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I+]Ljava/net/URI;Ljava/net/URI; +HSPLjava/net/URI;->compareTo(Ljava/net/URI;)I HSPLjava/net/URI;->create(Ljava/lang/String;)Ljava/net/URI; HSPLjava/net/URI;->decode(Ljava/lang/String;)Ljava/lang/String; HSPLjava/net/URI;->defineString()V @@ -3469,7 +3512,7 @@ HSPLjava/net/URL;-><init>(Ljava/lang/String;)V HSPLjava/net/URL;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V HSPLjava/net/URL;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/net/URLStreamHandler;)V HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;)V -HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler; +HSPLjava/net/URL;-><init>(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V HSPLjava/net/URL;->createBuiltinHandler(Ljava/lang/String;)Ljava/net/URLStreamHandler; HSPLjava/net/URL;->getAuthority()Ljava/lang/String; HSPLjava/net/URL;->getFile()Ljava/lang/String; @@ -3503,14 +3546,14 @@ HSPLjava/net/URLConnection;->setDoOutput(Z)V HSPLjava/net/URLConnection;->setReadTimeout(I)V HSPLjava/net/URLConnection;->setUseCaches(Z)V HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; -HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; +HSPLjava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/net/URLDecoder;->isValidHexChar(C)Z HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; HSPLjava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/BitSet;Ljava/util/BitSet;]Ljava/io/CharArrayWriter;Ljava/io/CharArrayWriter; HSPLjava/net/URLStreamHandler;-><init>()V HSPLjava/net/URLStreamHandler;->parseURL(Ljava/net/URL;Ljava/lang/String;II)V+]Ljava/net/URLStreamHandler;Lcom/android/okhttp/HttpsHandler;]Ljava/lang/String;Ljava/lang/String;]Ljava/net/URL;Ljava/net/URL; HSPLjava/net/URLStreamHandler;->setURL(Ljava/net/URL;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V -HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/net/URL;Ljava/net/URL; +HSPLjava/net/URLStreamHandler;->toExternalForm(Ljava/net/URL;)Ljava/lang/String; HSPLjava/net/UnknownHostException;-><init>(Ljava/lang/String;)V HSPLjava/nio/Bits;->byteOrder()Ljava/nio/ByteOrder; HSPLjava/nio/Bits;->char0(C)B @@ -3518,13 +3561,13 @@ HSPLjava/nio/Bits;->char1(C)B HSPLjava/nio/Bits;->getFloat(Ljava/nio/ByteBuffer;IZ)F HSPLjava/nio/Bits;->getFloatL(Ljava/nio/ByteBuffer;I)F HSPLjava/nio/Bits;->getInt(Ljava/nio/ByteBuffer;IZ)I -HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I -HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/Bits;->getIntB(Ljava/nio/ByteBuffer;I)I+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/Bits;->getIntL(Ljava/nio/ByteBuffer;I)I HSPLjava/nio/Bits;->getLong(Ljava/nio/ByteBuffer;IZ)J HSPLjava/nio/Bits;->getLongB(Ljava/nio/ByteBuffer;I)J HSPLjava/nio/Bits;->getLongL(Ljava/nio/ByteBuffer;I)J HSPLjava/nio/Bits;->getShort(Ljava/nio/ByteBuffer;IZ)S -HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/Bits;->getShortB(Ljava/nio/ByteBuffer;I)S HSPLjava/nio/Bits;->getShortL(Ljava/nio/ByteBuffer;I)S HSPLjava/nio/Bits;->int0(I)B HSPLjava/nio/Bits;->int1(I)B @@ -3548,8 +3591,8 @@ HSPLjava/nio/Bits;->putCharB(Ljava/nio/ByteBuffer;IC)V HSPLjava/nio/Bits;->putCharL(Ljava/nio/ByteBuffer;IC)V HSPLjava/nio/Bits;->putFloat(Ljava/nio/ByteBuffer;IFZ)V HSPLjava/nio/Bits;->putInt(Ljava/nio/ByteBuffer;IIZ)V -HSPLjava/nio/Bits;->putIntB(Ljava/nio/ByteBuffer;II)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; -HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/Bits;->putIntB(Ljava/nio/ByteBuffer;II)V +HSPLjava/nio/Bits;->putIntL(Ljava/nio/ByteBuffer;II)V HSPLjava/nio/Bits;->putLong(Ljava/nio/ByteBuffer;IJZ)V HSPLjava/nio/Bits;->putLongB(Ljava/nio/ByteBuffer;IJ)V HSPLjava/nio/Bits;->putLongL(Ljava/nio/ByteBuffer;IJ)V @@ -3559,7 +3602,7 @@ HSPLjava/nio/Bits;->putShortL(Ljava/nio/ByteBuffer;IS)V HSPLjava/nio/Bits;->short0(S)B HSPLjava/nio/Bits;->short1(S)B HSPLjava/nio/Bits;->unsafe()Lsun/misc/Unsafe; -HSPLjava/nio/Buffer;-><init>(IIIII)V+]Ljava/nio/Buffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/HeapCharBuffer;,Ljava/nio/DirectByteBuffer;,Ljava/nio/ByteBufferAsCharBuffer; +HSPLjava/nio/Buffer;-><init>(IIIII)V+]Ljava/nio/Buffer;megamorphic_types HSPLjava/nio/Buffer;->capacity()I HSPLjava/nio/Buffer;->checkBounds(III)V HSPLjava/nio/Buffer;->checkIndex(I)I @@ -3590,6 +3633,7 @@ HSPLjava/nio/ByteBuffer;->array()[B HSPLjava/nio/ByteBuffer;->arrayOffset()I HSPLjava/nio/ByteBuffer;->clear()Ljava/nio/Buffer; HSPLjava/nio/ByteBuffer;->compare(BB)I +HSPLjava/nio/ByteBuffer;->compareTo(Ljava/lang/Object;)I HSPLjava/nio/ByteBuffer;->compareTo(Ljava/nio/ByteBuffer;)I HSPLjava/nio/ByteBuffer;->equals(BB)Z HSPLjava/nio/ByteBuffer;->equals(Ljava/lang/Object;)Z @@ -3602,7 +3646,7 @@ HSPLjava/nio/ByteBuffer;->mark()Ljava/nio/Buffer; HSPLjava/nio/ByteBuffer;->order()Ljava/nio/ByteOrder; HSPLjava/nio/ByteBuffer;->order(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer; HSPLjava/nio/ByteBuffer;->position(I)Ljava/nio/Buffer; -HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;,Ljava/nio/DirectByteBuffer; +HSPLjava/nio/ByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer; HSPLjava/nio/ByteBuffer;->put([B)Ljava/nio/ByteBuffer; HSPLjava/nio/ByteBuffer;->reset()Ljava/nio/Buffer; HSPLjava/nio/ByteBuffer;->rewind()Ljava/nio/Buffer; @@ -3610,7 +3654,7 @@ HSPLjava/nio/ByteBuffer;->wrap([B)Ljava/nio/ByteBuffer; HSPLjava/nio/ByteBuffer;->wrap([BII)Ljava/nio/ByteBuffer; HSPLjava/nio/ByteBufferAsCharBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V HSPLjava/nio/ByteBufferAsCharBuffer;->duplicate()Ljava/nio/CharBuffer; -HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C +HSPLjava/nio/ByteBufferAsCharBuffer;->get(I)C+]Ljava/nio/ByteBuffer;Ljava/nio/DirectByteBuffer;]Ljava/nio/ByteBufferAsCharBuffer;Ljava/nio/ByteBufferAsCharBuffer; HSPLjava/nio/ByteBufferAsCharBuffer;->get([CII)Ljava/nio/CharBuffer; HSPLjava/nio/ByteBufferAsCharBuffer;->isDirect()Z HSPLjava/nio/ByteBufferAsCharBuffer;->ix(I)I @@ -3624,7 +3668,9 @@ HSPLjava/nio/ByteBufferAsFloatBuffer;->put([FII)Ljava/nio/FloatBuffer; HSPLjava/nio/ByteBufferAsIntBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V HSPLjava/nio/ByteBufferAsIntBuffer;->get([III)Ljava/nio/IntBuffer; HSPLjava/nio/ByteBufferAsIntBuffer;->ix(I)I -HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer;+]Ljava/nio/ByteBufferAsIntBuffer;Ljava/nio/ByteBufferAsIntBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/ByteBufferAsIntBuffer;->put(I)Ljava/nio/IntBuffer; +HSPLjava/nio/ByteBufferAsIntBuffer;->put(II)Ljava/nio/IntBuffer; +HSPLjava/nio/ByteBufferAsIntBuffer;->put([III)Ljava/nio/IntBuffer; HSPLjava/nio/ByteBufferAsLongBuffer;-><init>(Ljava/nio/ByteBuffer;IIIIILjava/nio/ByteOrder;)V HSPLjava/nio/ByteBufferAsLongBuffer;->get([JII)Ljava/nio/LongBuffer; HSPLjava/nio/ByteBufferAsLongBuffer;->ix(I)I @@ -3644,7 +3690,7 @@ HSPLjava/nio/CharBuffer;->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 +3698,7 @@ HSPLjava/nio/CharBuffer;->wrap(Ljava/lang/CharSequence;)Ljava/nio/CharBuffer; HSPLjava/nio/CharBuffer;->wrap(Ljava/lang/CharSequence;II)Ljava/nio/CharBuffer; HSPLjava/nio/CharBuffer;->wrap([C)Ljava/nio/CharBuffer; HSPLjava/nio/CharBuffer;->wrap([CII)Ljava/nio/CharBuffer; -HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(I)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime; +HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(I)V HSPLjava/nio/DirectByteBuffer$MemoryRef;-><init>(JLjava/lang/Object;)V HSPLjava/nio/DirectByteBuffer$MemoryRef;->free()V HSPLjava/nio/DirectByteBuffer;-><init>(IJLjava/io/FileDescriptor;Ljava/lang/Runnable;Z)V @@ -3663,10 +3709,11 @@ HSPLjava/nio/DirectByteBuffer;->address()J HSPLjava/nio/DirectByteBuffer;->asCharBuffer()Ljava/nio/CharBuffer; HSPLjava/nio/DirectByteBuffer;->asFloatBuffer()Ljava/nio/FloatBuffer; HSPLjava/nio/DirectByteBuffer;->asIntBuffer()Ljava/nio/IntBuffer; -HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer; +HSPLjava/nio/DirectByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer; HSPLjava/nio/DirectByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer; HSPLjava/nio/DirectByteBuffer;->cleaner()Lsun/misc/Cleaner; HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/ByteBuffer; +HSPLjava/nio/DirectByteBuffer;->duplicate()Ljava/nio/MappedByteBuffer; HSPLjava/nio/DirectByteBuffer;->get()B HSPLjava/nio/DirectByteBuffer;->get(I)B HSPLjava/nio/DirectByteBuffer;->get(J)B @@ -3680,7 +3727,7 @@ HSPLjava/nio/DirectByteBuffer;->getInt(J)I HSPLjava/nio/DirectByteBuffer;->getLong(I)J HSPLjava/nio/DirectByteBuffer;->getLong(J)J HSPLjava/nio/DirectByteBuffer;->getShort()S -HSPLjava/nio/DirectByteBuffer;->getShort(I)S +HSPLjava/nio/DirectByteBuffer;->getShort(I)S+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer; HSPLjava/nio/DirectByteBuffer;->getShort(J)S HSPLjava/nio/DirectByteBuffer;->getUnchecked(I[CII)V HSPLjava/nio/DirectByteBuffer;->getUnchecked(I[III)V @@ -3692,7 +3739,7 @@ HSPLjava/nio/DirectByteBuffer;->put(B)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->put(IB)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->put(JB)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->put(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer; -HSPLjava/nio/DirectByteBuffer;->put([BII)Ljava/nio/ByteBuffer; +HSPLjava/nio/DirectByteBuffer;->put([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/DirectByteBuffer;Ljava/nio/DirectByteBuffer; HSPLjava/nio/DirectByteBuffer;->putDouble(JD)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->putFloat(JF)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->putFloatUnchecked(IF)V @@ -3705,6 +3752,7 @@ HSPLjava/nio/DirectByteBuffer;->putLong(JJ)Ljava/nio/ByteBuffer; HSPLjava/nio/DirectByteBuffer;->putUnchecked(I[FII)V HSPLjava/nio/DirectByteBuffer;->setAccessible(Z)V HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/ByteBuffer; +HSPLjava/nio/DirectByteBuffer;->slice()Ljava/nio/MappedByteBuffer; HSPLjava/nio/FloatBuffer;-><init>(IIII)V HSPLjava/nio/FloatBuffer;-><init>(IIII[FI)V HSPLjava/nio/FloatBuffer;->limit(I)Ljava/nio/Buffer; @@ -3721,25 +3769,25 @@ HSPLjava/nio/HeapByteBuffer;->asIntBuffer()Ljava/nio/IntBuffer; HSPLjava/nio/HeapByteBuffer;->asLongBuffer()Ljava/nio/LongBuffer; HSPLjava/nio/HeapByteBuffer;->asReadOnlyBuffer()Ljava/nio/ByteBuffer; HSPLjava/nio/HeapByteBuffer;->asShortBuffer()Ljava/nio/ShortBuffer; -HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->compact()Ljava/nio/ByteBuffer; HSPLjava/nio/HeapByteBuffer;->duplicate()Ljava/nio/ByteBuffer; -HSPLjava/nio/HeapByteBuffer;->get()B -HSPLjava/nio/HeapByteBuffer;->get(I)B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; -HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer; +HSPLjava/nio/HeapByteBuffer;->get()B+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->get(I)B +HSPLjava/nio/HeapByteBuffer;->get([BII)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; HSPLjava/nio/HeapByteBuffer;->getFloat()F HSPLjava/nio/HeapByteBuffer;->getFloat(I)F -HSPLjava/nio/HeapByteBuffer;->getInt()I -HSPLjava/nio/HeapByteBuffer;->getInt(I)I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->getInt()I+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->getInt(I)I HSPLjava/nio/HeapByteBuffer;->getLong()J HSPLjava/nio/HeapByteBuffer;->getLong(I)J HSPLjava/nio/HeapByteBuffer;->getShort()S -HSPLjava/nio/HeapByteBuffer;->getShort(I)S+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->getShort(I)S HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[III)V HSPLjava/nio/HeapByteBuffer;->getUnchecked(I[SII)V HSPLjava/nio/HeapByteBuffer;->isDirect()Z HSPLjava/nio/HeapByteBuffer;->isReadOnly()Z HSPLjava/nio/HeapByteBuffer;->ix(I)I -HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer;+]Ljava/nio/HeapByteBuffer;Ljava/nio/HeapByteBuffer; +HSPLjava/nio/HeapByteBuffer;->put(B)Ljava/nio/ByteBuffer; HSPLjava/nio/HeapByteBuffer;->put(IB)Ljava/nio/ByteBuffer; HSPLjava/nio/HeapByteBuffer;->put([BII)Ljava/nio/ByteBuffer; HSPLjava/nio/HeapByteBuffer;->putChar(C)Ljava/nio/ByteBuffer; @@ -3756,9 +3804,9 @@ HSPLjava/nio/HeapCharBuffer;-><init>(IIZ)V HSPLjava/nio/HeapCharBuffer;-><init>([CII)V HSPLjava/nio/HeapCharBuffer;-><init>([CIIIIIZ)V HSPLjava/nio/HeapCharBuffer;-><init>([CIIZ)V -HSPLjava/nio/HeapCharBuffer;->get(I)C+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer; +HSPLjava/nio/HeapCharBuffer;->get(I)C HSPLjava/nio/HeapCharBuffer;->ix(I)I -HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/HeapCharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/CharBuffer;Ljava/nio/ByteBufferAsCharBuffer; +HSPLjava/nio/HeapCharBuffer;->put(Ljava/nio/CharBuffer;)Ljava/nio/CharBuffer; HSPLjava/nio/HeapCharBuffer;->put([CII)Ljava/nio/CharBuffer; HSPLjava/nio/HeapCharBuffer;->slice()Ljava/nio/CharBuffer; HSPLjava/nio/HeapCharBuffer;->toString(II)Ljava/lang/String; @@ -3787,7 +3835,7 @@ HSPLjava/nio/MappedByteBuffer;->mappingLength(J)J HSPLjava/nio/MappedByteBuffer;->mappingOffset()J HSPLjava/nio/NIOAccess;->getBaseArray(Ljava/nio/Buffer;)Ljava/lang/Object; HSPLjava/nio/NIOAccess;->getBaseArrayOffset(Ljava/nio/Buffer;)I -HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V +HSPLjava/nio/NioUtils;->freeDirectBuffer(Ljava/nio/ByteBuffer;)V+]Ljava/nio/DirectByteBuffer$MemoryRef;Ljava/nio/DirectByteBuffer$MemoryRef; HSPLjava/nio/ShortBuffer;-><init>(IIII)V HSPLjava/nio/ShortBuffer;-><init>(IIII[SI)V HSPLjava/nio/ShortBuffer;->get([S)Ljava/nio/ShortBuffer; @@ -3824,9 +3872,9 @@ HSPLjava/nio/channels/SocketChannel;->open()Ljava/nio/channels/SocketChannel; HSPLjava/nio/channels/SocketChannel;->validOps()I HSPLjava/nio/channels/spi/AbstractInterruptibleChannel$1;-><init>(Ljava/nio/channels/spi/AbstractInterruptibleChannel;)V HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;-><init>()V -HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V+]Ljava/lang/Thread;Landroid/os/HandlerThread; -HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V+]Ljava/lang/Thread;Landroid/os/HandlerThread; -HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V +HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->begin()V +HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->blockedOn(Lsun/nio/ch/Interruptible;)V +HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->close()V+]Ljava/nio/channels/spi/AbstractInterruptibleChannel;Lsun/nio/ch/FileChannelImpl; HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->end(Z)V HSPLjava/nio/channels/spi/AbstractInterruptibleChannel;->isOpen()Z HSPLjava/nio/channels/spi/AbstractSelectableChannel;-><init>(Ljava/nio/channels/spi/SelectorProvider;)V @@ -3859,7 +3907,6 @@ HSPLjava/nio/channels/spi/SelectorProvider;->loadProviderFromProperty()Z HSPLjava/nio/channels/spi/SelectorProvider;->provider()Ljava/nio/channels/spi/SelectorProvider; HSPLjava/nio/charset/Charset;-><init>(Ljava/lang/String;[Ljava/lang/String;)V HSPLjava/nio/charset/Charset;->aliases()Ljava/util/Set; -HSPLjava/nio/charset/Charset;->atBugLevel(Ljava/lang/String;)Z HSPLjava/nio/charset/Charset;->cache(Ljava/lang/String;Ljava/nio/charset/Charset;)V HSPLjava/nio/charset/Charset;->checkName(Ljava/lang/String;)V HSPLjava/nio/charset/Charset;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer; @@ -3878,7 +3925,7 @@ HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FF)V HSPLjava/nio/charset/CharsetDecoder;-><init>(Ljava/nio/charset/Charset;FFLjava/lang/String;)V HSPLjava/nio/charset/CharsetDecoder;->averageCharsPerByte()F HSPLjava/nio/charset/CharsetDecoder;->charset()Ljava/nio/charset/Charset; -HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; +HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer; HSPLjava/nio/charset/CharsetDecoder;->decode(Ljava/nio/ByteBuffer;Ljava/nio/CharBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; HSPLjava/nio/charset/CharsetDecoder;->flush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; HSPLjava/nio/charset/CharsetDecoder;->implFlush(Ljava/nio/CharBuffer;)Ljava/nio/charset/CoderResult; @@ -3888,7 +3935,7 @@ 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;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder; HSPLjava/nio/charset/CharsetDecoder;->replaceWith(Ljava/lang/String;)Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; HSPLjava/nio/charset/CharsetDecoder;->replacement()Ljava/lang/String; HSPLjava/nio/charset/CharsetDecoder;->reset()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; @@ -3901,8 +3948,8 @@ HSPLjava/nio/charset/CharsetEncoder;->canEncode(Ljava/lang/CharSequence;)Z HSPLjava/nio/charset/CharsetEncoder;->canEncode(Ljava/nio/CharBuffer;)Z HSPLjava/nio/charset/CharsetEncoder;->charset()Ljava/nio/charset/Charset; HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;)Ljava/nio/ByteBuffer; -HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult; -HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult; +HSPLjava/nio/charset/CharsetEncoder;->encode(Ljava/nio/CharBuffer;Ljava/nio/ByteBuffer;Z)Ljava/nio/charset/CoderResult;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult; +HSPLjava/nio/charset/CharsetEncoder;->flush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult; HSPLjava/nio/charset/CharsetEncoder;->implFlush(Ljava/nio/ByteBuffer;)Ljava/nio/charset/CoderResult; HSPLjava/nio/charset/CharsetEncoder;->implOnMalformedInput(Ljava/nio/charset/CodingErrorAction;)V HSPLjava/nio/charset/CharsetEncoder;->implOnUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)V @@ -3913,7 +3960,7 @@ HSPLjava/nio/charset/CharsetEncoder;->onMalformedInput(Ljava/nio/charset/CodingE HSPLjava/nio/charset/CharsetEncoder;->onUnmappableCharacter(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetEncoder; HSPLjava/nio/charset/CharsetEncoder;->replaceWith([B)Ljava/nio/charset/CharsetEncoder; HSPLjava/nio/charset/CharsetEncoder;->replacement()[B -HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder; +HSPLjava/nio/charset/CharsetEncoder;->reset()Ljava/nio/charset/CharsetEncoder;+]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU; HSPLjava/nio/charset/CharsetEncoder;->unmappableCharacterAction()Ljava/nio/charset/CodingErrorAction; HSPLjava/nio/charset/CoderResult;->isError()Z HSPLjava/nio/charset/CoderResult;->isOverflow()Z @@ -3944,6 +3991,7 @@ HSPLjava/nio/file/StandardOpenOption;->values()[Ljava/nio/file/StandardOpenOptio HSPLjava/nio/file/attribute/FileTime;-><init>(JLjava/util/concurrent/TimeUnit;Ljava/time/Instant;)V HSPLjava/nio/file/attribute/FileTime;->append(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder; HSPLjava/nio/file/attribute/FileTime;->from(JLjava/util/concurrent/TimeUnit;)Ljava/nio/file/attribute/FileTime; +HSPLjava/nio/file/attribute/FileTime;->toMillis()J HSPLjava/nio/file/attribute/FileTime;->toString()Ljava/lang/String; HSPLjava/nio/file/spi/FileSystemProvider;->newInputStream(Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream; HSPLjava/security/AccessControlContext;-><init>([Ljava/security/ProtectionDomain;)V @@ -3955,7 +4003,7 @@ HSPLjava/security/AlgorithmParametersSpi;-><init>()V HSPLjava/security/CodeSigner;-><init>(Ljava/security/cert/CertPath;Ljava/security/Timestamp;)V HSPLjava/security/CodeSigner;->getSignerCertPath()Ljava/security/cert/CertPath; HSPLjava/security/DigestInputStream;-><init>(Ljava/io/InputStream;Ljava/security/MessageDigest;)V -HSPLjava/security/DigestInputStream;->read([BII)I+]Ljava/io/InputStream;Ljava/io/FileInputStream;]Ljava/security/MessageDigest;Ljava/security/MessageDigest$Delegate; +HSPLjava/security/DigestInputStream;->read([BII)I HSPLjava/security/DigestInputStream;->setMessageDigest(Ljava/security/MessageDigest;)V HSPLjava/security/GeneralSecurityException;-><init>(Ljava/lang/String;)V HSPLjava/security/KeyFactory;-><init>(Ljava/lang/String;)V @@ -4001,7 +4049,7 @@ HSPLjava/security/MessageDigest$Delegate;->engineGetDigestLength()I HSPLjava/security/MessageDigest$Delegate;->engineReset()V HSPLjava/security/MessageDigest$Delegate;->engineUpdate(B)V HSPLjava/security/MessageDigest$Delegate;->engineUpdate(Ljava/nio/ByteBuffer;)V -HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V +HSPLjava/security/MessageDigest$Delegate;->engineUpdate([BII)V+]Ljava/security/MessageDigestSpi;missing_types HSPLjava/security/MessageDigest;-><init>(Ljava/lang/String;)V HSPLjava/security/MessageDigest;->digest()[B HSPLjava/security/MessageDigest;->digest([B)[B @@ -4033,20 +4081,20 @@ HSPLjava/security/Provider$Service;->addAttribute(Ljava/lang/String;Ljava/lang/S HSPLjava/security/Provider$Service;->getAlgorithm()Ljava/lang/String; HSPLjava/security/Provider$Service;->getAttribute(Ljava/lang/String;)Ljava/lang/String; HSPLjava/security/Provider$Service;->getClassName()Ljava/lang/String; -HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class; +HSPLjava/security/Provider$Service;->getImplClass()Ljava/lang/Class;+]Ljava/lang/ref/Reference;Ljava/lang/ref/WeakReference; HSPLjava/security/Provider$Service;->getKeyClass(Ljava/lang/String;)Ljava/lang/Class; HSPLjava/security/Provider$Service;->getProvider()Ljava/security/Provider; HSPLjava/security/Provider$Service;->getType()Ljava/lang/String; HSPLjava/security/Provider$Service;->hasKeyAttributes()Z HSPLjava/security/Provider$Service;->isValid()Z -HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/security/Provider$Service;->newInstance(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/security/Provider;missing_types]Ljava/util/Map;Ljava/util/HashMap;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/lang/reflect/Constructor;Ljava/lang/reflect/Constructor; HSPLjava/security/Provider$Service;->supportsKeyClass(Ljava/security/Key;)Z HSPLjava/security/Provider$Service;->supportsKeyFormat(Ljava/security/Key;)Z HSPLjava/security/Provider$Service;->supportsParameter(Ljava/lang/Object;)Z HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V HSPLjava/security/Provider$ServiceKey;-><init>(Ljava/lang/String;Ljava/lang/String;ZLjava/security/Provider$ServiceKey-IA;)V HSPLjava/security/Provider$ServiceKey;->equals(Ljava/lang/Object;)Z -HSPLjava/security/Provider$ServiceKey;->hashCode()I +HSPLjava/security/Provider$ServiceKey;->hashCode()I+]Ljava/lang/String;Ljava/lang/String; HSPLjava/security/Provider$ServiceKey;->matches(Ljava/lang/String;Ljava/lang/String;)Z HSPLjava/security/Provider$UString;-><init>(Ljava/lang/String;)V HSPLjava/security/Provider$UString;->equals(Ljava/lang/Object;)Z @@ -4059,11 +4107,11 @@ HSPLjava/security/Provider;->checkLegacy(Ljava/lang/Object;)Z HSPLjava/security/Provider;->ensureLegacyParsed()V HSPLjava/security/Provider;->getEngineName(Ljava/lang/String;)Ljava/lang/String; HSPLjava/security/Provider;->getName()Ljava/lang/String; -HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service; +HSPLjava/security/Provider;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Ljava/security/Provider$ServiceKey;Ljava/security/Provider$ServiceKey;]Ljava/util/Map;Ljava/util/LinkedHashMap; HSPLjava/security/Provider;->getServices()Ljava/util/Set; HSPLjava/security/Provider;->getTypeAndAlgorithm(Ljava/lang/String;)[Ljava/lang/String; HSPLjava/security/Provider;->implPut(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/security/Provider;->parseLegacyPut(Ljava/lang/String;Ljava/lang/String;)V +HSPLjava/security/Provider;->parseLegacyPut(Ljava/lang/String;Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Ljava/security/Provider$Service;Ljava/security/Provider$Service;]Ljava/util/Map;Ljava/util/LinkedHashMap; HSPLjava/security/Provider;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/security/Provider;->putId()V HSPLjava/security/Provider;->removeInvalidServices(Ljava/util/Map;)V @@ -4080,7 +4128,7 @@ HSPLjava/security/SecureRandom;->nextBytes([B)V HSPLjava/security/SecureRandom;->setSeed(J)V HSPLjava/security/SecureRandomSpi;-><init>()V HSPLjava/security/Security;->addProvider(Ljava/security/Provider;)I -HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object; +HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;+]Lsun/security/jca/GetInstance$Instance;Lsun/security/jca/GetInstance$Instance; HSPLjava/security/Security;->getImpl(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)[Ljava/lang/Object; HSPLjava/security/Security;->getProperty(Ljava/lang/String;)Ljava/lang/String; HSPLjava/security/Security;->getProvider(Ljava/lang/String;)Ljava/security/Provider; @@ -4205,7 +4253,6 @@ HSPLjava/security/spec/EllipticCurve;->checkValidity(Ljava/security/spec/ECField HSPLjava/security/spec/EllipticCurve;->getField()Ljava/security/spec/ECField; HSPLjava/security/spec/EncodedKeySpec;-><init>([B)V HSPLjava/security/spec/EncodedKeySpec;->getEncoded()[B -HSPLjava/security/spec/NamedParameterSpec;->getName()Ljava/lang/String; HSPLjava/security/spec/PKCS8EncodedKeySpec;-><init>([B)V HSPLjava/security/spec/PKCS8EncodedKeySpec;->getEncoded()[B HSPLjava/security/spec/X509EncodedKeySpec;-><init>([B)V @@ -4226,7 +4273,7 @@ HSPLjava/text/Collator;->getInstance(Ljava/util/Locale;)Ljava/text/Collator; HSPLjava/text/Collator;->setDecomposition(I)V HSPLjava/text/Collator;->setStrength(I)V HSPLjava/text/DateFormat;-><init>()V -HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; +HSPLjava/text/DateFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/DateFormat;Ljava/text/SimpleDateFormat; HSPLjava/text/DateFormat;->format(Ljava/util/Date;)Ljava/lang/String; HSPLjava/text/DateFormat;->get(IIILjava/util/Locale;)Ljava/text/DateFormat; HSPLjava/text/DateFormat;->getDateInstance(ILjava/util/Locale;)Ljava/text/DateFormat; @@ -4255,10 +4302,10 @@ HSPLjava/text/DateFormatSymbols;->initializeData(Ljava/util/Locale;)V HSPLjava/text/DateFormatSymbols;->initializeSupplementaryData(Llibcore/icu/LocaleData;)V HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;)V HSPLjava/text/DecimalFormat;-><init>(Ljava/lang/String;Ljava/text/DecimalFormatSymbols;)V -HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat; +HSPLjava/text/DecimalFormat;->clone()Ljava/lang/Object; HSPLjava/text/DecimalFormat;->equals(Ljava/lang/Object;)Z HSPLjava/text/DecimalFormat;->format(DLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; -HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; +HSPLjava/text/DecimalFormat;->format(JLjava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/DontCareFieldPosition;]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat; HSPLjava/text/DecimalFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; HSPLjava/text/DecimalFormat;->getDecimalFormatSymbols()Ljava/text/DecimalFormatSymbols; HSPLjava/text/DecimalFormat;->getIcuFieldPosition(Ljava/text/FieldPosition;)Ljava/text/FieldPosition; @@ -4282,20 +4329,21 @@ HSPLjava/text/DecimalFormat;->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;->setParseIntegerOnly(Z)V HSPLjava/text/DecimalFormat;->toPattern()Ljava/lang/String; -HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V +HSPLjava/text/DecimalFormat;->updateFieldsFromIcu()V+]Landroid/icu/text/DecimalFormat;Landroid/icu/text/DecimalFormat; HSPLjava/text/DecimalFormatSymbols;-><init>(Ljava/util/Locale;)V HSPLjava/text/DecimalFormatSymbols;->clone()Ljava/lang/Object; +HSPLjava/text/DecimalFormatSymbols;->findNonFormatChar(Ljava/lang/String;C)C HSPLjava/text/DecimalFormatSymbols;->fromIcuInstance(Landroid/icu/text/DecimalFormatSymbols;)Ljava/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols;]Landroid/icu/util/Currency;Landroid/icu/util/Currency; HSPLjava/text/DecimalFormatSymbols;->getCurrency()Ljava/util/Currency; HSPLjava/text/DecimalFormatSymbols;->getDecimalSeparator()C HSPLjava/text/DecimalFormatSymbols;->getGroupingSeparator()C -HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols; +HSPLjava/text/DecimalFormatSymbols;->getIcuDecimalFormatSymbols()Landroid/icu/text/DecimalFormatSymbols;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/util/Currency;Ljava/util/Currency;]Landroid/icu/text/DecimalFormatSymbols;Landroid/icu/text/DecimalFormatSymbols; HSPLjava/text/DecimalFormatSymbols;->getInfinity()Ljava/lang/String; HSPLjava/text/DecimalFormatSymbols;->getInstance(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols; HSPLjava/text/DecimalFormatSymbols;->getNaN()Ljava/lang/String; HSPLjava/text/DecimalFormatSymbols;->getZeroDigit()C HSPLjava/text/DecimalFormatSymbols;->initialize(Ljava/util/Locale;)V+]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData; -HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V+]Ljava/util/Currency;Ljava/util/Currency;]Ljava/util/Locale;Ljava/util/Locale; +HSPLjava/text/DecimalFormatSymbols;->initializeCurrency(Ljava/util/Locale;)V HSPLjava/text/DecimalFormatSymbols;->maybeStripMarkers(Ljava/lang/String;C)C HSPLjava/text/DecimalFormatSymbols;->setCurrency(Ljava/util/Currency;)V HSPLjava/text/DecimalFormatSymbols;->setCurrencySymbol(Ljava/lang/String;)V @@ -4307,6 +4355,7 @@ HSPLjava/text/DecimalFormatSymbols;->setInfinity(Ljava/lang/String;)V HSPLjava/text/DecimalFormatSymbols;->setInternationalCurrencySymbol(Ljava/lang/String;)V HSPLjava/text/DecimalFormatSymbols;->setMinusSign(C)V HSPLjava/text/DecimalFormatSymbols;->setMonetaryDecimalSeparator(C)V +HSPLjava/text/DecimalFormatSymbols;->setMonetaryGroupingSeparator(C)V HSPLjava/text/DecimalFormatSymbols;->setNaN(Ljava/lang/String;)V HSPLjava/text/DecimalFormatSymbols;->setPatternSeparator(C)V HSPLjava/text/DecimalFormatSymbols;->setPerMill(C)V @@ -4327,12 +4376,12 @@ HSPLjava/text/FieldPosition;->setBeginIndex(I)V HSPLjava/text/FieldPosition;->setEndIndex(I)V HSPLjava/text/Format;-><init>()V HSPLjava/text/Format;->clone()Ljava/lang/Object; -HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String; +HSPLjava/text/Format;->format(Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/Format;Ljava/text/SimpleDateFormat; HSPLjava/text/IcuIteratorWrapper;-><init>(Landroid/icu/text/BreakIterator;)V HSPLjava/text/IcuIteratorWrapper;->checkOffset(ILjava/text/CharacterIterator;)V -HSPLjava/text/IcuIteratorWrapper;->following(I)I+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper; +HSPLjava/text/IcuIteratorWrapper;->following(I)I HSPLjava/text/IcuIteratorWrapper;->getText()Ljava/text/CharacterIterator; -HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z+]Landroid/icu/text/BreakIterator;Landroid/icu/text/RuleBasedBreakIterator;]Ljava/text/IcuIteratorWrapper;Ljava/text/IcuIteratorWrapper; +HSPLjava/text/IcuIteratorWrapper;->isBoundary(I)Z HSPLjava/text/IcuIteratorWrapper;->next()I HSPLjava/text/IcuIteratorWrapper;->preceding(I)I HSPLjava/text/IcuIteratorWrapper;->setText(Ljava/lang/String;)V @@ -4341,7 +4390,7 @@ HSPLjava/text/MessageFormat;->applyPattern(Ljava/lang/String;)V HSPLjava/text/MessageFormat;->format(Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; HSPLjava/text/MessageFormat;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; HSPLjava/text/MessageFormat;->makeFormat(II[Ljava/lang/StringBuilder;)V -HSPLjava/text/MessageFormat;->subformat([Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;Ljava/util/List;)Ljava/lang/StringBuffer;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/FieldPosition;Ljava/text/FieldPosition;]Ljava/text/MessageFormat$Field;Ljava/text/MessageFormat$Field; +HSPLjava/text/MessageFormat;->subformat([Ljava/lang/Object;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;Ljava/util/List;)Ljava/lang/StringBuffer; HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda0;->get()Ljava/lang/Object; HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda2;->get()Ljava/lang/Object; HSPLjava/text/Normalizer$Form$$ExternalSyntheticLambda3;->get()Ljava/lang/Object; @@ -4352,7 +4401,7 @@ HSPLjava/text/NumberFormat;->format(D)Ljava/lang/String; HSPLjava/text/NumberFormat;->format(J)Ljava/lang/String; HSPLjava/text/NumberFormat;->getInstance()Ljava/text/NumberFormat; HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;)Ljava/text/NumberFormat; -HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;I)Ljava/text/NumberFormat; +HSPLjava/text/NumberFormat;->getInstance(Ljava/util/Locale;Ljava/text/NumberFormat$Style;I)Ljava/text/NumberFormat; HSPLjava/text/NumberFormat;->getIntegerInstance()Ljava/text/NumberFormat; HSPLjava/text/NumberFormat;->getIntegerInstance(Ljava/util/Locale;)Ljava/text/NumberFormat; HSPLjava/text/NumberFormat;->getNumberInstance(Ljava/util/Locale;)Ljava/text/NumberFormat; @@ -4378,16 +4427,16 @@ HSPLjava/text/SimpleDateFormat;-><init>(IILjava/util/Locale;)V HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;)V HSPLjava/text/SimpleDateFormat;-><init>(Ljava/lang/String;Ljava/util/Locale;)V HSPLjava/text/SimpleDateFormat;->checkNegativeNumberExpression()V -HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; +HSPLjava/text/SimpleDateFormat;->compile(Ljava/lang/String;)[C HSPLjava/text/SimpleDateFormat;->encode(IILjava/lang/StringBuilder;)V -HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer; -HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer; +HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/FieldPosition;)Ljava/lang/StringBuffer;+]Ljava/text/FieldPosition;Ljava/text/FieldPosition; +HSPLjava/text/SimpleDateFormat;->format(Ljava/util/Date;Ljava/lang/StringBuffer;Ljava/text/Format$FieldDelegate;)Ljava/lang/StringBuffer;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar; HSPLjava/text/SimpleDateFormat;->formatMonth(IIILjava/lang/StringBuffer;ZZII)Ljava/lang/String; HSPLjava/text/SimpleDateFormat;->formatWeekday(IIZZ)Ljava/lang/String; HSPLjava/text/SimpleDateFormat;->getDateTimeFormat(IILjava/util/Locale;)Ljava/lang/String; HSPLjava/text/SimpleDateFormat;->getExtendedTimeZoneNames()Lcom/android/icu/text/ExtendedTimeZoneNames; HSPLjava/text/SimpleDateFormat;->getTimeZoneNames()Landroid/icu/text/TimeZoneNames; -HSPLjava/text/SimpleDateFormat;->initialize(Ljava/util/Locale;)V+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat; +HSPLjava/text/SimpleDateFormat;->initialize(Ljava/util/Locale;)V HSPLjava/text/SimpleDateFormat;->initializeCalendar(Ljava/util/Locale;)V HSPLjava/text/SimpleDateFormat;->initializeDefaultCentury()V HSPLjava/text/SimpleDateFormat;->isDigit(C)Z @@ -4398,12 +4447,12 @@ HSPLjava/text/SimpleDateFormat;->parseInternal(Ljava/lang/String;Ljava/text/Pars HSPLjava/text/SimpleDateFormat;->parseMonth(Ljava/lang/String;IIIILjava/text/ParsePosition;ZZLjava/text/CalendarBuilder;)I HSPLjava/text/SimpleDateFormat;->parseWeekday(Ljava/lang/String;IIZZLjava/text/CalendarBuilder;)I HSPLjava/text/SimpleDateFormat;->shouldObeyCount(II)Z -HSPLjava/text/SimpleDateFormat;->subFormat(IILjava/text/Format$FieldDelegate;Ljava/lang/StringBuffer;Z)V+]Ljava/text/DateFormatSymbols;Ljava/text/DateFormatSymbols;]Ljava/text/Format$FieldDelegate;Ljava/text/DontCareFieldPosition$1;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/util/Calendar;Ljava/util/GregorianCalendar; -HSPLjava/text/SimpleDateFormat;->subParse(Ljava/lang/String;IIIZ[ZLjava/text/ParsePosition;ZLjava/text/CalendarBuilder;)I+]Ljava/text/ParsePosition;Ljava/text/ParsePosition;]Ljava/text/CalendarBuilder;Ljava/text/CalendarBuilder;]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat; +HSPLjava/text/SimpleDateFormat;->subFormat(IILjava/text/Format$FieldDelegate;Ljava/lang/StringBuffer;Z)V +HSPLjava/text/SimpleDateFormat;->subParse(Ljava/lang/String;IIIZ[ZLjava/text/ParsePosition;ZLjava/text/CalendarBuilder;)I+]Ljava/lang/String;Ljava/lang/String;]Ljava/text/ParsePosition;Ljava/text/ParsePosition;]Ljava/text/CalendarBuilder;Ljava/text/CalendarBuilder;]Ljava/lang/Number;Ljava/lang/Long;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat; HSPLjava/text/SimpleDateFormat;->subParseNumericZone(Ljava/lang/String;IIIZLjava/text/CalendarBuilder;)I HSPLjava/text/SimpleDateFormat;->toPattern()Ljava/lang/String; HSPLjava/text/SimpleDateFormat;->useDateFormatSymbols()Z+]Ljava/lang/Object;Ljava/util/GregorianCalendar;]Ljava/lang/Class;Ljava/lang/Class; -HSPLjava/text/SimpleDateFormat;->zeroPaddingNumber(IIILjava/lang/StringBuffer;)V+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer;]Ljava/text/DecimalFormat;Ljava/text/DecimalFormat;]Ljava/text/NumberFormat;Ljava/text/DecimalFormat; +HSPLjava/text/SimpleDateFormat;->zeroPaddingNumber(IIILjava/lang/StringBuffer;)V HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;)V HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;I)V HSPLjava/text/StringCharacterIterator;-><init>(Ljava/lang/String;III)V @@ -4421,6 +4470,7 @@ HSPLjava/time/Clock$SystemClock;->getZone()Ljava/time/ZoneId; HSPLjava/time/Clock$SystemClock;->instant()Ljava/time/Instant; HSPLjava/time/Clock$SystemClock;->millis()J HSPLjava/time/Clock;-><init>()V +HSPLjava/time/Clock;->currentInstant()Ljava/time/Instant; HSPLjava/time/Clock;->systemDefaultZone()Ljava/time/Clock; HSPLjava/time/Clock;->systemUTC()Ljava/time/Clock; HSPLjava/time/DayOfWeek;->getValue()I @@ -4511,7 +4561,7 @@ HSPLjava/time/LocalDateTime;->isAfter(Ljava/time/chrono/ChronoLocalDateTime;)Z HSPLjava/time/LocalDateTime;->isBefore(Ljava/time/chrono/ChronoLocalDateTime;)Z HSPLjava/time/LocalDateTime;->isSupported(Ljava/time/temporal/TemporalField;)Z HSPLjava/time/LocalDateTime;->now()Ljava/time/LocalDateTime; -HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime;+]Ljava/time/Clock;Ljava/time/Clock$SystemClock;]Ljava/time/Instant;Ljava/time/Instant;]Ljava/time/ZoneId;Ljava/time/ZoneRegion;]Ljava/time/zone/ZoneRules;Ljava/time/zone/ZoneRules; +HSPLjava/time/LocalDateTime;->now(Ljava/time/Clock;)Ljava/time/LocalDateTime; HSPLjava/time/LocalDateTime;->of(Ljava/time/LocalDate;Ljava/time/LocalTime;)Ljava/time/LocalDateTime; HSPLjava/time/LocalDateTime;->ofEpochSecond(JILjava/time/ZoneOffset;)Ljava/time/LocalDateTime; HSPLjava/time/LocalDateTime;->ofInstant(Ljava/time/Instant;Ljava/time/ZoneId;)Ljava/time/LocalDateTime; @@ -4615,7 +4665,6 @@ HSPLjava/time/format/DateTimeFormatter;->getZone()Ljava/time/ZoneId; HSPLjava/time/format/DateTimeFormatter;->parse(Ljava/lang/CharSequence;Ljava/time/temporal/TemporalQuery;)Ljava/lang/Object; HSPLjava/time/format/DateTimeFormatter;->parseResolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/temporal/TemporalAccessor; HSPLjava/time/format/DateTimeFormatter;->parseUnresolved0(Ljava/lang/CharSequence;Ljava/text/ParsePosition;)Ljava/time/format/DateTimeParseContext; -HSPLjava/time/format/DateTimeFormatterBuilder$3;-><clinit>()V HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;-><init>(C)V HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->format(Ljava/time/format/DateTimePrintContext;Ljava/lang/StringBuilder;)Z HSPLjava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser;->parse(Ljava/time/format/DateTimeParseContext;Ljava/lang/CharSequence;I)I @@ -4761,16 +4810,16 @@ HSPLjava/time/zone/ZoneRulesProvider;->getAvailableZoneIds()Ljava/util/Set; HSPLjava/time/zone/ZoneRulesProvider;->getProvider(Ljava/lang/String;)Ljava/time/zone/ZoneRulesProvider; HSPLjava/time/zone/ZoneRulesProvider;->getRules(Ljava/lang/String;Z)Ljava/time/zone/ZoneRules; HSPLjava/util/AbstractCollection;-><init>()V -HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types +HSPLjava/util/AbstractCollection;->addAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Collection;megamorphic_types]Ljava/util/Iterator;megamorphic_types HSPLjava/util/AbstractCollection;->clear()V HSPLjava/util/AbstractCollection;->contains(Ljava/lang/Object;)Z -HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Collection;missing_types]Ljava/util/Iterator;missing_types +HSPLjava/util/AbstractCollection;->containsAll(Ljava/util/Collection;)Z HSPLjava/util/AbstractCollection;->isEmpty()Z+]Ljava/util/AbstractCollection;missing_types HSPLjava/util/AbstractCollection;->remove(Ljava/lang/Object;)Z HSPLjava/util/AbstractCollection;->removeAll(Ljava/util/Collection;)Z HSPLjava/util/AbstractCollection;->retainAll(Ljava/util/Collection;)Z HSPLjava/util/AbstractCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/util/Iterator;missing_types -HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/AbstractCollection;missing_types]Ljava/lang/Object;[Ljava/security/Provider;,[Ljava/lang/String;,[Ljava/lang/Object;]Ljava/lang/Class;Ljava/lang/Class;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/AbstractList$Itr;,Ljava/util/ArrayList$SubList$1; +HSPLjava/util/AbstractCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/AbstractCollection;->toString()Ljava/lang/String; HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;)V HSPLjava/util/AbstractList$Itr;-><init>(Ljava/util/AbstractList;Ljava/util/AbstractList$Itr-IA;)V @@ -4805,8 +4854,8 @@ HSPLjava/util/AbstractList$SubList;->size()I HSPLjava/util/AbstractList;-><init>()V HSPLjava/util/AbstractList;->add(Ljava/lang/Object;)Z HSPLjava/util/AbstractList;->clear()V -HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z+]Ljava/util/ListIterator;Ljava/util/ArrayList$ListItr;]Ljava/util/AbstractList;Ljava/util/ArrayList;]Ljava/util/List;Ljava/util/ArrayList; -HSPLjava/util/AbstractList;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractList;Ljava/util/Collections$SingletonList;,Ljava/util/Arrays$ArrayList;,Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;,Ljava/util/Arrays$ArrayItr;,Ljava/util/Collections$1; +HSPLjava/util/AbstractList;->equals(Ljava/lang/Object;)Z +HSPLjava/util/AbstractList;->hashCode()I HSPLjava/util/AbstractList;->indexOf(Ljava/lang/Object;)I HSPLjava/util/AbstractList;->iterator()Ljava/util/Iterator; HSPLjava/util/AbstractList;->listIterator()Ljava/util/ListIterator; @@ -4823,7 +4872,7 @@ HSPLjava/util/AbstractMap$SimpleEntry;-><init>(Ljava/lang/Object;Ljava/lang/Obje HSPLjava/util/AbstractMap$SimpleEntry;->getKey()Ljava/lang/Object; HSPLjava/util/AbstractMap$SimpleEntry;->getValue()Ljava/lang/Object; HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V -HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V +HSPLjava/util/AbstractMap$SimpleImmutableEntry;-><init>(Ljava/util/Map$Entry;)V+]Ljava/util/Map$Entry;Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/AbstractMap$SimpleImmutableEntry;->equals(Ljava/lang/Object;)Z HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getKey()Ljava/lang/Object; HSPLjava/util/AbstractMap$SimpleImmutableEntry;->getValue()Ljava/lang/Object; @@ -4832,12 +4881,11 @@ HSPLjava/util/AbstractMap;-><init>()V HSPLjava/util/AbstractMap;->clear()V HSPLjava/util/AbstractMap;->clone()Ljava/lang/Object; HSPLjava/util/AbstractMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z -HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z -HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/lang/Object;Ljava/lang/Boolean;]Ljava/util/Iterator;Ljava/util/Collections$UnmodifiableCollection$1; -HSPLjava/util/AbstractMap;->hashCode()I+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/util/AbstractMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/HashMap$EntryIterator;,Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet;,Ljava/util/HashMap$EntrySet; +HSPLjava/util/AbstractMap;->equals(Ljava/lang/Object;)Z+]Ljava/util/Map$Entry;Ljava/util/LinkedHashMap$LinkedHashMapEntry;]Ljava/lang/Object;missing_types]Ljava/util/AbstractMap;Ljava/util/LinkedHashMap;]Ljava/util/Map;Ljava/util/LinkedHashMap;]Ljava/util/Iterator;Ljava/util/LinkedHashMap$LinkedEntryIterator;]Ljava/util/Set;Ljava/util/LinkedHashMap$LinkedEntrySet; +HSPLjava/util/AbstractMap;->get(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/AbstractMap;->hashCode()I HSPLjava/util/AbstractMap;->isEmpty()Z+]Ljava/util/AbstractMap;missing_types -HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V -HSPLjava/util/AbstractMap;->remove(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/AbstractMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map$Entry;Ljava/util/AbstractMap$SimpleImmutableEntry;]Ljava/util/Map;Ljava/util/Collections$SingletonMap;]Ljava/util/Iterator;Ljava/util/Collections$1;]Ljava/util/Set;Ljava/util/Collections$SingletonSet; HSPLjava/util/AbstractMap;->size()I HSPLjava/util/AbstractMap;->toString()Ljava/lang/String; HSPLjava/util/AbstractMap;->values()Ljava/util/Collection; @@ -4850,37 +4898,43 @@ HSPLjava/util/AbstractSequentialList;-><init>()V HSPLjava/util/AbstractSequentialList;->iterator()Ljava/util/Iterator; HSPLjava/util/AbstractSet;-><init>()V HSPLjava/util/AbstractSet;->equals(Ljava/lang/Object;)Z -HSPLjava/util/AbstractSet;->hashCode()I+]Ljava/lang/Object;missing_types]Ljava/util/AbstractSet;Ljava/util/Collections$SingletonSet;,Ljava/util/HashSet;,Ljava/util/LinkedHashSet;]Ljava/util/Iterator;Ljava/util/HashMap$KeyIterator;,Ljava/util/LinkedHashMap$LinkedKeyIterator;,Ljava/util/Collections$1; -HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types]Ljava/util/AbstractSet;Ljava/util/HashSet;,Ljava/util/LinkedHashSet;,Ljava/util/LinkedHashMap$LinkedKeySet;]Ljava/util/Iterator;missing_types +HSPLjava/util/AbstractSet;->hashCode()I +HSPLjava/util/AbstractSet;->removeAll(Ljava/util/Collection;)Z +HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;-><init>(Ljava/util/ArrayDeque;)V +HSPLjava/util/ArrayDeque$$ExternalSyntheticLambda1;->accept(Ljava/lang/Object;)V HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;)V -HSPLjava/util/ArrayDeque$DeqIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DeqIterator-IA;)V HSPLjava/util/ArrayDeque$DeqIterator;->hasNext()Z HSPLjava/util/ArrayDeque$DeqIterator;->next()Ljava/lang/Object; +HSPLjava/util/ArrayDeque$DeqIterator;->postDelete(Z)V HSPLjava/util/ArrayDeque$DeqIterator;->remove()V HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;)V -HSPLjava/util/ArrayDeque$DescendingIterator;-><init>(Ljava/util/ArrayDeque;Ljava/util/ArrayDeque$DescendingIterator-IA;)V -HSPLjava/util/ArrayDeque$DescendingIterator;->hasNext()Z HSPLjava/util/ArrayDeque$DescendingIterator;->next()Ljava/lang/Object; HSPLjava/util/ArrayDeque;-><init>()V HSPLjava/util/ArrayDeque;-><init>(I)V HSPLjava/util/ArrayDeque;-><init>(Ljava/util/Collection;)V HSPLjava/util/ArrayDeque;->add(Ljava/lang/Object;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque; +HSPLjava/util/ArrayDeque;->addAll(Ljava/util/Collection;)Z+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque;]Ljava/util/Collection;Ljava/util/ArrayDeque; HSPLjava/util/ArrayDeque;->addFirst(Ljava/lang/Object;)V HSPLjava/util/ArrayDeque;->addLast(Ljava/lang/Object;)V -HSPLjava/util/ArrayDeque;->allocateElements(I)V HSPLjava/util/ArrayDeque;->checkInvariants()V +HSPLjava/util/ArrayDeque;->circularClear([Ljava/lang/Object;II)V HSPLjava/util/ArrayDeque;->clear()V HSPLjava/util/ArrayDeque;->contains(Ljava/lang/Object;)Z +HSPLjava/util/ArrayDeque;->copyElements(Ljava/util/Collection;)V +HSPLjava/util/ArrayDeque;->dec(II)I HSPLjava/util/ArrayDeque;->delete(I)Z HSPLjava/util/ArrayDeque;->descendingIterator()Ljava/util/Iterator; -HSPLjava/util/ArrayDeque;->doubleCapacity()V +HSPLjava/util/ArrayDeque;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object; +HSPLjava/util/ArrayDeque;->forEach(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;Ljava/util/ArrayDeque$$ExternalSyntheticLambda1; HSPLjava/util/ArrayDeque;->getFirst()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->getLast()Ljava/lang/Object; +HSPLjava/util/ArrayDeque;->grow(I)V +HSPLjava/util/ArrayDeque;->inc(II)I HSPLjava/util/ArrayDeque;->isEmpty()Z HSPLjava/util/ArrayDeque;->iterator()Ljava/util/Iterator; HSPLjava/util/ArrayDeque;->offer(Ljava/lang/Object;)Z HSPLjava/util/ArrayDeque;->offerLast(Ljava/lang/Object;)Z -HSPLjava/util/ArrayDeque;->peek()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque; +HSPLjava/util/ArrayDeque;->peek()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->peekFirst()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->peekLast()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->poll()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque; @@ -4888,13 +4942,15 @@ HSPLjava/util/ArrayDeque;->pollFirst()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->pollLast()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->pop()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->push(Ljava/lang/Object;)V -HSPLjava/util/ArrayDeque;->remove()Ljava/lang/Object; +HSPLjava/util/ArrayDeque;->remove()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque; HSPLjava/util/ArrayDeque;->remove(Ljava/lang/Object;)Z -HSPLjava/util/ArrayDeque;->removeFirst()Ljava/lang/Object; +HSPLjava/util/ArrayDeque;->removeFirst()Ljava/lang/Object;+]Ljava/util/ArrayDeque;Ljava/util/ArrayDeque; HSPLjava/util/ArrayDeque;->removeFirstOccurrence(Ljava/lang/Object;)Z HSPLjava/util/ArrayDeque;->removeLast()Ljava/lang/Object; HSPLjava/util/ArrayDeque;->size()I +HSPLjava/util/ArrayDeque;->sub(III)I HSPLjava/util/ArrayDeque;->toArray()[Ljava/lang/Object; +HSPLjava/util/ArrayDeque;->toArray(Ljava/lang/Class;)[Ljava/lang/Object; HSPLjava/util/ArrayDeque;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/ArrayList$ArrayListSpliterator;-><init>(Ljava/util/ArrayList;III)V HSPLjava/util/ArrayList$ArrayListSpliterator;->characteristics()I @@ -4903,75 +4959,94 @@ HSPLjava/util/ArrayList$ArrayListSpliterator;->forEachRemaining(Ljava/util/funct HSPLjava/util/ArrayList$ArrayListSpliterator;->getFence()I HSPLjava/util/ArrayList$ArrayListSpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;)V -HSPLjava/util/ArrayList$Itr;-><init>(Ljava/util/ArrayList;Ljava/util/ArrayList$Itr-IA;)V +HSPLjava/util/ArrayList$Itr;->checkForComodification()V HSPLjava/util/ArrayList$Itr;->hasNext()Z -HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object; +HSPLjava/util/ArrayList$Itr;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$Itr;Ljava/util/ArrayList$ListItr;,Ljava/util/ArrayList$Itr; HSPLjava/util/ArrayList$Itr;->remove()V HSPLjava/util/ArrayList$ListItr;-><init>(Ljava/util/ArrayList;I)V HSPLjava/util/ArrayList$ListItr;->hasPrevious()Z HSPLjava/util/ArrayList$ListItr;->nextIndex()I HSPLjava/util/ArrayList$ListItr;->previous()Ljava/lang/Object; HSPLjava/util/ArrayList$ListItr;->set(Ljava/lang/Object;)V -HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;II)V +HSPLjava/util/ArrayList$SubList$1;-><init>(Ljava/util/ArrayList$SubList;I)V +HSPLjava/util/ArrayList$SubList$1;->checkForComodification()V HSPLjava/util/ArrayList$SubList$1;->hasNext()Z -HSPLjava/util/ArrayList$SubList$1;->next()Ljava/lang/Object; -HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;Ljava/util/AbstractList;III)V +HSPLjava/util/ArrayList$SubList$1;->next()Ljava/lang/Object;+]Ljava/util/ArrayList$SubList$1;Ljava/util/ArrayList$SubList$1; +HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetoffset(Ljava/util/ArrayList$SubList;)I +HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetroot(Ljava/util/ArrayList$SubList;)Ljava/util/ArrayList; +HSPLjava/util/ArrayList$SubList;->-$$Nest$fgetsize(Ljava/util/ArrayList$SubList;)I +HSPLjava/util/ArrayList$SubList;-><init>(Ljava/util/ArrayList;II)V +HSPLjava/util/ArrayList$SubList;->checkForComodification()V HSPLjava/util/ArrayList$SubList;->get(I)Ljava/lang/Object; HSPLjava/util/ArrayList$SubList;->iterator()Ljava/util/Iterator; HSPLjava/util/ArrayList$SubList;->listIterator(I)Ljava/util/ListIterator; +HSPLjava/util/ArrayList$SubList;->rangeCheckForAdd(I)V HSPLjava/util/ArrayList$SubList;->removeRange(II)V HSPLjava/util/ArrayList$SubList;->size()I HSPLjava/util/ArrayList$SubList;->subList(II)Ljava/util/List; +HSPLjava/util/ArrayList$SubList;->toArray()[Ljava/lang/Object; +HSPLjava/util/ArrayList$SubList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/ArrayList;->-$$Nest$fgetsize(Ljava/util/ArrayList;)I HSPLjava/util/ArrayList;-><init>()V HSPLjava/util/ArrayList;-><init>(I)V -HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;missing_types]Ljava/util/Collection;missing_types +HSPLjava/util/ArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Landroid/net/Uri$PathSegments;]Ljava/util/Collection;Ljava/util/Collections$EmptyList;,Landroid/net/Uri$PathSegments; HSPLjava/util/ArrayList;->add(ILjava/lang/Object;)V HSPLjava/util/ArrayList;->add(Ljava/lang/Object;)Z +HSPLjava/util/ArrayList;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V HSPLjava/util/ArrayList;->addAll(ILjava/util/Collection;)Z HSPLjava/util/ArrayList;->addAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;missing_types -HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;Z)Z+]Ljava/util/Collection;Ljava/util/Collections$SingletonSet;,Ljava/util/ArrayList; +HSPLjava/util/ArrayList;->batchRemove(Ljava/util/Collection;ZII)Z +HSPLjava/util/ArrayList;->checkForComodification(I)V HSPLjava/util/ArrayList;->clear()V HSPLjava/util/ArrayList;->clone()Ljava/lang/Object; -HSPLjava/util/ArrayList;->contains(Ljava/lang/Object;)Z+]Ljava/util/ArrayList;Ljava/util/ArrayList; +HSPLjava/util/ArrayList;->contains(Ljava/lang/Object;)Z +HSPLjava/util/ArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object; +HSPLjava/util/ArrayList;->elementData(I)Ljava/lang/Object; HSPLjava/util/ArrayList;->ensureCapacity(I)V -HSPLjava/util/ArrayList;->ensureCapacityInternal(I)V -HSPLjava/util/ArrayList;->ensureExplicitCapacity(I)V -HSPLjava/util/ArrayList;->fastRemove(I)V +HSPLjava/util/ArrayList;->equals(Ljava/lang/Object;)Z+]Ljava/lang/Object;Ljava/util/ArrayList; +HSPLjava/util/ArrayList;->equalsArrayList(Ljava/util/ArrayList;)Z +HSPLjava/util/ArrayList;->equalsRange(Ljava/util/List;II)Z +HSPLjava/util/ArrayList;->fastRemove([Ljava/lang/Object;I)V HSPLjava/util/ArrayList;->forEach(Ljava/util/function/Consumer;)V -HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object; -HSPLjava/util/ArrayList;->grow(I)V -HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types +HSPLjava/util/ArrayList;->get(I)Ljava/lang/Object;+]Ljava/util/ArrayList;missing_types +HSPLjava/util/ArrayList;->grow()[Ljava/lang/Object; +HSPLjava/util/ArrayList;->grow(I)[Ljava/lang/Object; +HSPLjava/util/ArrayList;->hashCode()I +HSPLjava/util/ArrayList;->hashCodeRange(II)I +HSPLjava/util/ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/ArrayList;Ljava/util/ArrayList; +HSPLjava/util/ArrayList;->indexOfRange(Ljava/lang/Object;II)I+]Ljava/lang/Object;missing_types HSPLjava/util/ArrayList;->isEmpty()Z HSPLjava/util/ArrayList;->iterator()Ljava/util/Iterator; HSPLjava/util/ArrayList;->lastIndexOf(Ljava/lang/Object;)I HSPLjava/util/ArrayList;->listIterator()Ljava/util/ListIterator; HSPLjava/util/ArrayList;->listIterator(I)Ljava/util/ListIterator; +HSPLjava/util/ArrayList;->rangeCheckForAdd(I)V HSPLjava/util/ArrayList;->readObject(Ljava/io/ObjectInputStream;)V HSPLjava/util/ArrayList;->remove(I)Ljava/lang/Object; -HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types +HSPLjava/util/ArrayList;->remove(Ljava/lang/Object;)Z HSPLjava/util/ArrayList;->removeAll(Ljava/util/Collection;)Z HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;)Z +HSPLjava/util/ArrayList;->removeIf(Ljava/util/function/Predicate;II)Z HSPLjava/util/ArrayList;->removeRange(II)V HSPLjava/util/ArrayList;->retainAll(Ljava/util/Collection;)Z -HSPLjava/util/ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object;+]Ljava/util/ArrayList;Ljava/util/ArrayList; +HSPLjava/util/ArrayList;->shiftTailOverGap([Ljava/lang/Object;II)V HSPLjava/util/ArrayList;->size()I HSPLjava/util/ArrayList;->sort(Ljava/util/Comparator;)V HSPLjava/util/ArrayList;->spliterator()Ljava/util/Spliterator; HSPLjava/util/ArrayList;->subList(II)Ljava/util/List; -HSPLjava/util/ArrayList;->subListRangeCheck(III)V HSPLjava/util/ArrayList;->toArray()[Ljava/lang/Object; -HSPLjava/util/ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; +HSPLjava/util/ArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;missing_types HSPLjava/util/ArrayList;->trimToSize()V HSPLjava/util/ArrayList;->writeObject(Ljava/io/ObjectOutputStream;)V HSPLjava/util/Arrays$ArrayItr;-><init>([Ljava/lang/Object;)V HSPLjava/util/Arrays$ArrayItr;->hasNext()Z HSPLjava/util/Arrays$ArrayItr;->next()Ljava/lang/Object; HSPLjava/util/Arrays$ArrayList;-><init>([Ljava/lang/Object;)V -HSPLjava/util/Arrays$ArrayList;->contains(Ljava/lang/Object;)Z+]Ljava/util/Arrays$ArrayList;Ljava/util/Arrays$ArrayList; +HSPLjava/util/Arrays$ArrayList;->contains(Ljava/lang/Object;)Z HSPLjava/util/Arrays$ArrayList;->forEach(Ljava/util/function/Consumer;)V HSPLjava/util/Arrays$ArrayList;->get(I)Ljava/lang/Object; -HSPLjava/util/Arrays$ArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types +HSPLjava/util/Arrays$ArrayList;->indexOf(Ljava/lang/Object;)I HSPLjava/util/Arrays$ArrayList;->iterator()Ljava/util/Iterator; HSPLjava/util/Arrays$ArrayList;->set(ILjava/lang/Object;)Ljava/lang/Object; HSPLjava/util/Arrays$ArrayList;->size()I @@ -5035,7 +5110,7 @@ HSPLjava/util/Arrays;->hashCode([B)I HSPLjava/util/Arrays;->hashCode([F)I HSPLjava/util/Arrays;->hashCode([I)I HSPLjava/util/Arrays;->hashCode([J)I -HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types +HSPLjava/util/Arrays;->hashCode([Ljava/lang/Object;)I HSPLjava/util/Arrays;->rangeCheck(III)V HSPLjava/util/Arrays;->sort([C)V HSPLjava/util/Arrays;->sort([F)V @@ -5052,15 +5127,14 @@ HSPLjava/util/Arrays;->stream([I)Ljava/util/stream/IntStream; HSPLjava/util/Arrays;->stream([III)Ljava/util/stream/IntStream; HSPLjava/util/Arrays;->stream([Ljava/lang/Object;)Ljava/util/stream/Stream; HSPLjava/util/Arrays;->stream([Ljava/lang/Object;II)Ljava/util/stream/Stream; -HSPLjava/util/Arrays;->toString([B)Ljava/lang/String; +HSPLjava/util/Arrays;->toString([B)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/util/Arrays;->toString([F)Ljava/lang/String; HSPLjava/util/Arrays;->toString([I)Ljava/lang/String; HSPLjava/util/Arrays;->toString([J)Ljava/lang/String; -HSPLjava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; +HSPLjava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String; HSPLjava/util/Base64$Decoder;->decode(Ljava/lang/String;)[B HSPLjava/util/Base64$Decoder;->decode([B)[B HSPLjava/util/Base64$Decoder;->decode0([BII[B)I -HSPLjava/util/Base64$Decoder;->outLength([BII)I HSPLjava/util/Base64;->getDecoder()Ljava/util/Base64$Decoder; HSPLjava/util/Base64;->getEncoder()Ljava/util/Base64$Encoder; HSPLjava/util/Base64;->getMimeDecoder()Ljava/util/Base64$Decoder; @@ -5073,7 +5147,7 @@ HSPLjava/util/BitSet;->checkInvariants()V HSPLjava/util/BitSet;->checkRange(II)V HSPLjava/util/BitSet;->clear()V HSPLjava/util/BitSet;->clear(I)V -HSPLjava/util/BitSet;->clone()Ljava/lang/Object; +HSPLjava/util/BitSet;->clone()Ljava/lang/Object;+][J[J HSPLjava/util/BitSet;->ensureCapacity(I)V HSPLjava/util/BitSet;->equals(Ljava/lang/Object;)Z HSPLjava/util/BitSet;->expandTo(I)V @@ -5093,6 +5167,7 @@ HSPLjava/util/BitSet;->set(IZ)V HSPLjava/util/BitSet;->size()I HSPLjava/util/BitSet;->toString()Ljava/lang/String; HSPLjava/util/BitSet;->trimToSize()V +HSPLjava/util/BitSet;->valueOf(Ljava/nio/ByteBuffer;)Ljava/util/BitSet;+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer; HSPLjava/util/BitSet;->valueOf([J)Ljava/util/BitSet; HSPLjava/util/BitSet;->wordIndex(I)I HSPLjava/util/Calendar;-><init>()V @@ -5105,7 +5180,8 @@ HSPLjava/util/Calendar;->compareTo(J)I HSPLjava/util/Calendar;->compareTo(Ljava/util/Calendar;)I HSPLjava/util/Calendar;->complete()V HSPLjava/util/Calendar;->createCalendar(Ljava/util/TimeZone;Ljava/util/Locale;)Ljava/util/Calendar; -HSPLjava/util/Calendar;->get(I)I +HSPLjava/util/Calendar;->defaultTimeZone(Ljava/util/Locale;)Ljava/util/TimeZone; +HSPLjava/util/Calendar;->get(I)I+]Ljava/util/Calendar;Ljava/util/GregorianCalendar; HSPLjava/util/Calendar;->getFirstDayOfWeek()I HSPLjava/util/Calendar;->getInstance()Ljava/util/Calendar; HSPLjava/util/Calendar;->getInstance(Ljava/util/Locale;)Ljava/util/Calendar; @@ -5127,19 +5203,19 @@ HSPLjava/util/Calendar;->isLenient()Z HSPLjava/util/Calendar;->isPartiallyNormalized()Z HSPLjava/util/Calendar;->isSet(I)Z HSPLjava/util/Calendar;->selectFields()I -HSPLjava/util/Calendar;->set(II)V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar; +HSPLjava/util/Calendar;->set(II)V HSPLjava/util/Calendar;->set(III)V HSPLjava/util/Calendar;->set(IIIIII)V HSPLjava/util/Calendar;->setFieldsComputed(I)V HSPLjava/util/Calendar;->setFieldsNormalized(I)V HSPLjava/util/Calendar;->setLenient(Z)V -HSPLjava/util/Calendar;->setTime(Ljava/util/Date;)V -HSPLjava/util/Calendar;->setTimeInMillis(J)V+]Ljava/util/Calendar;Ljava/util/GregorianCalendar; +HSPLjava/util/Calendar;->setTime(Ljava/util/Date;)V+]Ljava/util/Date;Ljava/util/Date;]Ljava/util/Calendar;Ljava/util/GregorianCalendar; +HSPLjava/util/Calendar;->setTimeInMillis(J)V HSPLjava/util/Calendar;->setTimeZone(Ljava/util/TimeZone;)V -HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V +HSPLjava/util/Calendar;->setWeekCountData(Ljava/util/Locale;)V+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/lang/Integer;Ljava/lang/Integer; HSPLjava/util/Calendar;->setZoneShared(Z)V HSPLjava/util/Calendar;->updateTime()V -HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/LinkedList;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/LinkedList$ListItr; +HSPLjava/util/Collection;->removeIf(Ljava/util/function/Predicate;)Z+]Ljava/util/Collection;Landroid/util/MapCollections$ValuesCollection;,Ljava/util/HashMap$EntrySet;]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator;,Ljava/util/HashMap$EntryIterator; HSPLjava/util/Collection;->spliterator()Ljava/util/Spliterator; HSPLjava/util/Collection;->stream()Ljava/util/stream/Stream;+]Ljava/util/Collection;megamorphic_types HSPLjava/util/Collections$1;-><init>(Ljava/lang/Object;)V @@ -5199,6 +5275,7 @@ HSPLjava/util/Collections$SetFromMap;->toArray([Ljava/lang/Object;)[Ljava/lang/O HSPLjava/util/Collections$SingletonList;-><init>(Ljava/lang/Object;)V HSPLjava/util/Collections$SingletonList;->contains(Ljava/lang/Object;)Z HSPLjava/util/Collections$SingletonList;->get(I)Ljava/lang/Object; +HSPLjava/util/Collections$SingletonList;->hashCode()I HSPLjava/util/Collections$SingletonList;->iterator()Ljava/util/Iterator; HSPLjava/util/Collections$SingletonList;->size()I HSPLjava/util/Collections$SingletonMap;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V @@ -5223,11 +5300,12 @@ HSPLjava/util/Collections$SynchronizedCollection;->forEach(Ljava/util/function/C HSPLjava/util/Collections$SynchronizedCollection;->isEmpty()Z HSPLjava/util/Collections$SynchronizedCollection;->iterator()Ljava/util/Iterator; HSPLjava/util/Collections$SynchronizedCollection;->remove(Ljava/lang/Object;)Z -HSPLjava/util/Collections$SynchronizedCollection;->size()I +HSPLjava/util/Collections$SynchronizedCollection;->size()I+]Ljava/util/Collection;Ljava/util/ArrayList; HSPLjava/util/Collections$SynchronizedCollection;->toArray()[Ljava/lang/Object; -HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; +HSPLjava/util/Collections$SynchronizedCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;Ljava/util/ArrayList; +HSPLjava/util/Collections$SynchronizedCollection;->toString()Ljava/lang/String; HSPLjava/util/Collections$SynchronizedList;-><init>(Ljava/util/List;)V -HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object; +HSPLjava/util/Collections$SynchronizedList;->get(I)Ljava/lang/Object;+]Ljava/util/List;Ljava/util/ArrayList; HSPLjava/util/Collections$SynchronizedMap;-><init>(Ljava/util/Map;)V HSPLjava/util/Collections$SynchronizedMap;->clear()V HSPLjava/util/Collections$SynchronizedMap;->containsKey(Ljava/lang/Object;)Z @@ -5237,7 +5315,7 @@ HSPLjava/util/Collections$SynchronizedMap;->getOrDefault(Ljava/lang/Object;Ljava HSPLjava/util/Collections$SynchronizedMap;->isEmpty()Z HSPLjava/util/Collections$SynchronizedMap;->keySet()Ljava/util/Set; HSPLjava/util/Collections$SynchronizedMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V+]Ljava/util/Map;Ljava/util/HashMap; +HSPLjava/util/Collections$SynchronizedMap;->putAll(Ljava/util/Map;)V HSPLjava/util/Collections$SynchronizedMap;->remove(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/Collections$SynchronizedMap;->size()I HSPLjava/util/Collections$SynchronizedMap;->values()Ljava/util/Collection; @@ -5246,18 +5324,18 @@ HSPLjava/util/Collections$SynchronizedSet;-><init>(Ljava/util/Set;)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;->hasNext()Z+]Ljava/util/Iterator;missing_types -HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;missing_types +HSPLjava/util/Collections$UnmodifiableCollection$1;->hasNext()Z+]Ljava/util/Iterator;megamorphic_types +HSPLjava/util/Collections$UnmodifiableCollection$1;->next()Ljava/lang/Object;+]Ljava/util/Iterator;megamorphic_types HSPLjava/util/Collections$UnmodifiableCollection;-><init>(Ljava/util/Collection;)V HSPLjava/util/Collections$UnmodifiableCollection;->contains(Ljava/lang/Object;)Z+]Ljava/util/Collection;megamorphic_types -HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z+]Ljava/util/Collection;Ljava/util/HashSet; +HSPLjava/util/Collections$UnmodifiableCollection;->containsAll(Ljava/util/Collection;)Z HSPLjava/util/Collections$UnmodifiableCollection;->forEach(Ljava/util/function/Consumer;)V -HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z+]Ljava/util/Collection;missing_types +HSPLjava/util/Collections$UnmodifiableCollection;->isEmpty()Z+]Ljava/util/Collection;Ljava/util/ArrayList;,Ljava/util/TreeMap$KeySet; HSPLjava/util/Collections$UnmodifiableCollection;->iterator()Ljava/util/Iterator; HSPLjava/util/Collections$UnmodifiableCollection;->size()I+]Ljava/util/Collection;missing_types HSPLjava/util/Collections$UnmodifiableCollection;->stream()Ljava/util/stream/Stream; -HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object;+]Ljava/util/Collection;megamorphic_types -HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/util/Collection;missing_types +HSPLjava/util/Collections$UnmodifiableCollection;->toArray()[Ljava/lang/Object; +HSPLjava/util/Collections$UnmodifiableCollection;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/Collections$UnmodifiableCollection;->toString()Ljava/lang/String; HSPLjava/util/Collections$UnmodifiableList$1;-><init>(Ljava/util/Collections$UnmodifiableList;I)V HSPLjava/util/Collections$UnmodifiableList$1;->hasNext()Z @@ -5271,12 +5349,12 @@ HSPLjava/util/Collections$UnmodifiableList;->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 @@ -5297,7 +5375,7 @@ HSPLjava/util/Collections$UnmodifiableSet;-><init>(Ljava/util/Set;)V HSPLjava/util/Collections$UnmodifiableSet;->equals(Ljava/lang/Object;)Z HSPLjava/util/Collections$UnmodifiableSortedMap;-><init>(Ljava/util/SortedMap;)V HSPLjava/util/Collections$UnmodifiableSortedSet;-><init>(Ljava/util/SortedSet;)V -HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z +HSPLjava/util/Collections;->addAll(Ljava/util/Collection;[Ljava/lang/Object;)Z+]Ljava/util/Collection;Ljava/util/ArrayList; HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;)I HSPLjava/util/Collections;->binarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I HSPLjava/util/Collections;->disjoint(Ljava/util/Collection;Ljava/util/Collection;)Z @@ -5309,7 +5387,7 @@ HSPLjava/util/Collections;->emptyMap()Ljava/util/Map; HSPLjava/util/Collections;->emptySet()Ljava/util/Set; HSPLjava/util/Collections;->enumeration(Ljava/util/Collection;)Ljava/util/Enumeration; HSPLjava/util/Collections;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z -HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I +HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;)I+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/lang/Comparable;Ljava/lang/Integer; HSPLjava/util/Collections;->indexedBinarySearch(Ljava/util/List;Ljava/lang/Object;Ljava/util/Comparator;)I HSPLjava/util/Collections;->list(Ljava/util/Enumeration;)Ljava/util/ArrayList; HSPLjava/util/Collections;->max(Ljava/util/Collection;)Ljava/lang/Object; @@ -5329,7 +5407,7 @@ HSPLjava/util/Collections;->singletonIterator(Ljava/lang/Object;)Ljava/util/Iter HSPLjava/util/Collections;->singletonList(Ljava/lang/Object;)Ljava/util/List; HSPLjava/util/Collections;->singletonMap(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map; HSPLjava/util/Collections;->sort(Ljava/util/List;)V -HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/List;Ljava/util/ArrayList; +HSPLjava/util/Collections;->sort(Ljava/util/List;Ljava/util/Comparator;)V HSPLjava/util/Collections;->swap(Ljava/util/List;II)V HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;)Ljava/util/Collection; HSPLjava/util/Collections;->synchronizedCollection(Ljava/util/Collection;Ljava/lang/Object;)Ljava/util/Collection; @@ -5338,8 +5416,8 @@ HSPLjava/util/Collections;->synchronizedMap(Ljava/util/Map;)Ljava/util/Map; HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;)Ljava/util/Set; HSPLjava/util/Collections;->synchronizedSet(Ljava/util/Set;Ljava/lang/Object;)Ljava/util/Set; HSPLjava/util/Collections;->unmodifiableCollection(Ljava/util/Collection;)Ljava/util/Collection; -HSPLjava/util/Collections;->unmodifiableList(Ljava/util/List;)Ljava/util/List; -HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map; +HSPLjava/util/Collections;->unmodifiableList(Ljava/util/List;)Ljava/util/List;+]Ljava/lang/Object;missing_types +HSPLjava/util/Collections;->unmodifiableMap(Ljava/util/Map;)Ljava/util/Map;+]Ljava/lang/Object;missing_types HSPLjava/util/Collections;->unmodifiableSet(Ljava/util/Set;)Ljava/util/Set; HSPLjava/util/Collections;->unmodifiableSortedMap(Ljava/util/SortedMap;)Ljava/util/SortedMap; HSPLjava/util/Collections;->unmodifiableSortedSet(Ljava/util/SortedSet;)Ljava/util/SortedSet; @@ -5370,17 +5448,19 @@ HSPLjava/util/Comparator;->comparingInt(Ljava/util/function/ToIntFunction;)Ljava HSPLjava/util/Comparator;->comparingLong(Ljava/util/function/ToLongFunction;)Ljava/util/Comparator; HSPLjava/util/Comparator;->lambda$comparing$77a9974f$1(Ljava/util/function/Function;Ljava/lang/Object;Ljava/lang/Object;)I HSPLjava/util/Comparator;->lambda$comparingInt$7b0bb60$1(Ljava/util/function/ToIntFunction;Ljava/lang/Object;Ljava/lang/Object;)I -HSPLjava/util/Comparator;->lambda$thenComparing$36697e65$1(Ljava/util/Comparator;Ljava/util/Comparator;Ljava/lang/Object;Ljava/lang/Object;)I HSPLjava/util/Comparator;->naturalOrder()Ljava/util/Comparator; +HSPLjava/util/Comparator;->nullsFirst(Ljava/util/Comparator;)Ljava/util/Comparator; HSPLjava/util/Comparator;->reversed()Ljava/util/Comparator; HSPLjava/util/Comparator;->thenComparing(Ljava/util/Comparator;)Ljava/util/Comparator; -HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator; +HSPLjava/util/Comparator;->thenComparing(Ljava/util/function/Function;)Ljava/util/Comparator;+]Ljava/util/Comparator;Ljava/util/Comparator$$ExternalSyntheticLambda5; HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Comparable;Ljava/lang/Comparable;)I HSPLjava/util/Comparators$NaturalOrderComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I +HSPLjava/util/Comparators$NullComparator;-><init>(ZLjava/util/Comparator;)V +HSPLjava/util/Comparators$NullComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I HSPLjava/util/Currency;-><init>(Landroid/icu/util/Currency;)V HSPLjava/util/Currency;->getCurrencyCode()Ljava/lang/String; HSPLjava/util/Currency;->getInstance(Ljava/lang/String;)Ljava/util/Currency; -HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency; +HSPLjava/util/Currency;->getInstance(Ljava/util/Locale;)Ljava/util/Currency;+]Ljava/util/Locale;Ljava/util/Locale;]Landroid/icu/util/Currency;Landroid/icu/util/Currency; HSPLjava/util/Currency;->getSymbol(Ljava/util/Locale;)Ljava/lang/String; HSPLjava/util/Date;-><init>()V HSPLjava/util/Date;-><init>(J)V @@ -5405,16 +5485,19 @@ HSPLjava/util/Date;->setTime(J)V HSPLjava/util/Date;->toInstant()Ljava/time/Instant; HSPLjava/util/Date;->toString()Ljava/lang/String; HSPLjava/util/Dictionary;-><init>()V -HSPLjava/util/DualPivotQuicksort;->doSort([CII[CII)V -HSPLjava/util/DualPivotQuicksort;->doSort([FII[FII)V -HSPLjava/util/DualPivotQuicksort;->sort([CIIZ)V -HSPLjava/util/DualPivotQuicksort;->sort([CII[CII)V -HSPLjava/util/DualPivotQuicksort;->sort([FIIZ)V -HSPLjava/util/DualPivotQuicksort;->sort([FII[FII)V -HSPLjava/util/DualPivotQuicksort;->sort([IIIZ)V -HSPLjava/util/DualPivotQuicksort;->sort([III[III)V -HSPLjava/util/DualPivotQuicksort;->sort([JIIZ)V -HSPLjava/util/DualPivotQuicksort;->sort([JII[JII)V +HSPLjava/util/DualPivotQuicksort;->insertionSort([CII)V +HSPLjava/util/DualPivotQuicksort;->insertionSort([FII)V +HSPLjava/util/DualPivotQuicksort;->insertionSort([III)V +HSPLjava/util/DualPivotQuicksort;->insertionSort([JII)V +HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[FIII)V +HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[IIII)V +HSPLjava/util/DualPivotQuicksort;->sort(Ljava/util/DualPivotQuicksort$Sorter;[JIII)V +HSPLjava/util/DualPivotQuicksort;->sort([CIII)V +HSPLjava/util/DualPivotQuicksort;->sort([FIII)V +HSPLjava/util/DualPivotQuicksort;->sort([IIII)V +HSPLjava/util/DualPivotQuicksort;->sort([JIII)V +HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[III)Z +HSPLjava/util/DualPivotQuicksort;->tryMergeRuns(Ljava/util/DualPivotQuicksort$Sorter;[JII)Z HSPLjava/util/EnumMap$EntryIterator$Entry;-><init>(Ljava/util/EnumMap$EntryIterator;I)V HSPLjava/util/EnumMap$EntryIterator$Entry;->checkIndexForEntryUse()V HSPLjava/util/EnumMap$EntryIterator$Entry;->getKey()Ljava/lang/Enum; @@ -5479,62 +5562,64 @@ HSPLjava/util/Formatter$Conversion;->isInteger(C)Z HSPLjava/util/Formatter$Conversion;->isText(C)Z HSPLjava/util/Formatter$Conversion;->isValid(C)Z HSPLjava/util/Formatter$DateTime;->isValid(C)Z -HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;)V +HSPLjava/util/Formatter$FixedString;-><init>(Ljava/util/Formatter;Ljava/lang/String;II)V HSPLjava/util/Formatter$FixedString;->index()I -HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Appendable;Ljava/lang/StringBuilder; +HSPLjava/util/Formatter$FixedString;->print(Ljava/lang/Object;Ljava/util/Locale;)V +HSPLjava/util/Formatter$Flags;->-$$Nest$madd(Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags; HSPLjava/util/Formatter$Flags;-><init>(I)V -HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags; +HSPLjava/util/Formatter$Flags;->add(Ljava/util/Formatter$Flags;)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; HSPLjava/util/Formatter$Flags;->contains(Ljava/util/Formatter$Flags;)Z+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; HSPLjava/util/Formatter$Flags;->parse(C)Ljava/util/Formatter$Flags; -HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;)Ljava/util/Formatter$Flags; +HSPLjava/util/Formatter$Flags;->parse(Ljava/lang/String;II)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; HSPLjava/util/Formatter$Flags;->valueOf()I HSPLjava/util/Formatter$FormatSpecifier;-><init>(Ljava/util/Formatter;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V -HSPLjava/util/Formatter$FormatSpecifier;->addZeros([CI)[C +HSPLjava/util/Formatter$FormatSpecifier;->addZeros(Ljava/lang/StringBuilder;I)V HSPLjava/util/Formatter$FormatSpecifier;->adjustWidth(ILjava/util/Formatter$Flags;Z)I +HSPLjava/util/Formatter$FormatSpecifier;->appendJustified(Ljava/lang/Appendable;Ljava/lang/CharSequence;)Ljava/lang/Appendable;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder;]Ljava/lang/Appendable;Ljava/lang/StringBuilder; HSPLjava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; HSPLjava/util/Formatter$FormatSpecifier;->checkCharacter()V HSPLjava/util/Formatter$FormatSpecifier;->checkDateTime()V HSPLjava/util/Formatter$FormatSpecifier;->checkFloat()V -HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; +HSPLjava/util/Formatter$FormatSpecifier;->checkGeneral()V HSPLjava/util/Formatter$FormatSpecifier;->checkInteger()V -HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; +HSPLjava/util/Formatter$FormatSpecifier;->checkNumeric()V HSPLjava/util/Formatter$FormatSpecifier;->checkText()V -HSPLjava/util/Formatter$FormatSpecifier;->conversion(Ljava/lang/String;)C +HSPLjava/util/Formatter$FormatSpecifier;->conversion(C)C HSPLjava/util/Formatter$FormatSpecifier;->flags(Ljava/lang/String;)Ljava/util/Formatter$Flags;+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; -HSPLjava/util/Formatter$FormatSpecifier;->getZero(Ljava/util/Locale;)C+]Ljava/util/Formatter;Ljava/util/Formatter;]Ljava/util/Locale;Ljava/util/Locale; +HSPLjava/util/Formatter$FormatSpecifier;->getZero(Ljava/util/Locale;)C HSPLjava/util/Formatter$FormatSpecifier;->index()I HSPLjava/util/Formatter$FormatSpecifier;->index(Ljava/lang/String;)I -HSPLjava/util/Formatter$FormatSpecifier;->justify(Ljava/lang/String;)Ljava/lang/String; -HSPLjava/util/Formatter$FormatSpecifier;->leadingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags; +HSPLjava/util/Formatter$FormatSpecifier;->leadingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder; HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;JLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder; -HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;[CLjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale; +HSPLjava/util/Formatter$FormatSpecifier;->localizedMagnitude(Ljava/lang/StringBuilder;Ljava/lang/CharSequence;ILjava/util/Formatter$Flags;ILjava/util/Locale;)Ljava/lang/StringBuilder;+]Ljava/text/DecimalFormatSymbols;Ljava/text/DecimalFormatSymbols;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/util/Locale;Ljava/util/Locale;]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder; HSPLjava/util/Formatter$FormatSpecifier;->precision(Ljava/lang/String;)I HSPLjava/util/Formatter$FormatSpecifier;->print(BLjava/util/Locale;)V -HSPLjava/util/Formatter$FormatSpecifier;->print(DLjava/util/Locale;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/Appendable;Ljava/lang/StringBuilder; +HSPLjava/util/Formatter$FormatSpecifier;->print(DLjava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->print(FLjava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->print(ILjava/util/Locale;)V -HSPLjava/util/Formatter$FormatSpecifier;->print(JLjava/util/Locale;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Appendable;Ljava/lang/StringBuilder; +HSPLjava/util/Formatter$FormatSpecifier;->print(JLjava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/Object;Ljava/util/Locale;)V -HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Appendable;Ljava/lang/StringBuilder; +HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/String;Ljava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;DLjava/util/Locale;Ljava/util/Formatter$Flags;CIZ)V HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/lang/StringBuilder;Ljava/util/Calendar;CLjava/util/Locale;)Ljava/lang/Appendable; HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/math/BigInteger;Ljava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->print(Ljava/util/Calendar;CLjava/util/Locale;)V -HSPLjava/util/Formatter$FormatSpecifier;->printBoolean(Ljava/lang/Object;)V -HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;)V +HSPLjava/util/Formatter$FormatSpecifier;->printBoolean(Ljava/lang/Object;Ljava/util/Locale;)V +HSPLjava/util/Formatter$FormatSpecifier;->printCharacter(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Character;Ljava/lang/Character; HSPLjava/util/Formatter$FormatSpecifier;->printDateTime(Ljava/lang/Object;Ljava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->printFloat(Ljava/lang/Object;Ljava/util/Locale;)V -HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer; -HSPLjava/util/Formatter$FormatSpecifier;->printString(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/util/Formatter$Flags;Ljava/util/Formatter$Flags;]Ljava/lang/Object;missing_types +HSPLjava/util/Formatter$FormatSpecifier;->printInteger(Ljava/lang/Object;Ljava/util/Locale;)V+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/Long;Ljava/lang/Long;]Ljava/lang/Byte;Ljava/lang/Byte; +HSPLjava/util/Formatter$FormatSpecifier;->printString(Ljava/lang/Object;Ljava/util/Locale;)V HSPLjava/util/Formatter$FormatSpecifier;->trailingSign(Ljava/lang/StringBuilder;Z)Ljava/lang/StringBuilder; +HSPLjava/util/Formatter$FormatSpecifier;->trailingZeros(Ljava/lang/StringBuilder;I)V HSPLjava/util/Formatter$FormatSpecifier;->width(Ljava/lang/String;)I -HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V +HSPLjava/util/Formatter$FormatSpecifierParser;-><init>(Ljava/util/Formatter;Ljava/lang/String;I)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/util/Formatter$FormatSpecifierParser;->advance()C HSPLjava/util/Formatter$FormatSpecifierParser;->back(I)V HSPLjava/util/Formatter$FormatSpecifierParser;->getEndIdx()I HSPLjava/util/Formatter$FormatSpecifierParser;->getFormatSpecifier()Ljava/util/Formatter$FormatSpecifier; HSPLjava/util/Formatter$FormatSpecifierParser;->isEnd()Z -HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String; +HSPLjava/util/Formatter$FormatSpecifierParser;->nextInt()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; HSPLjava/util/Formatter$FormatSpecifierParser;->nextIsInt()Z HSPLjava/util/Formatter$FormatSpecifierParser;->peek()C HSPLjava/util/Formatter;->-$$Nest$fgeta(Ljava/util/Formatter;)Ljava/lang/Appendable; @@ -5547,29 +5632,29 @@ HSPLjava/util/Formatter;-><init>(Ljava/util/Locale;Ljava/lang/Appendable;)V HSPLjava/util/Formatter;->close()V HSPLjava/util/Formatter;->ensureOpen()V HSPLjava/util/Formatter;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; -HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier; -HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C +HSPLjava/util/Formatter;->format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter;+]Ljava/util/List;Ljava/util/ArrayList;]Ljava/util/Formatter$FormatString;Ljava/util/Formatter$FixedString;,Ljava/util/Formatter$FormatSpecifier;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr; +HSPLjava/util/Formatter;->getZero(Ljava/util/Locale;)C+]Ljava/util/Locale;Ljava/util/Locale;]Llibcore/icu/DecimalFormatData;Llibcore/icu/DecimalFormatData; HSPLjava/util/Formatter;->locale()Ljava/util/Locale; HSPLjava/util/Formatter;->nonNullAppendable(Ljava/lang/Appendable;)Ljava/lang/Appendable; HSPLjava/util/Formatter;->out()Ljava/lang/Appendable; -HSPLjava/util/Formatter;->parse(Ljava/lang/String;)[Ljava/util/Formatter$FormatString;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList; -HSPLjava/util/Formatter;->toString()Ljava/lang/String;+]Ljava/lang/Object;Ljava/lang/StringBuilder; -HSPLjava/util/GregorianCalendar;-><init>()V +HSPLjava/util/Formatter;->parse(Ljava/lang/String;)Ljava/util/List;+]Ljava/util/Formatter$FormatSpecifierParser;Ljava/util/Formatter$FormatSpecifierParser;]Ljava/util/ArrayList;Ljava/util/ArrayList; +HSPLjava/util/Formatter;->toString()Ljava/lang/String; +HSPLjava/util/GregorianCalendar;-><init>()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar; HSPLjava/util/GregorianCalendar;-><init>(IIIIII)V HSPLjava/util/GregorianCalendar;-><init>(IIIIIII)V HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;)V -HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V +HSPLjava/util/GregorianCalendar;-><init>(Ljava/util/TimeZone;Ljava/util/Locale;)V+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar; HSPLjava/util/GregorianCalendar;->add(II)V HSPLjava/util/GregorianCalendar;->adjustDstOffsetForInvalidWallClock(JLjava/util/TimeZone;I)I HSPLjava/util/GregorianCalendar;->adjustForZoneAndDaylightSavingsTime(IJLjava/util/TimeZone;)J HSPLjava/util/GregorianCalendar;->clone()Ljava/lang/Object; -HSPLjava/util/GregorianCalendar;->computeFields()V -HSPLjava/util/GregorianCalendar;->computeFields(II)I+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo; +HSPLjava/util/GregorianCalendar;->computeFields()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar; +HSPLjava/util/GregorianCalendar;->computeFields(II)I+]Lsun/util/calendar/Gregorian;Lsun/util/calendar/Gregorian;]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo;]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone; HSPLjava/util/GregorianCalendar;->computeTime()V+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar; HSPLjava/util/GregorianCalendar;->getActualMaximum(I)I HSPLjava/util/GregorianCalendar;->getCalendarDate(J)Lsun/util/calendar/BaseCalendar$Date; HSPLjava/util/GregorianCalendar;->getCurrentFixedDate()J -HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian; +HSPLjava/util/GregorianCalendar;->getFixedDate(Lsun/util/calendar/BaseCalendar;II)J HSPLjava/util/GregorianCalendar;->getGregorianCutoverDate()Lsun/util/calendar/BaseCalendar$Date; HSPLjava/util/GregorianCalendar;->getJulianCalendarSystem()Lsun/util/calendar/BaseCalendar; HSPLjava/util/GregorianCalendar;->getLeastMaximum(I)I @@ -5577,7 +5662,7 @@ HSPLjava/util/GregorianCalendar;->getMaximum(I)I HSPLjava/util/GregorianCalendar;->getMinimum(I)I HSPLjava/util/GregorianCalendar;->getNormalizedCalendar()Ljava/util/GregorianCalendar; HSPLjava/util/GregorianCalendar;->getTimeZone()Ljava/util/TimeZone; -HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I +HSPLjava/util/GregorianCalendar;->getWeekNumber(JJ)I+]Ljava/util/GregorianCalendar;Ljava/util/GregorianCalendar; HSPLjava/util/GregorianCalendar;->internalGetEra()I HSPLjava/util/GregorianCalendar;->isCutoverYear(I)Z HSPLjava/util/GregorianCalendar;->isLeapYear(I)Z @@ -5611,9 +5696,12 @@ HSPLjava/util/HashMap$KeySet;->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 HSPLjava/util/HashMap$Node;-><init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)V HSPLjava/util/HashMap$Node;->getKey()Ljava/lang/Object; HSPLjava/util/HashMap$Node;->getValue()Ljava/lang/Object; @@ -5631,7 +5719,7 @@ HSPLjava/util/HashMap$TreeNode;->split(Ljava/util/HashMap;[Ljava/util/HashMap$No HSPLjava/util/HashMap$TreeNode;->treeify([Ljava/util/HashMap$Node;)V HSPLjava/util/HashMap$TreeNode;->untreeify(Ljava/util/HashMap;)Ljava/util/HashMap$Node; HSPLjava/util/HashMap$ValueIterator;-><init>(Ljava/util/HashMap;)V -HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object;+]Ljava/util/HashMap$ValueIterator;Ljava/util/HashMap$ValueIterator; +HSPLjava/util/HashMap$ValueIterator;->next()Ljava/lang/Object; HSPLjava/util/HashMap$ValueSpliterator;-><init>(Ljava/util/HashMap;IIII)V HSPLjava/util/HashMap$ValueSpliterator;->characteristics()I HSPLjava/util/HashMap$ValueSpliterator;->forEachRemaining(Ljava/util/function/Consumer;)V @@ -5641,10 +5729,12 @@ HSPLjava/util/HashMap$Values;->forEach(Ljava/util/function/Consumer;)V HSPLjava/util/HashMap$Values;->iterator()Ljava/util/Iterator; HSPLjava/util/HashMap$Values;->size()I HSPLjava/util/HashMap$Values;->spliterator()Ljava/util/Spliterator; +HSPLjava/util/HashMap$Values;->toArray()[Ljava/lang/Object; +HSPLjava/util/HashMap$Values;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/HashMap;-><init>()V HSPLjava/util/HashMap;-><init>(I)V HSPLjava/util/HashMap;-><init>(IF)V -HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V +HSPLjava/util/HashMap;-><init>(Ljava/util/Map;)V+]Ljava/util/HashMap;Ljava/util/HashMap; HSPLjava/util/HashMap;->afterNodeAccess(Ljava/util/HashMap$Node;)V HSPLjava/util/HashMap;->afterNodeInsertion(Z)V HSPLjava/util/HashMap;->afterNodeRemoval(Ljava/util/HashMap$Node;)V @@ -5656,33 +5746,36 @@ HSPLjava/util/HashMap;->containsKey(Ljava/lang/Object;)Z+]Ljava/util/HashMap;mis HSPLjava/util/HashMap;->containsValue(Ljava/lang/Object;)Z HSPLjava/util/HashMap;->entrySet()Ljava/util/Set; HSPLjava/util/HashMap;->forEach(Ljava/util/function/BiConsumer;)V -HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;megamorphic_types -HSPLjava/util/HashMap;->getNode(ILjava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;missing_types +HSPLjava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types +HSPLjava/util/HashMap;->getNode(Ljava/lang/Object;)Ljava/util/HashMap$Node;+]Ljava/lang/Object;megamorphic_types HSPLjava/util/HashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/HashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types +HSPLjava/util/HashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types HSPLjava/util/HashMap;->internalWriteEntries(Ljava/io/ObjectOutputStream;)V HSPLjava/util/HashMap;->isEmpty()Z HSPLjava/util/HashMap;->keySet()Ljava/util/Set; +HSPLjava/util/HashMap;->keysToArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/HashMap;->loadFactor()F HSPLjava/util/HashMap;->merge(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; HSPLjava/util/HashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node; HSPLjava/util/HashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode; +HSPLjava/util/HashMap;->prepareArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types -HSPLjava/util/HashMap;->putAll(Ljava/util/Map;)V+]Ljava/util/HashMap;missing_types +HSPLjava/util/HashMap;->putAll(Ljava/util/Map;)V HSPLjava/util/HashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/HashMap;->putMapEntries(Ljava/util/Map;Z)V+]Ljava/util/HashMap;missing_types]Ljava/util/Map$Entry;megamorphic_types]Ljava/util/Map;megamorphic_types]Ljava/util/Iterator;megamorphic_types]Ljava/util/Set;megamorphic_types +HSPLjava/util/HashMap;->putMapEntries(Ljava/util/Map;Z)V+]Ljava/util/HashMap;missing_types]Ljava/util/Map$Entry;megamorphic_types]Ljava/util/Map;missing_types]Ljava/util/Iterator;missing_types]Ljava/util/Set;missing_types HSPLjava/util/HashMap;->putVal(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;megamorphic_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode; HSPLjava/util/HashMap;->readObject(Ljava/io/ObjectInputStream;)V HSPLjava/util/HashMap;->reinitialize()V HSPLjava/util/HashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/HashMap;missing_types -HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode; +HSPLjava/util/HashMap;->removeNode(ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/util/HashMap$Node;+]Ljava/util/HashMap;missing_types]Ljava/lang/Object;missing_types HSPLjava/util/HashMap;->replacementNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node; HSPLjava/util/HashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode; -HSPLjava/util/HashMap;->resize()[Ljava/util/HashMap$Node;+]Ljava/util/HashMap$TreeNode;Ljava/util/HashMap$TreeNode; +HSPLjava/util/HashMap;->resize()[Ljava/util/HashMap$Node; HSPLjava/util/HashMap;->size()I HSPLjava/util/HashMap;->tableSizeFor(I)I HSPLjava/util/HashMap;->treeifyBin([Ljava/util/HashMap$Node;I)V HSPLjava/util/HashMap;->values()Ljava/util/Collection; +HSPLjava/util/HashMap;->valuesToArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/HashMap;->writeObject(Ljava/io/ObjectOutputStream;)V HSPLjava/util/HashSet;-><init>()V HSPLjava/util/HashSet;-><init>(I)V @@ -5696,7 +5789,7 @@ HSPLjava/util/HashSet;->contains(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/ HSPLjava/util/HashSet;->isEmpty()Z HSPLjava/util/HashSet;->iterator()Ljava/util/Iterator;+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap;]Ljava/util/Set;Ljava/util/HashMap$KeySet;,Ljava/util/LinkedHashMap$LinkedKeySet; HSPLjava/util/HashSet;->readObject(Ljava/io/ObjectInputStream;)V -HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap; +HSPLjava/util/HashSet;->remove(Ljava/lang/Object;)Z+]Ljava/util/HashMap;Ljava/util/HashMap; HSPLjava/util/HashSet;->size()I+]Ljava/util/HashMap;Ljava/util/HashMap;,Ljava/util/LinkedHashMap; HSPLjava/util/HashSet;->spliterator()Ljava/util/Spliterator; HSPLjava/util/HashSet;->writeObject(Ljava/io/ObjectOutputStream;)V @@ -5748,6 +5841,7 @@ HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/lang/Object; HSPLjava/util/IdentityHashMap$EntryIterator;->next()Ljava/util/Map$Entry; HSPLjava/util/IdentityHashMap$EntrySet;-><init>(Ljava/util/IdentityHashMap;)V HSPLjava/util/IdentityHashMap$EntrySet;->iterator()Ljava/util/Iterator; +HSPLjava/util/IdentityHashMap$EntrySet;->size()I HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;-><init>(Ljava/util/IdentityHashMap;)V HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->hasNext()Z HSPLjava/util/IdentityHashMap$IdentityHashMapIterator;->nextIndex()I @@ -5783,29 +5877,30 @@ HSPLjava/util/IdentityHashMap;->unmaskNull(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/IdentityHashMap;->values()Ljava/util/Collection; HSPLjava/util/ImmutableCollections$AbstractImmutableCollection;-><init>()V HSPLjava/util/ImmutableCollections$AbstractImmutableList;-><init>()V -HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator;+]Ljava/util/ImmutableCollections$AbstractImmutableList;Ljava/util/ImmutableCollections$ListN;,Ljava/util/ImmutableCollections$List12; +HSPLjava/util/ImmutableCollections$AbstractImmutableList;->iterator()Ljava/util/Iterator; HSPLjava/util/ImmutableCollections$AbstractImmutableMap;-><init>()V HSPLjava/util/ImmutableCollections$AbstractImmutableSet;-><init>()V HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;)V HSPLjava/util/ImmutableCollections$List12;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V HSPLjava/util/ImmutableCollections$List12;->get(I)Ljava/lang/Object; HSPLjava/util/ImmutableCollections$List12;->size()I -HSPLjava/util/ImmutableCollections$ListN;-><init>([Ljava/lang/Object;)V +HSPLjava/util/ImmutableCollections$ListItr;-><init>(Ljava/util/List;I)V +HSPLjava/util/ImmutableCollections$ListItr;->hasNext()Z +HSPLjava/util/ImmutableCollections$ListItr;->next()Ljava/lang/Object; HSPLjava/util/ImmutableCollections$ListN;->get(I)Ljava/lang/Object; HSPLjava/util/ImmutableCollections$ListN;->size()I +HSPLjava/util/ImmutableCollections$Map1;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V HSPLjava/util/ImmutableCollections$Map1;->entrySet()Ljava/util/Set; HSPLjava/util/ImmutableCollections$MapN;-><init>([Ljava/lang/Object;)V +HSPLjava/util/ImmutableCollections$MapN;->containsKey(Ljava/lang/Object;)Z HSPLjava/util/ImmutableCollections$MapN;->get(Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Landroid/hardware/biometrics/BiometricSourceType; -HSPLjava/util/ImmutableCollections$Set0;->instance()Ljava/util/ImmutableCollections$Set0; -HSPLjava/util/ImmutableCollections$Set1;-><init>(Ljava/lang/Object;)V -HSPLjava/util/ImmutableCollections$Set1;->iterator()Ljava/util/Iterator; +HSPLjava/util/ImmutableCollections$MapN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String; HSPLjava/util/ImmutableCollections$SetN;-><init>([Ljava/lang/Object;)V HSPLjava/util/ImmutableCollections$SetN;->contains(Ljava/lang/Object;)Z -HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/util/Pair;,Ljava/lang/String; +HSPLjava/util/ImmutableCollections$SetN;->probe(Ljava/lang/Object;)I HSPLjava/util/ImmutableCollections;-><clinit>()V -HSPLjava/util/ImmutableCollections;->emptyList()Ljava/util/List; -HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;Ljava/util/AbstractList$Itr;,Landroid/util/MapCollections$ArrayIterator; +HSPLjava/util/ImmutableCollections;->listCopy(Ljava/util/Collection;)Ljava/util/List; +HSPLjava/util/Iterator;->forEachRemaining(Ljava/util/function/Consumer;)V+]Ljava/util/function/Consumer;missing_types]Ljava/util/Iterator;Landroid/util/MapCollections$ArrayIterator; HSPLjava/util/JumboEnumSet$EnumSetIterator;-><init>(Ljava/util/JumboEnumSet;)V HSPLjava/util/JumboEnumSet$EnumSetIterator;->hasNext()Z HSPLjava/util/JumboEnumSet$EnumSetIterator;->next()Ljava/lang/Enum; @@ -5829,16 +5924,16 @@ HSPLjava/util/LinkedHashMap$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 +5952,7 @@ HSPLjava/util/LinkedHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/u HSPLjava/util/LinkedHashMap;->keySet()Ljava/util/Set; HSPLjava/util/LinkedHashMap;->linkNodeLast(Ljava/util/LinkedHashMap$LinkedHashMapEntry;)V HSPLjava/util/LinkedHashMap;->newNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node; +HSPLjava/util/LinkedHashMap;->newTreeNode(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode; HSPLjava/util/LinkedHashMap;->reinitialize()V HSPLjava/util/LinkedHashMap;->removeEldestEntry(Ljava/util/Map$Entry;)Z HSPLjava/util/LinkedHashMap;->replacementTreeNode(Ljava/util/HashMap$Node;Ljava/util/HashMap$Node;)Ljava/util/HashMap$TreeNode; @@ -5866,7 +5962,7 @@ HSPLjava/util/LinkedHashSet;-><init>()V HSPLjava/util/LinkedHashSet;-><init>(I)V HSPLjava/util/LinkedHashSet;-><init>(Ljava/util/Collection;)V HSPLjava/util/LinkedList$ListItr;-><init>(Ljava/util/LinkedList;I)V -HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V+]Ljava/util/LinkedList;Ljava/util/LinkedList;]Ljava/util/LinkedList$ListItr;Ljava/util/LinkedList$ListItr; +HSPLjava/util/LinkedList$ListItr;->add(Ljava/lang/Object;)V HSPLjava/util/LinkedList$ListItr;->checkForComodification()V HSPLjava/util/LinkedList$ListItr;->hasNext()Z HSPLjava/util/LinkedList$ListItr;->hasPrevious()Z @@ -5878,7 +5974,7 @@ HSPLjava/util/LinkedList$Node;-><init>(Ljava/util/LinkedList$Node;Ljava/lang/Obj HSPLjava/util/LinkedList;-><init>()V HSPLjava/util/LinkedList;-><init>(Ljava/util/Collection;)V HSPLjava/util/LinkedList;->add(ILjava/lang/Object;)V -HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z +HSPLjava/util/LinkedList;->add(Ljava/lang/Object;)Z+]Ljava/util/LinkedList;missing_types HSPLjava/util/LinkedList;->addAll(ILjava/util/Collection;)Z HSPLjava/util/LinkedList;->addAll(Ljava/util/Collection;)Z HSPLjava/util/LinkedList;->addFirst(Ljava/lang/Object;)V @@ -5905,10 +6001,11 @@ HSPLjava/util/LinkedList;->peekFirst()Ljava/lang/Object; HSPLjava/util/LinkedList;->peekLast()Ljava/lang/Object; HSPLjava/util/LinkedList;->poll()Ljava/lang/Object; HSPLjava/util/LinkedList;->pollFirst()Ljava/lang/Object; +HSPLjava/util/LinkedList;->pollLast()Ljava/lang/Object; HSPLjava/util/LinkedList;->pop()Ljava/lang/Object; HSPLjava/util/LinkedList;->push(Ljava/lang/Object;)V HSPLjava/util/LinkedList;->remove()Ljava/lang/Object; -HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object; +HSPLjava/util/LinkedList;->remove(I)Ljava/lang/Object;+]Ljava/util/LinkedList;Ljava/util/LinkedList; HSPLjava/util/LinkedList;->remove(Ljava/lang/Object;)Z HSPLjava/util/LinkedList;->removeFirst()Ljava/lang/Object; HSPLjava/util/LinkedList;->removeLast()Ljava/lang/Object; @@ -5919,11 +6016,13 @@ HSPLjava/util/LinkedList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; HSPLjava/util/LinkedList;->unlink(Ljava/util/LinkedList$Node;)Ljava/lang/Object; HSPLjava/util/LinkedList;->unlinkFirst(Ljava/util/LinkedList$Node;)Ljava/lang/Object; HSPLjava/util/LinkedList;->unlinkLast(Ljava/util/LinkedList$Node;)Ljava/lang/Object; +HSPLjava/util/List;->copyOf(Ljava/util/Collection;)Ljava/util/List; HSPLjava/util/List;->of()Ljava/util/List; HSPLjava/util/List;->of(Ljava/lang/Object;)Ljava/util/List; HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; HSPLjava/util/List;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; -HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;]Ljava/util/List;Ljava/util/LinkedList; +HSPLjava/util/List;->of([Ljava/lang/Object;)Ljava/util/List; +HSPLjava/util/List;->sort(Ljava/util/Comparator;)V+]Ljava/util/ListIterator;Ljava/util/LinkedList$ListItr;,Ljava/util/ArrayList$SubList$1;]Ljava/util/List;Ljava/util/LinkedList;,Ljava/util/ArrayList$SubList; HSPLjava/util/List;->spliterator()Ljava/util/Spliterator; HSPLjava/util/Locale$Builder;-><init>()V HSPLjava/util/Locale$Builder;->build()Ljava/util/Locale; @@ -5932,12 +6031,12 @@ HSPLjava/util/Locale$Builder;->setRegion(Ljava/lang/String;)Ljava/util/Locale$Bu HSPLjava/util/Locale$Builder;->setScript(Ljava/lang/String;)Ljava/util/Locale$Builder; HSPLjava/util/Locale$Builder;->setVariant(Ljava/lang/String;)Ljava/util/Locale$Builder; HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/Locale$Cache;->createObject(Ljava/util/Locale$LocaleKey;)Ljava/util/Locale; +HSPLjava/util/Locale$Cache;->createObject(Ljava/lang/Object;)Ljava/util/Locale; HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetbase(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/BaseLocale; HSPLjava/util/Locale$LocaleKey;->-$$Nest$fgetexts(Ljava/util/Locale$LocaleKey;)Lsun/util/locale/LocaleExtensions; HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)V HSPLjava/util/Locale$LocaleKey;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;Ljava/util/Locale$LocaleKey-IA;)V -HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; +HSPLjava/util/Locale$LocaleKey;->equals(Ljava/lang/Object;)Z HSPLjava/util/Locale$LocaleKey;->hashCode()I HSPLjava/util/Locale;-><init>(Ljava/lang/String;)V HSPLjava/util/Locale;-><init>(Ljava/lang/String;Ljava/lang/String;)V @@ -5946,15 +6045,15 @@ HSPLjava/util/Locale;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/Local HSPLjava/util/Locale;-><init>(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;Ljava/util/Locale-IA;)V HSPLjava/util/Locale;->cleanCache()V HSPLjava/util/Locale;->clone()Ljava/lang/Object; -HSPLjava/util/Locale;->convertOldISOCodes(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/util/Locale;->convertOldISOCodes(Ljava/lang/String;)Ljava/lang/String; HSPLjava/util/Locale;->equals(Ljava/lang/Object;)Z+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->forLanguageTag(Ljava/lang/String;)Ljava/util/Locale; HSPLjava/util/Locale;->getAvailableLocales()[Ljava/util/Locale; HSPLjava/util/Locale;->getBaseLocale()Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->getCompatibilityExtensions(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/LocaleExtensions; -HSPLjava/util/Locale;->getCountry()Ljava/lang/String; +HSPLjava/util/Locale;->getCountry()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->getDefault()Ljava/util/Locale; -HSPLjava/util/Locale;->getDefault(Ljava/util/Locale$Category;)Ljava/util/Locale; +HSPLjava/util/Locale;->getDefault(Ljava/util/Locale$Category;)Ljava/util/Locale;+]Ljava/util/Locale$Category;Ljava/util/Locale$Category; HSPLjava/util/Locale;->getDisplayCountry(Ljava/util/Locale;)Ljava/lang/String; HSPLjava/util/Locale;->getDisplayLanguage()Ljava/lang/String; HSPLjava/util/Locale;->getDisplayLanguage(Ljava/util/Locale;)Ljava/lang/String; @@ -5966,22 +6065,25 @@ HSPLjava/util/Locale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lan HSPLjava/util/Locale;->getInstance(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)Ljava/util/Locale; HSPLjava/util/Locale;->getLanguage()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->getScript()Ljava/lang/String; -HSPLjava/util/Locale;->getVariant()Ljava/lang/String; +HSPLjava/util/Locale;->getUnicodeLocaleType(Ljava/lang/String;)Ljava/lang/String; +HSPLjava/util/Locale;->getVariant()Ljava/lang/String;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->hasExtensions()Z HSPLjava/util/Locale;->hashCode()I +HSPLjava/util/Locale;->isUnicodeExtensionKey(Ljava/lang/String;)Z HSPLjava/util/Locale;->isValidBcp47Alpha(Ljava/lang/String;II)Z HSPLjava/util/Locale;->normalizeAndValidateLanguage(Ljava/lang/String;Z)Ljava/lang/String; HSPLjava/util/Locale;->normalizeAndValidateRegion(Ljava/lang/String;Z)Ljava/lang/String; -HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V+]Ljava/io/ObjectInputStream;Landroid/os/Parcel$2;]Ljava/io/ObjectInputStream$GetField;Ljava/io/ObjectInputStream$GetFieldImpl; -HSPLjava/util/Locale;->readResolve()Ljava/lang/Object;+]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; +HSPLjava/util/Locale;->readObject(Ljava/io/ObjectInputStream;)V +HSPLjava/util/Locale;->readResolve()Ljava/lang/Object; HSPLjava/util/Locale;->setDefault(Ljava/util/Locale$Category;Ljava/util/Locale;)V HSPLjava/util/Locale;->setDefault(Ljava/util/Locale;)V -HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String; -HSPLjava/util/Locale;->toString()Ljava/lang/String; +HSPLjava/util/Locale;->toLanguageTag()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/LanguageTag;Lsun/util/locale/LanguageTag;]Ljava/util/List;Ljava/util/Collections$EmptyList;]Ljava/util/Iterator;Ljava/util/Collections$EmptyIterator; +HSPLjava/util/Locale;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/util/locale/BaseLocale;Lsun/util/locale/BaseLocale; HSPLjava/util/Locale;->writeObject(Ljava/io/ObjectOutputStream;)V HSPLjava/util/Map;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;+]Ljava/util/function/Function;missing_types]Ljava/util/Map;Landroid/util/ArrayMap; HSPLjava/util/Map;->forEach(Ljava/util/function/BiConsumer;)V HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Landroid/util/ArrayMap;,Ljava/util/ImmutableCollections$MapN; +HSPLjava/util/Map;->of(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map; HSPLjava/util/MissingResourceException;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V HSPLjava/util/NoSuchElementException;-><init>()V HSPLjava/util/NoSuchElementException;-><init>(Ljava/lang/String;)V @@ -5989,7 +6091,7 @@ HSPLjava/util/Objects;->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;->hash([Ljava/lang/Object;)I -HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;megamorphic_types +HSPLjava/util/Objects;->hashCode(Ljava/lang/Object;)I+]Ljava/lang/Object;Ljava/lang/String;,Ljava/lang/Integer;,Ljava/lang/Long; HSPLjava/util/Objects;->nonNull(Ljava/lang/Object;)Z HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/Objects;->requireNonNull(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; @@ -6046,11 +6148,9 @@ HSPLjava/util/PriorityQueue;->poll()Ljava/lang/Object; HSPLjava/util/PriorityQueue;->remove(Ljava/lang/Object;)Z HSPLjava/util/PriorityQueue;->removeAt(I)Ljava/lang/Object; HSPLjava/util/PriorityQueue;->siftDown(ILjava/lang/Object;)V -HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;)V -HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;)V +HSPLjava/util/PriorityQueue;->siftDownComparable(ILjava/lang/Object;[Ljava/lang/Object;I)V +HSPLjava/util/PriorityQueue;->siftDownUsingComparator(ILjava/lang/Object;[Ljava/lang/Object;ILjava/util/Comparator;)V HSPLjava/util/PriorityQueue;->siftUp(ILjava/lang/Object;)V -HSPLjava/util/PriorityQueue;->siftUpComparable(ILjava/lang/Object;)V -HSPLjava/util/PriorityQueue;->siftUpUsingComparator(ILjava/lang/Object;)V HSPLjava/util/PriorityQueue;->size()I HSPLjava/util/PriorityQueue;->toArray()[Ljava/lang/Object; HSPLjava/util/PriorityQueue;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; @@ -6065,7 +6165,7 @@ HSPLjava/util/Properties;->load(Ljava/io/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 @@ -6099,7 +6199,7 @@ HSPLjava/util/RegularEnumSet;->addAll()V HSPLjava/util/RegularEnumSet;->addAll(Ljava/util/Collection;)Z HSPLjava/util/RegularEnumSet;->clear()V HSPLjava/util/RegularEnumSet;->complement()V -HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z +HSPLjava/util/RegularEnumSet;->contains(Ljava/lang/Object;)Z+]Ljava/lang/Object;missing_types]Ljava/lang/Enum;missing_types HSPLjava/util/RegularEnumSet;->containsAll(Ljava/util/Collection;)Z HSPLjava/util/RegularEnumSet;->equals(Ljava/lang/Object;)Z HSPLjava/util/RegularEnumSet;->isEmpty()Z @@ -6109,8 +6209,6 @@ HSPLjava/util/RegularEnumSet;->size()I HSPLjava/util/ResourceBundle$BundleReference;-><init>(Ljava/util/ResourceBundle;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V HSPLjava/util/ResourceBundle$BundleReference;->getCacheKey()Ljava/util/ResourceBundle$CacheKey; HSPLjava/util/ResourceBundle$CacheKey;-><init>(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;)V -HSPLjava/util/ResourceBundle$CacheKey;->calculateHashCode()V -HSPLjava/util/ResourceBundle$CacheKey;->clone()Ljava/lang/Object; HSPLjava/util/ResourceBundle$CacheKey;->equals(Ljava/lang/Object;)Z HSPLjava/util/ResourceBundle$CacheKey;->getCause()Ljava/lang/Throwable; HSPLjava/util/ResourceBundle$CacheKey;->getLoader()Ljava/lang/ClassLoader; @@ -6119,7 +6217,6 @@ HSPLjava/util/ResourceBundle$CacheKey;->getName()Ljava/lang/String; HSPLjava/util/ResourceBundle$CacheKey;->hashCode()I HSPLjava/util/ResourceBundle$CacheKey;->setFormat(Ljava/lang/String;)V HSPLjava/util/ResourceBundle$CacheKey;->setLocale(Ljava/util/Locale;)Ljava/util/ResourceBundle$CacheKey; -HSPLjava/util/ResourceBundle$Control$1;-><init>(Ljava/util/ResourceBundle$Control;ZLjava/lang/ClassLoader;Ljava/lang/String;)V HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/io/InputStream; HSPLjava/util/ResourceBundle$Control$1;->run()Ljava/lang/Object; HSPLjava/util/ResourceBundle$Control$CandidateListCache;->createObject(Ljava/lang/Object;)Ljava/lang/Object; @@ -6133,7 +6230,6 @@ HSPLjava/util/ResourceBundle$Control;->newBundle(Ljava/lang/String;Ljava/util/Lo HSPLjava/util/ResourceBundle$Control;->toBundleName(Ljava/lang/String;Ljava/util/Locale;)Ljava/lang/String; HSPLjava/util/ResourceBundle$Control;->toResourceName(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; HSPLjava/util/ResourceBundle$Control;->toResourceName0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; -HSPLjava/util/ResourceBundle$LoaderReference;-><init>(Ljava/lang/ClassLoader;Ljava/lang/ref/ReferenceQueue;Ljava/util/ResourceBundle$CacheKey;)V HSPLjava/util/ResourceBundle;-><init>()V HSPLjava/util/ResourceBundle;->findBundle(Ljava/util/ResourceBundle$CacheKey;Ljava/util/List;Ljava/util/List;ILjava/util/ResourceBundle$Control;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle; HSPLjava/util/ResourceBundle;->findBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; @@ -6147,9 +6243,6 @@ HSPLjava/util/ResourceBundle;->loadBundle(Ljava/util/ResourceBundle$CacheKey;Lja HSPLjava/util/ResourceBundle;->putBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; HSPLjava/util/ResourceBundle;->setExpirationTime(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)V HSPLjava/util/ResourceBundle;->setParent(Ljava/util/ResourceBundle;)V -HSPLjava/util/Scanner$1;-><init>(Ljava/util/Scanner;I)V -HSPLjava/util/Scanner$1;->create(Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/Scanner$1;->create(Ljava/lang/String;)Ljava/util/regex/Pattern; HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;)V HSPLjava/util/Scanner;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V HSPLjava/util/Scanner;-><init>(Ljava/lang/Readable;Ljava/util/regex/Pattern;)V @@ -6187,6 +6280,7 @@ HSPLjava/util/ServiceLoader;->parse(Ljava/lang/Class;Ljava/net/URL;)Ljava/util/I HSPLjava/util/ServiceLoader;->parseLine(Ljava/lang/Class;Ljava/net/URL;Ljava/io/BufferedReader;ILjava/util/List;)I HSPLjava/util/ServiceLoader;->reload()V HSPLjava/util/Set;->of(Ljava/lang/Object;)Ljava/util/Set; +HSPLjava/util/Set;->of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Set; HSPLjava/util/Set;->of([Ljava/lang/Object;)Ljava/util/Set; HSPLjava/util/Set;->spliterator()Ljava/util/Spliterator; HSPLjava/util/SimpleTimeZone;-><init>(ILjava/lang/String;)V @@ -6205,6 +6299,7 @@ HSPLjava/util/Spliterators$ArraySpliterator;->forEachRemaining(Ljava/util/functi HSPLjava/util/Spliterators$ArraySpliterator;->tryAdvance(Ljava/util/function/Consumer;)Z HSPLjava/util/Spliterators$EmptySpliterator$OfInt;->forEachRemaining(Ljava/util/function/IntConsumer;)V HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->forEachRemaining(Ljava/util/function/Consumer;)V +HSPLjava/util/Spliterators$EmptySpliterator$OfRef;->tryAdvance(Ljava/util/function/Consumer;)Z HSPLjava/util/Spliterators$EmptySpliterator;->characteristics()I HSPLjava/util/Spliterators$EmptySpliterator;->estimateSize()J HSPLjava/util/Spliterators$EmptySpliterator;->forEachRemaining(Ljava/lang/Object;)V @@ -6234,7 +6329,8 @@ HSPLjava/util/Stack;->push(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;)V HSPLjava/util/StringJoiner;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V HSPLjava/util/StringJoiner;->add(Ljava/lang/CharSequence;)Ljava/util/StringJoiner; -HSPLjava/util/StringJoiner;->prepareBuilder()Ljava/lang/StringBuilder; +HSPLjava/util/StringJoiner;->compactElts()V +HSPLjava/util/StringJoiner;->getChars(Ljava/lang/String;[CI)I HSPLjava/util/StringJoiner;->toString()Ljava/lang/String; HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;)V HSPLjava/util/StringTokenizer;-><init>(Ljava/lang/String;Ljava/lang/String;)V @@ -6257,7 +6353,7 @@ HSPLjava/util/TaskQueue;->removeMin()V HSPLjava/util/TaskQueue;->rescheduleMin(J)V HSPLjava/util/TimSort;-><init>([Ljava/lang/Object;Ljava/util/Comparator;[Ljava/lang/Object;II)V HSPLjava/util/TimSort;->binarySort([Ljava/lang/Object;IIILjava/util/Comparator;)V -HSPLjava/util/TimSort;->countRunAndMakeAscending([Ljava/lang/Object;IILjava/util/Comparator;)I+]Ljava/util/Comparator;missing_types +HSPLjava/util/TimSort;->countRunAndMakeAscending([Ljava/lang/Object;IILjava/util/Comparator;)I HSPLjava/util/TimSort;->ensureCapacity(I)[Ljava/lang/Object; HSPLjava/util/TimSort;->gallopLeft(Ljava/lang/Object;[Ljava/lang/Object;IIILjava/util/Comparator;)I HSPLjava/util/TimSort;->gallopRight(Ljava/lang/Object;[Ljava/lang/Object;IIILjava/util/Comparator;)I @@ -6275,12 +6371,12 @@ HSPLjava/util/TimeZone;->appendNumber(Ljava/lang/StringBuilder;II)V HSPLjava/util/TimeZone;->clone()Ljava/lang/Object; HSPLjava/util/TimeZone;->createGmtOffsetString(ZZI)Ljava/lang/String; HSPLjava/util/TimeZone;->getAvailableIDs()[Ljava/lang/String; -HSPLjava/util/TimeZone;->getDefault()Ljava/util/TimeZone;+]Ljava/util/TimeZone;Llibcore/util/ZoneInfo; -HSPLjava/util/TimeZone;->getDefaultRef()Ljava/util/TimeZone;+]Ljava/lang/String;Ljava/lang/String;]Ljava/util/function/Supplier;Lcom/android/internal/os/RuntimeInit$$ExternalSyntheticLambda1; +HSPLjava/util/TimeZone;->getDefault()Ljava/util/TimeZone; +HSPLjava/util/TimeZone;->getDefaultRef()Ljava/util/TimeZone; HSPLjava/util/TimeZone;->getDisplayName(ZI)Ljava/lang/String; HSPLjava/util/TimeZone;->getDisplayName(ZILjava/util/Locale;)Ljava/lang/String; HSPLjava/util/TimeZone;->getID()Ljava/lang/String; -HSPLjava/util/TimeZone;->getTimeZone(Ljava/lang/String;)Ljava/util/TimeZone;+]Ljava/util/TimeZone;Ljava/util/SimpleTimeZone;]Lcom/android/i18n/timezone/ZoneInfoDb;Lcom/android/i18n/timezone/ZoneInfoDb; +HSPLjava/util/TimeZone;->getTimeZone(Ljava/lang/String;)Ljava/util/TimeZone; HSPLjava/util/TimeZone;->setDefault(Ljava/util/TimeZone;)V HSPLjava/util/TimeZone;->setID(Ljava/lang/String;)V HSPLjava/util/TimeZone;->toZoneId()Ljava/time/ZoneId; @@ -6292,6 +6388,7 @@ HSPLjava/util/Timer;-><init>(Ljava/lang/String;Z)V HSPLjava/util/Timer;->cancel()V HSPLjava/util/Timer;->sched(Ljava/util/TimerTask;JJ)V HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;J)V +HSPLjava/util/Timer;->schedule(Ljava/util/TimerTask;JJ)V HSPLjava/util/Timer;->scheduleAtFixedRate(Ljava/util/TimerTask;JJ)V HSPLjava/util/Timer;->serialNumber()I HSPLjava/util/TimerTask;-><init>()V @@ -6311,16 +6408,16 @@ HSPLjava/util/TreeMap$EntryIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeM HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator; HSPLjava/util/TreeMap$EntryIterator;->next()Ljava/util/Map$Entry;+]Ljava/util/TreeMap$EntryIterator;Ljava/util/TreeMap$EntryIterator; HSPLjava/util/TreeMap$EntrySet;-><init>(Ljava/util/TreeMap;)V -HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator; +HSPLjava/util/TreeMap$EntrySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap; HSPLjava/util/TreeMap$EntrySet;->size()I HSPLjava/util/TreeMap$KeyIterator;-><init>(Ljava/util/TreeMap;Ljava/util/TreeMap$TreeMapEntry;)V -HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$KeyIterator;Ljava/util/TreeMap$KeyIterator; +HSPLjava/util/TreeMap$KeyIterator;->next()Ljava/lang/Object; HSPLjava/util/TreeMap$KeySet;-><init>(Ljava/util/NavigableMap;)V HSPLjava/util/TreeMap$KeySet;->isEmpty()Z -HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap; +HSPLjava/util/TreeMap$KeySet;->iterator()Ljava/util/Iterator; HSPLjava/util/TreeMap$KeySet;->size()I HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;-><init>(Ljava/util/TreeMap$NavigableSubMap;Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;)V -HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object;+]Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;Ljava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator; +HSPLjava/util/TreeMap$NavigableSubMap$DescendingSubMapKeyIterator;->next()Ljava/lang/Object; HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;-><init>(Ljava/util/TreeMap$NavigableSubMap;)V HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->isEmpty()Z HSPLjava/util/TreeMap$NavigableSubMap$EntrySetView;->size()I @@ -6369,12 +6466,12 @@ HSPLjava/util/TreeMap;->addAllForTreeSet(Ljava/util/SortedSet;Ljava/lang/Object; HSPLjava/util/TreeMap;->buildFromSorted(IIIILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->buildFromSorted(ILjava/util/Iterator;Ljava/io/ObjectInputStream;Ljava/lang/Object;)V HSPLjava/util/TreeMap;->ceilingEntry(Ljava/lang/Object;)Ljava/util/Map$Entry; -HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/TreeMap;->ceilingKey(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap; HSPLjava/util/TreeMap;->clear()V HSPLjava/util/TreeMap;->clone()Ljava/lang/Object; HSPLjava/util/TreeMap;->colorOf(Ljava/util/TreeMap$TreeMapEntry;)Z HSPLjava/util/TreeMap;->comparator()Ljava/util/Comparator; -HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer; +HSPLjava/util/TreeMap;->compare(Ljava/lang/Object;Ljava/lang/Object;)I+]Ljava/util/Comparator;missing_types]Ljava/lang/Comparable;missing_types HSPLjava/util/TreeMap;->computeRedLevel(I)I HSPLjava/util/TreeMap;->containsKey(Ljava/lang/Object;)Z HSPLjava/util/TreeMap;->deleteEntry(Ljava/util/TreeMap$TreeMapEntry;)V @@ -6389,8 +6486,8 @@ HSPLjava/util/TreeMap;->floorEntry(Ljava/lang/Object;)Ljava/util/Map$Entry; HSPLjava/util/TreeMap;->floorKey(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/TreeMap;->get(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/TreeMap;->getCeilingEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; -HSPLjava/util/TreeMap;->getEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; -HSPLjava/util/TreeMap;->getEntryUsingComparator(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; +HSPLjava/util/TreeMap;->getEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;+]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;missing_types +HSPLjava/util/TreeMap;->getEntryUsingComparator(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry;+]Ljava/util/Comparator;missing_types HSPLjava/util/TreeMap;->getFirstEntry()Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->getFloorEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->getHigherEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; @@ -6398,7 +6495,7 @@ HSPLjava/util/TreeMap;->getLastEntry()Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->getLowerEntry(Ljava/lang/Object;)Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->headMap(Ljava/lang/Object;Z)Ljava/util/NavigableMap; HSPLjava/util/TreeMap;->key(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object; -HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator;+]Ljava/util/TreeMap;Ljava/util/TreeMap; +HSPLjava/util/TreeMap;->keyIterator()Ljava/util/Iterator; HSPLjava/util/TreeMap;->keyOrNull(Ljava/util/TreeMap$TreeMapEntry;)Ljava/lang/Object; HSPLjava/util/TreeMap;->keySet()Ljava/util/Set; HSPLjava/util/TreeMap;->lastKey()Ljava/lang/Object; @@ -6408,7 +6505,7 @@ HSPLjava/util/TreeMap;->navigableKeySet()Ljava/util/NavigableSet; HSPLjava/util/TreeMap;->parentOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry; HSPLjava/util/TreeMap;->pollFirstEntry()Ljava/util/Map$Entry; HSPLjava/util/TreeMap;->predecessor(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry; -HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;Ljava/lang/String;,Ljava/lang/Integer; +HSPLjava/util/TreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Comparator;missing_types]Ljava/util/TreeMap$TreeMapEntry;Ljava/util/TreeMap$TreeMapEntry;]Ljava/util/TreeMap;Ljava/util/TreeMap;]Ljava/lang/Comparable;missing_types HSPLjava/util/TreeMap;->putAll(Ljava/util/Map;)V HSPLjava/util/TreeMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/TreeMap;Ljava/util/TreeMap; HSPLjava/util/TreeMap;->rightOf(Ljava/util/TreeMap$TreeMapEntry;)Ljava/util/TreeMap$TreeMapEntry; @@ -6426,7 +6523,7 @@ HSPLjava/util/TreeSet;-><init>(Ljava/util/Collection;)V HSPLjava/util/TreeSet;-><init>(Ljava/util/Comparator;)V HSPLjava/util/TreeSet;-><init>(Ljava/util/NavigableMap;)V HSPLjava/util/TreeSet;-><init>(Ljava/util/SortedSet;)V -HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap; +HSPLjava/util/TreeSet;->add(Ljava/lang/Object;)Z HSPLjava/util/TreeSet;->addAll(Ljava/util/Collection;)Z HSPLjava/util/TreeSet;->ceiling(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/TreeSet;->clear()V @@ -6434,9 +6531,9 @@ HSPLjava/util/TreeSet;->comparator()Ljava/util/Comparator; HSPLjava/util/TreeSet;->contains(Ljava/lang/Object;)Z HSPLjava/util/TreeSet;->descendingSet()Ljava/util/NavigableSet; HSPLjava/util/TreeSet;->first()Ljava/lang/Object; -HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/NavigableMap;Ljava/util/TreeMap; -HSPLjava/util/TreeSet;->isEmpty()Z -HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator;+]Ljava/util/NavigableMap;Ljava/util/TreeMap;]Ljava/util/NavigableSet;Ljava/util/TreeMap$KeySet; +HSPLjava/util/TreeSet;->floor(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/TreeSet;->isEmpty()Z+]Ljava/util/NavigableMap;Ljava/util/TreeMap; +HSPLjava/util/TreeSet;->iterator()Ljava/util/Iterator; HSPLjava/util/TreeSet;->last()Ljava/lang/Object; HSPLjava/util/TreeSet;->remove(Ljava/lang/Object;)Z HSPLjava/util/TreeSet;->size()I @@ -6447,12 +6544,13 @@ HSPLjava/util/UUID;-><init>([B)V HSPLjava/util/UUID;->digits(JI)Ljava/lang/String; HSPLjava/util/UUID;->equals(Ljava/lang/Object;)Z HSPLjava/util/UUID;->fromString(Ljava/lang/String;)Ljava/util/UUID; +HSPLjava/util/UUID;->fromStringJava8(Ljava/lang/String;)Ljava/util/UUID;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/Long;Ljava/lang/Long; HSPLjava/util/UUID;->getLeastSignificantBits()J HSPLjava/util/UUID;->getMostSignificantBits()J HSPLjava/util/UUID;->hashCode()I HSPLjava/util/UUID;->nameUUIDFromBytes([B)Ljava/util/UUID; HSPLjava/util/UUID;->randomUUID()Ljava/util/UUID; -HSPLjava/util/UUID;->toString()Ljava/lang/String; +HSPLjava/util/UUID;->toString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/util/Vector$1;-><init>(Ljava/util/Vector;)V HSPLjava/util/Vector$1;->hasMoreElements()Z HSPLjava/util/Vector$1;->nextElement()Ljava/lang/Object; @@ -6464,6 +6562,7 @@ HSPLjava/util/Vector;-><init>()V HSPLjava/util/Vector;-><init>(I)V HSPLjava/util/Vector;-><init>(II)V HSPLjava/util/Vector;->add(Ljava/lang/Object;)Z +HSPLjava/util/Vector;->add(Ljava/lang/Object;[Ljava/lang/Object;I)V HSPLjava/util/Vector;->addElement(Ljava/lang/Object;)V HSPLjava/util/Vector;->clear()V HSPLjava/util/Vector;->contains(Ljava/lang/Object;)Z @@ -6471,9 +6570,7 @@ HSPLjava/util/Vector;->copyInto([Ljava/lang/Object;)V HSPLjava/util/Vector;->elementAt(I)Ljava/lang/Object; HSPLjava/util/Vector;->elementData(I)Ljava/lang/Object; HSPLjava/util/Vector;->elements()Ljava/util/Enumeration; -HSPLjava/util/Vector;->ensureCapacityHelper(I)V HSPLjava/util/Vector;->get(I)Ljava/lang/Object; -HSPLjava/util/Vector;->grow(I)V HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;)I HSPLjava/util/Vector;->indexOf(Ljava/lang/Object;I)I HSPLjava/util/Vector;->isEmpty()Z @@ -6494,7 +6591,7 @@ HSPLjava/util/WeakHashMap$EntryIterator;->next()Ljava/util/Map$Entry; HSPLjava/util/WeakHashMap$EntrySet;-><init>(Ljava/util/WeakHashMap;)V HSPLjava/util/WeakHashMap$EntrySet;->iterator()Ljava/util/Iterator; HSPLjava/util/WeakHashMap$HashIterator;-><init>(Ljava/util/WeakHashMap;)V -HSPLjava/util/WeakHashMap$HashIterator;->hasNext()Z +HSPLjava/util/WeakHashMap$HashIterator;->hasNext()Z+]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry; HSPLjava/util/WeakHashMap$HashIterator;->nextEntry()Ljava/util/WeakHashMap$Entry; HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;)V HSPLjava/util/WeakHashMap$KeyIterator;-><init>(Ljava/util/WeakHashMap;Ljava/util/WeakHashMap$KeyIterator-IA;)V @@ -6513,19 +6610,18 @@ HSPLjava/util/WeakHashMap;-><init>(IF)V HSPLjava/util/WeakHashMap;->clear()V HSPLjava/util/WeakHashMap;->containsKey(Ljava/lang/Object;)Z HSPLjava/util/WeakHashMap;->entrySet()Ljava/util/Set; -HSPLjava/util/WeakHashMap;->eq(Ljava/lang/Object;Ljava/lang/Object;)Z -HSPLjava/util/WeakHashMap;->expungeStaleEntries()V -HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/WeakHashMap;->expungeStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue; +HSPLjava/util/WeakHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry; HSPLjava/util/WeakHashMap;->getEntry(Ljava/lang/Object;)Ljava/util/WeakHashMap$Entry; HSPLjava/util/WeakHashMap;->getTable()[Ljava/util/WeakHashMap$Entry; -HSPLjava/util/WeakHashMap;->hash(Ljava/lang/Object;)I +HSPLjava/util/WeakHashMap;->hash(Ljava/lang/Object;)I+]Ljava/lang/Object;missing_types HSPLjava/util/WeakHashMap;->indexFor(II)I HSPLjava/util/WeakHashMap;->isEmpty()Z HSPLjava/util/WeakHashMap;->keySet()Ljava/util/Set; HSPLjava/util/WeakHashMap;->maskNull(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/WeakHashMap;->newTable(I)[Ljava/util/WeakHashMap$Entry; -HSPLjava/util/WeakHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap; -HSPLjava/util/WeakHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/WeakHashMap;Ljava/util/WeakHashMap;]Ljava/util/WeakHashMap$Entry;Ljava/util/WeakHashMap$Entry; +HSPLjava/util/WeakHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/WeakHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/WeakHashMap;->resize(I)V HSPLjava/util/WeakHashMap;->size()I HSPLjava/util/WeakHashMap;->transfer([Ljava/util/WeakHashMap$Entry;[Ljava/util/WeakHashMap$Entry;)V @@ -6558,7 +6654,7 @@ HSPLjava/util/concurrent/CancellationException;-><init>(Ljava/lang/String;)V HSPLjava/util/concurrent/CompletableFuture$AsyncRun;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/lang/Runnable;)V HSPLjava/util/concurrent/CompletableFuture$AsyncRun;->run()V HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;-><init>(Ljava/util/concurrent/CompletableFuture;Ljava/util/function/Supplier;)V -HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V+]Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture; +HSPLjava/util/concurrent/CompletableFuture$AsyncSupply;->run()V HSPLjava/util/concurrent/CompletableFuture$Completion;-><init>()V HSPLjava/util/concurrent/CompletableFuture$Signaller;-><init>(ZJJ)V HSPLjava/util/concurrent/CompletableFuture$Signaller;->block()Z @@ -6566,7 +6662,7 @@ HSPLjava/util/concurrent/CompletableFuture$Signaller;->isReleasable()Z HSPLjava/util/concurrent/CompletableFuture$Signaller;->tryFire(I)Ljava/util/concurrent/CompletableFuture; HSPLjava/util/concurrent/CompletableFuture;-><init>()V HSPLjava/util/concurrent/CompletableFuture;->asyncRunStage(Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)Ljava/util/concurrent/CompletableFuture; -HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;+]Ljava/util/concurrent/Executor;Ljava/util/concurrent/ForkJoinPool; +HSPLjava/util/concurrent/CompletableFuture;->asyncSupplyStage(Ljava/util/concurrent/Executor;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture; HSPLjava/util/concurrent/CompletableFuture;->complete(Ljava/lang/Object;)Z HSPLjava/util/concurrent/CompletableFuture;->completeNull()Z HSPLjava/util/concurrent/CompletableFuture;->completeValue(Ljava/lang/Object;)Z @@ -6589,7 +6685,7 @@ HSPLjava/util/concurrent/ConcurrentHashMap$BaseIterator;->hasNext()Z HSPLjava/util/concurrent/ConcurrentHashMap$BaseIterator;->remove()V HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;)V HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->size()I -HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object; +HSPLjava/util/concurrent/ConcurrentHashMap$CollectionView;->toArray()[Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap;]Ljava/util/Iterator;Ljava/util/concurrent/ConcurrentHashMap$ValueIterator;,Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;]Ljava/util/concurrent/ConcurrentHashMap$CollectionView;Ljava/util/concurrent/ConcurrentHashMap$KeySetView;,Ljava/util/concurrent/ConcurrentHashMap$ValuesView; HSPLjava/util/concurrent/ConcurrentHashMap$CounterCell;-><init>(J)V HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V HSPLjava/util/concurrent/ConcurrentHashMap$EntryIterator;->next()Ljava/lang/Object; @@ -6599,7 +6695,7 @@ HSPLjava/util/concurrent/ConcurrentHashMap$EntrySetView;->iterator()Ljava/util/I HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;)V HSPLjava/util/concurrent/ConcurrentHashMap$ForwardingNode;->find(ILjava/lang/Object;)Ljava/util/concurrent/ConcurrentHashMap$Node; HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;-><init>([Ljava/util/concurrent/ConcurrentHashMap$Node;IIILjava/util/concurrent/ConcurrentHashMap;)V -HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object; +HSPLjava/util/concurrent/ConcurrentHashMap$KeyIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentHashMap$KeyIterator;Ljava/util/concurrent/ConcurrentHashMap$KeyIterator; HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;-><init>(Ljava/util/concurrent/ConcurrentHashMap;Ljava/lang/Object;)V HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->iterator()Ljava/util/Iterator; HSPLjava/util/concurrent/ConcurrentHashMap$KeySetView;->spliterator()Ljava/util/Spliterator; @@ -6627,8 +6723,9 @@ HSPLjava/util/concurrent/ConcurrentHashMap;->clear()V HSPLjava/util/concurrent/ConcurrentHashMap;->computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; HSPLjava/util/concurrent/ConcurrentHashMap;->containsKey(Ljava/lang/Object;)Z HSPLjava/util/concurrent/ConcurrentHashMap;->entrySet()Ljava/util/Set; +HSPLjava/util/concurrent/ConcurrentHashMap;->forEach(Ljava/util/function/BiConsumer;)V HSPLjava/util/concurrent/ConcurrentHashMap;->fullAddCount(JZ)V -HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;missing_types +HSPLjava/util/concurrent/ConcurrentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;megamorphic_types HSPLjava/util/concurrent/ConcurrentHashMap;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/concurrent/ConcurrentHashMap;->helpTransfer([Ljava/util/concurrent/ConcurrentHashMap$Node;Ljava/util/concurrent/ConcurrentHashMap$Node;)[Ljava/util/concurrent/ConcurrentHashMap$Node; HSPLjava/util/concurrent/ConcurrentHashMap;->initTable()[Ljava/util/concurrent/ConcurrentHashMap$Node; @@ -6638,16 +6735,17 @@ HSPLjava/util/concurrent/ConcurrentHashMap;->mappingCount()J HSPLjava/util/concurrent/ConcurrentHashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/concurrent/ConcurrentHashMap;->putAll(Ljava/util/Map;)V HSPLjava/util/concurrent/ConcurrentHashMap;->putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object; +HSPLjava/util/concurrent/ConcurrentHashMap;->putVal(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/util/locale/BaseLocale$Key; HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/concurrent/ConcurrentHashMap;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z HSPLjava/util/concurrent/ConcurrentHashMap;->replace(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z -HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLjava/util/concurrent/ConcurrentHashMap;->replaceNode(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/Object;Lsun/nio/ch/FileKey;,Ljava/lang/reflect/Proxy$Key1;,Lsun/util/locale/BaseLocale; HSPLjava/util/concurrent/ConcurrentHashMap;->resizeStamp(I)I HSPLjava/util/concurrent/ConcurrentHashMap;->setTabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;ILjava/util/concurrent/ConcurrentHashMap$Node;)V HSPLjava/util/concurrent/ConcurrentHashMap;->size()I HSPLjava/util/concurrent/ConcurrentHashMap;->spread(I)I HSPLjava/util/concurrent/ConcurrentHashMap;->sumCount()J -HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node; +HSPLjava/util/concurrent/ConcurrentHashMap;->tabAt([Ljava/util/concurrent/ConcurrentHashMap$Node;I)Ljava/util/concurrent/ConcurrentHashMap$Node;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/ConcurrentHashMap;->tableSizeFor(I)I HSPLjava/util/concurrent/ConcurrentHashMap;->transfer([Ljava/util/concurrent/ConcurrentHashMap$Node;[Ljava/util/concurrent/ConcurrentHashMap$Node;)V HSPLjava/util/concurrent/ConcurrentHashMap;->treeifyBin([Ljava/util/concurrent/ConcurrentHashMap$Node;I)V @@ -6686,7 +6784,7 @@ HSPLjava/util/concurrent/ConcurrentLinkedQueue;->add(Ljava/lang/Object;)Z HSPLjava/util/concurrent/ConcurrentLinkedQueue;->bulkRemove(Ljava/util/function/Predicate;)Z HSPLjava/util/concurrent/ConcurrentLinkedQueue;->clear()V HSPLjava/util/concurrent/ConcurrentLinkedQueue;->contains(Ljava/lang/Object;)Z -HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node;+]Ljava/util/concurrent/ConcurrentLinkedQueue;Ljava/util/concurrent/ConcurrentLinkedQueue; +HSPLjava/util/concurrent/ConcurrentLinkedQueue;->first()Ljava/util/concurrent/ConcurrentLinkedQueue$Node; HSPLjava/util/concurrent/ConcurrentLinkedQueue;->isEmpty()Z HSPLjava/util/concurrent/ConcurrentLinkedQueue;->iterator()Ljava/util/Iterator; HSPLjava/util/concurrent/ConcurrentLinkedQueue;->lambda$clear$2(Ljava/lang/Object;)Z @@ -6723,9 +6821,9 @@ HSPLjava/util/concurrent/CopyOnWriteArrayList$$ExternalSyntheticLambda2;-><init> HSPLjava/util/concurrent/CopyOnWriteArrayList$$ExternalSyntheticLambda2;->test(Ljava/lang/Object;)Z HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;-><init>([Ljava/lang/Object;I)V HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->hasNext()Z -HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->next()Ljava/lang/Object; -HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V -HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V +HSPLjava/util/concurrent/CopyOnWriteArrayList$COWIterator;->next()Ljava/lang/Object;+]Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator;Ljava/util/concurrent/CopyOnWriteArrayList$COWIterator; +HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>()V+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList; +HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArrayList;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList; HSPLjava/util/concurrent/CopyOnWriteArrayList;-><init>([Ljava/lang/Object;)V HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(ILjava/lang/Object;)V HSPLjava/util/concurrent/CopyOnWriteArrayList;->add(Ljava/lang/Object;)Z @@ -6740,19 +6838,19 @@ HSPLjava/util/concurrent/CopyOnWriteArrayList;->contains(Ljava/lang/Object;)Z HSPLjava/util/concurrent/CopyOnWriteArrayList;->elementAt([Ljava/lang/Object;I)Ljava/lang/Object; HSPLjava/util/concurrent/CopyOnWriteArrayList;->get(I)Ljava/lang/Object; HSPLjava/util/concurrent/CopyOnWriteArrayList;->getArray()[Ljava/lang/Object; -HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I+]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList; -HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I+]Ljava/lang/Object;Ljava/lang/Integer;,Landroid/media/ImageReader$SurfaceImage; -HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z -HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator; +HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOf(Ljava/lang/Object;)I +HSPLjava/util/concurrent/CopyOnWriteArrayList;->indexOfRange(Ljava/lang/Object;[Ljava/lang/Object;II)I +HSPLjava/util/concurrent/CopyOnWriteArrayList;->isEmpty()Z+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types +HSPLjava/util/concurrent/CopyOnWriteArrayList;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types HSPLjava/util/concurrent/CopyOnWriteArrayList;->lambda$removeAll$0(Ljava/util/Collection;Ljava/lang/Object;)Z HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(I)Ljava/lang/Object; HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;)Z HSPLjava/util/concurrent/CopyOnWriteArrayList;->remove(Ljava/lang/Object;[Ljava/lang/Object;I)Z HSPLjava/util/concurrent/CopyOnWriteArrayList;->removeAll(Ljava/util/Collection;)Z HSPLjava/util/concurrent/CopyOnWriteArrayList;->setArray([Ljava/lang/Object;)V -HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I +HSPLjava/util/concurrent/CopyOnWriteArrayList;->size()I+]Ljava/util/concurrent/CopyOnWriteArrayList;missing_types HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray()[Ljava/lang/Object; -HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object; +HSPLjava/util/concurrent/CopyOnWriteArrayList;->toArray([Ljava/lang/Object;)[Ljava/lang/Object;+]Ljava/lang/Object;[Ljava/util/logging/Handler;]Ljava/util/concurrent/CopyOnWriteArrayList;Ljava/util/concurrent/CopyOnWriteArrayList; HSPLjava/util/concurrent/CopyOnWriteArrayList;->toString()Ljava/lang/String; HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>()V HSPLjava/util/concurrent/CopyOnWriteArraySet;-><init>(Ljava/util/Collection;)V+]Ljava/lang/Object;Ljava/util/concurrent/CopyOnWriteArraySet; @@ -6781,7 +6879,7 @@ HSPLjava/util/concurrent/Executors$DefaultThreadFactory;-><init>()V HSPLjava/util/concurrent/Executors$DefaultThreadFactory;->newThread(Ljava/lang/Runnable;)Ljava/lang/Thread; HSPLjava/util/concurrent/Executors$DelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->awaitTermination(JLjava/util/concurrent/TimeUnit;)Z -HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor; +HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->execute(Ljava/lang/Runnable;)V HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->isShutdown()Z HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->shutdown()V HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->shutdownNow()Ljava/util/List; @@ -6789,13 +6887,13 @@ HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->submit(Ljava/lang/ HSPLjava/util/concurrent/Executors$DelegatedExecutorService;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;-><init>(Ljava/util/concurrent/ScheduledExecutorService;)V HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; -HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledExecutorService;Ljava/util/concurrent/ScheduledThreadPoolExecutor; +HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; HSPLjava/util/concurrent/Executors$DelegatedScheduledExecutorService;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;-><init>(Ljava/util/concurrent/ExecutorService;)V HSPLjava/util/concurrent/Executors$FinalizableDelegatedExecutorService;->finalize()V HSPLjava/util/concurrent/Executors$RunnableAdapter;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V -HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object;+]Ljava/lang/Runnable;missing_types +HSPLjava/util/concurrent/Executors$RunnableAdapter;->call()Ljava/lang/Object; HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;)Ljava/util/concurrent/Callable; HSPLjava/util/concurrent/Executors;->callable(Ljava/lang/Runnable;Ljava/lang/Object;)Ljava/util/concurrent/Callable; HSPLjava/util/concurrent/Executors;->defaultThreadFactory()Ljava/util/concurrent/ThreadFactory; @@ -6812,6 +6910,7 @@ HSPLjava/util/concurrent/Executors;->newSingleThreadScheduledExecutor(Ljava/util HSPLjava/util/concurrent/Executors;->unconfigurableExecutorService(Ljava/util/concurrent/ExecutorService;)Ljava/util/concurrent/ExecutorService; HSPLjava/util/concurrent/Executors;->unconfigurableScheduledExecutorService(Ljava/util/concurrent/ScheduledExecutorService;)Ljava/util/concurrent/ScheduledExecutorService; HSPLjava/util/concurrent/ForkJoinPool;->managedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V +HSPLjava/util/concurrent/ForkJoinPool;->unmanagedBlock(Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;)V+]Ljava/util/concurrent/ForkJoinPool$ManagedBlocker;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode; HSPLjava/util/concurrent/ForkJoinTask;-><init>()V HSPLjava/util/concurrent/FutureTask$WaitNode;-><init>()V HSPLjava/util/concurrent/FutureTask;-><init>(Ljava/lang/Runnable;Ljava/lang/Object;)V @@ -6819,7 +6918,7 @@ HSPLjava/util/concurrent/FutureTask;-><init>(Ljava/util/concurrent/Callable;)V HSPLjava/util/concurrent/FutureTask;->awaitDone(ZJ)I HSPLjava/util/concurrent/FutureTask;->cancel(Z)Z HSPLjava/util/concurrent/FutureTask;->done()V -HSPLjava/util/concurrent/FutureTask;->finishCompletion()V+]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask; +HSPLjava/util/concurrent/FutureTask;->finishCompletion()V+]Ljava/util/concurrent/FutureTask;missing_types HSPLjava/util/concurrent/FutureTask;->get()Ljava/lang/Object; HSPLjava/util/concurrent/FutureTask;->get(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object; HSPLjava/util/concurrent/FutureTask;->handlePossibleCancellationInterrupt(I)V @@ -6827,7 +6926,7 @@ HSPLjava/util/concurrent/FutureTask;->isCancelled()Z HSPLjava/util/concurrent/FutureTask;->isDone()Z HSPLjava/util/concurrent/FutureTask;->removeWaiter(Ljava/util/concurrent/FutureTask$WaitNode;)V HSPLjava/util/concurrent/FutureTask;->report(I)Ljava/lang/Object; -HSPLjava/util/concurrent/FutureTask;->run()V+]Ljava/util/concurrent/Callable;Ljava/util/concurrent/Executors$RunnableAdapter;]Ljava/util/concurrent/FutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;,Landroid/os/AsyncTask$4; +HSPLjava/util/concurrent/FutureTask;->run()V+]Ljava/util/concurrent/Callable;missing_types]Ljava/util/concurrent/FutureTask;missing_types HSPLjava/util/concurrent/FutureTask;->runAndReset()Z HSPLjava/util/concurrent/FutureTask;->set(Ljava/lang/Object;)V HSPLjava/util/concurrent/FutureTask;->setException(Ljava/lang/Throwable;)V @@ -6863,12 +6962,12 @@ HSPLjava/util/concurrent/LinkedBlockingQueue;->enqueue(Ljava/util/concurrent/Lin HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyLock()V HSPLjava/util/concurrent/LinkedBlockingQueue;->fullyUnlock()V HSPLjava/util/concurrent/LinkedBlockingQueue;->offer(Ljava/lang/Object;)Z -HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; +HSPLjava/util/concurrent/LinkedBlockingQueue;->poll()Ljava/lang/Object; HSPLjava/util/concurrent/LinkedBlockingQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object; HSPLjava/util/concurrent/LinkedBlockingQueue;->put(Ljava/lang/Object;)V -HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotEmpty()V +HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotEmpty()V+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject; HSPLjava/util/concurrent/LinkedBlockingQueue;->signalNotFull()V -HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I +HSPLjava/util/concurrent/LinkedBlockingQueue;->size()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; HSPLjava/util/concurrent/LinkedBlockingQueue;->take()Ljava/lang/Object; HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>()V HSPLjava/util/concurrent/PriorityBlockingQueue;-><init>(ILjava/util/Comparator;)V @@ -6894,49 +6993,49 @@ HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->next HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->next()Ljava/lang/Runnable; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr;->remove()V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;-><init>()V -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue; -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Object;)Z +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->add(Ljava/lang/Runnable;)Z HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;)I HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->drainTo(Ljava/util/Collection;I)I HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->finishPoll(Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->grow()V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->indexOf(Ljava/lang/Object;)I HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->isEmpty()Z -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->iterator()Ljava/util/Iterator;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->offer(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/RunnableScheduledFuture; -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->remove(Ljava/lang/Object;)Z+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->setIndex(Ljava/util/concurrent/RunnableScheduledFuture;I)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftDown(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->siftUp(ILjava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->size()I -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue; -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture;+]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/lang/Object; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->take()Ljava/util/concurrent/RunnableScheduledFuture; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;->toArray()[Ljava/lang/Object; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJ)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/lang/Runnable;Ljava/lang/Object;JJJ)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;-><init>(Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/Callable;JJ)V -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->cancel(Z)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/lang/Object;)I+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->compareTo(Ljava/util/concurrent/Delayed;)I -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->getDelay(Ljava/util/concurrent/TimeUnit;)J HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->isPeriodic()Z -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->run()V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;->setNextRunTime()V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(I)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;-><init>(ILjava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/RunnableScheduledFuture;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->canRunInCurrentRunState(Ljava/util/concurrent/RunnableScheduledFuture;)Z HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/lang/Runnable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->decorateTask(Ljava/util/concurrent/Callable;Ljava/util/concurrent/RunnableScheduledFuture;)Ljava/util/concurrent/RunnableScheduledFuture; -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor; -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->delayedExecute(Ljava/util/concurrent/RunnableScheduledFuture;)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->execute(Ljava/lang/Runnable;)V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getContinueExistingPeriodicTasksAfterShutdownPolicy()Z HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->getExecuteExistingDelayedTasksAfterShutdownPolicy()Z HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->onShutdown()V HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->reExecutePeriodic(Ljava/util/concurrent/RunnableScheduledFuture;)V -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture;+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->schedule(Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleAtFixedRate(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->scheduleWithFixedDelay(Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; @@ -6946,7 +7045,7 @@ HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->shutdownNow()Ljava/util/L HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/lang/Runnable;)Ljava/util/concurrent/Future; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->submit(Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(J)J -HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit; +HSPLjava/util/concurrent/ScheduledThreadPoolExecutor;->triggerTime(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/ScheduledThreadPoolExecutor;missing_types]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit; HSPLjava/util/concurrent/Semaphore$FairSync;-><init>(I)V HSPLjava/util/concurrent/Semaphore$FairSync;->tryAcquireShared(I)I HSPLjava/util/concurrent/Semaphore$NonfairSync;-><init>(I)V @@ -6966,24 +7065,24 @@ HSPLjava/util/concurrent/Semaphore;->tryAcquire()Z HSPLjava/util/concurrent/Semaphore;->tryAcquire(IJLjava/util/concurrent/TimeUnit;)Z HSPLjava/util/concurrent/Semaphore;->tryAcquire(JLjava/util/concurrent/TimeUnit;)Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;-><init>(Ljava/lang/Object;)V +HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->block()Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->casNext(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z +HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->forgetWaiter()V HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isCancelled()Z -HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryCancel()V +HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->isReleasable()Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack$SNode;->tryMatch(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack;-><init>()V -HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->awaitFulfill(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;ZJ)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/lang/Thread;Ljava/lang/Thread;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack; HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->casHead(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->clean(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)V HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->isFulfilling(I)Z -HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->shouldSpin(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;)Z HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->snode(Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/lang/Object;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;I)Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode; -HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;Ljava/util/concurrent/SynchronousQueue$TransferStack$SNode;]Ljava/util/concurrent/SynchronousQueue$TransferStack;Ljava/util/concurrent/SynchronousQueue$TransferStack; +HSPLjava/util/concurrent/SynchronousQueue$TransferStack;->transfer(Ljava/lang/Object;ZJ)Ljava/lang/Object; HSPLjava/util/concurrent/SynchronousQueue$Transferer;-><init>()V HSPLjava/util/concurrent/SynchronousQueue;-><init>()V HSPLjava/util/concurrent/SynchronousQueue;-><init>(Z)V HSPLjava/util/concurrent/SynchronousQueue;->isEmpty()Z -HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z -HSPLjava/util/concurrent/SynchronousQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object; +HSPLjava/util/concurrent/SynchronousQueue;->offer(Ljava/lang/Object;)Z+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack; +HSPLjava/util/concurrent/SynchronousQueue;->poll(JLjava/util/concurrent/TimeUnit;)Ljava/lang/Object;+]Ljava/util/concurrent/SynchronousQueue$Transferer;Ljava/util/concurrent/SynchronousQueue$TransferStack;]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit; HSPLjava/util/concurrent/SynchronousQueue;->size()I HSPLjava/util/concurrent/SynchronousQueue;->take()Ljava/lang/Object; HSPLjava/util/concurrent/ThreadLocalRandom;-><clinit>()V @@ -7025,13 +7124,13 @@ HSPLjava/util/concurrent/ThreadPoolExecutor;->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;->execute(Ljava/lang/Runnable;)V HSPLjava/util/concurrent/ThreadPoolExecutor;->finalize()V HSPLjava/util/concurrent/ThreadPoolExecutor;->getCorePoolSize()I HSPLjava/util/concurrent/ThreadPoolExecutor;->getMaximumPoolSize()I HSPLjava/util/concurrent/ThreadPoolExecutor;->getQueue()Ljava/util/concurrent/BlockingQueue; HSPLjava/util/concurrent/ThreadPoolExecutor;->getRejectedExecutionHandler()Ljava/util/concurrent/RejectedExecutionHandler; -HSPLjava/util/concurrent/ThreadPoolExecutor;->getTask()Ljava/lang/Runnable;+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; +HSPLjava/util/concurrent/ThreadPoolExecutor;->getTask()Ljava/lang/Runnable; HSPLjava/util/concurrent/ThreadPoolExecutor;->getThreadFactory()Ljava/util/concurrent/ThreadFactory; HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptIdleWorkers()V HSPLjava/util/concurrent/ThreadPoolExecutor;->interruptIdleWorkers(Z)V @@ -7042,13 +7141,13 @@ HSPLjava/util/concurrent/ThreadPoolExecutor;->isTerminated()Z HSPLjava/util/concurrent/ThreadPoolExecutor;->onShutdown()V HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartAllCoreThreads()I HSPLjava/util/concurrent/ThreadPoolExecutor;->prestartCoreThread()Z -HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V -HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V -HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z +HSPLjava/util/concurrent/ThreadPoolExecutor;->processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;,Ljava/util/concurrent/SynchronousQueue;,Ljava/util/concurrent/LinkedBlockingQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/HashSet;Ljava/util/HashSet;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; +HSPLjava/util/concurrent/ThreadPoolExecutor;->purge()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/Iterator;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue$Itr; +HSPLjava/util/concurrent/ThreadPoolExecutor;->remove(Ljava/lang/Runnable;)Z+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;missing_types HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateAtLeast(II)Z HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateLessThan(II)Z HSPLjava/util/concurrent/ThreadPoolExecutor;->runStateOf(I)I -HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;]Ljava/lang/Runnable;missing_types]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker; +HSPLjava/util/concurrent/ThreadPoolExecutor;->runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger;]Ljava/lang/Runnable;Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;]Ljava/util/concurrent/ThreadPoolExecutor$Worker;Ljava/util/concurrent/ThreadPoolExecutor$Worker; HSPLjava/util/concurrent/ThreadPoolExecutor;->setCorePoolSize(I)V HSPLjava/util/concurrent/ThreadPoolExecutor;->setKeepAliveTime(JLjava/util/concurrent/TimeUnit;)V HSPLjava/util/concurrent/ThreadPoolExecutor;->setMaximumPoolSize(I)V @@ -7058,9 +7157,9 @@ HSPLjava/util/concurrent/ThreadPoolExecutor;->shutdown()V HSPLjava/util/concurrent/ThreadPoolExecutor;->shutdownNow()Ljava/util/List; HSPLjava/util/concurrent/ThreadPoolExecutor;->terminated()V HSPLjava/util/concurrent/ThreadPoolExecutor;->toString()Ljava/lang/String; -HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V +HSPLjava/util/concurrent/ThreadPoolExecutor;->tryTerminate()V+]Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/LinkedBlockingQueue;,Ljava/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue;]Ljava/util/concurrent/ThreadPoolExecutor;Ljava/util/concurrent/ThreadPoolExecutor;,Ljava/util/concurrent/ScheduledThreadPoolExecutor;]Ljava/util/concurrent/locks/ReentrantLock;Ljava/util/concurrent/locks/ReentrantLock;]Ljava/util/concurrent/locks/Condition;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; HSPLjava/util/concurrent/ThreadPoolExecutor;->workerCountOf(I)I -HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J+]Ljava/util/concurrent/TimeUnit;Ljava/util/concurrent/TimeUnit; +HSPLjava/util/concurrent/TimeUnit;->convert(JLjava/util/concurrent/TimeUnit;)J HSPLjava/util/concurrent/TimeUnit;->cvt(JJJ)J HSPLjava/util/concurrent/TimeUnit;->sleep(J)V HSPLjava/util/concurrent/TimeUnit;->toDays(J)J @@ -7092,14 +7191,15 @@ HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndDecrement()I HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndIncrement()I HSPLjava/util/concurrent/atomic/AtomicInteger;->getAndSet(I)I HSPLjava/util/concurrent/atomic/AtomicInteger;->incrementAndGet()I -HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I+]Ljava/util/concurrent/atomic/AtomicInteger;Ljava/util/concurrent/atomic/AtomicInteger; +HSPLjava/util/concurrent/atomic/AtomicInteger;->intValue()I HSPLjava/util/concurrent/atomic/AtomicInteger;->lazySet(I)V HSPLjava/util/concurrent/atomic/AtomicInteger;->set(I)V +HSPLjava/util/concurrent/atomic/AtomicInteger;->weakCompareAndSetVolatile(II)Z HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V -HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class; +HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;II)Z HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->decrementAndGet(Ljava/lang/Object;)I -HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I +HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->getAndAdd(Ljava/lang/Object;I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->incrementAndGet(Ljava/lang/Object;)I HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl;->set(Ljava/lang/Object;I)V HSPLjava/util/concurrent/atomic/AtomicIntegerFieldUpdater;-><init>()V @@ -7118,7 +7218,7 @@ HSPLjava/util/concurrent/atomic/AtomicLong;->lazySet(J)V HSPLjava/util/concurrent/atomic/AtomicLong;->set(J)V HSPLjava/util/concurrent/atomic/AtomicLong;->toString()Ljava/lang/String; HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;-><init>(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V -HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V +HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->addAndGet(Ljava/lang/Object;J)J HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->compareAndSet(Ljava/lang/Object;JJ)Z HSPLjava/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater;->getAndAdd(Ljava/lang/Object;J)J+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; @@ -7138,21 +7238,23 @@ HSPLjava/util/concurrent/atomic/AtomicReference;->weakCompareAndSetVolatile(Ljav HSPLjava/util/concurrent/atomic/AtomicReferenceArray;-><init>(I)V HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->compareAndSet(ILjava/lang/Object;Ljava/lang/Object;)Z HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->get(I)Ljava/lang/Object; +HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAcquire(I)Ljava/lang/Object; HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->getAndSet(ILjava/lang/Object;)Ljava/lang/Object; HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->lazySet(ILjava/lang/Object;)V HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->length()I HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->set(ILjava/lang/Object;)V +HSPLjava/util/concurrent/atomic/AtomicReferenceArray;->setRelease(ILjava/lang/Object;)V HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;-><init>(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)V HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->accessCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class; -HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z +HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->get(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->getAndSet(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V +HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->lazySet(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl;->valueCheck(Ljava/lang/Object;)V+]Ljava/lang/Class;Ljava/lang/Class; HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;-><init>()V HSPLjava/util/concurrent/atomic/AtomicReferenceFieldUpdater;->newUpdater(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater; HSPLjava/util/concurrent/atomic/LongAdder;-><init>()V -HSPLjava/util/concurrent/atomic/LongAdder;->add(J)V+]Ljava/util/concurrent/atomic/Striped64$Cell;Ljava/util/concurrent/atomic/Striped64$Cell;]Ljava/util/concurrent/atomic/LongAdder;Ljava/util/concurrent/atomic/LongAdder; +HSPLjava/util/concurrent/atomic/LongAdder;->add(J)V HSPLjava/util/concurrent/atomic/Striped64$Cell;-><clinit>()V HSPLjava/util/concurrent/atomic/Striped64$Cell;-><init>(J)V HSPLjava/util/concurrent/atomic/Striped64$Cell;->cas(JJ)Z @@ -7160,86 +7262,80 @@ HSPLjava/util/concurrent/atomic/Striped64;-><init>()V HSPLjava/util/concurrent/atomic/Striped64;->casBase(JJ)Z HSPLjava/util/concurrent/atomic/Striped64;->casCellsBusy()Z HSPLjava/util/concurrent/atomic/Striped64;->getProbe()I -HSPLjava/util/concurrent/atomic/Striped64;->longAccumulate(JLjava/util/function/LongBinaryOperator;Z)V HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;-><init>()V HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->getExclusiveOwnerThread()Ljava/lang/Thread; HSPLjava/util/concurrent/locks/AbstractOwnableSynchronizer;->setExclusiveOwnerThread(Ljava/lang/Thread;)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;-><init>()V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->block()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;->isReleasable()Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->addConditionWaiter()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->await()V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->awaitNanos(J)J -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->checkInterruptWhileWaiting(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignalAll(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->canReacquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->doSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Z)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->enableWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)I +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->hasWaiters()Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->isOwnedBy(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->reportInterruptAfterWait(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signal()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signal()V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->signalAll()V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters()V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;->unlinkCancelledWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;-><init>()V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>()V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;-><init>(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->compareAndSetWaitStatus(II)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->isShared()Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->predecessor()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->clearStatus()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->getAndUnsetStatus(I)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setPrevRelaxed(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;->setStatusRelaxed(I)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode;-><init>()V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->-$$Nest$sfgetU()Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;-><init>()V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireQueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;IZZZJ)I +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireInterruptibly(I)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireShared(I)V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->acquireSharedInterruptibly(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->addWaiter(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cancelAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->apparentlyFirstQueuedIsExclusive()Z +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->casTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->cleanQueue()V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetState(II)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->compareAndSetTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireInterruptibly(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireShared(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedInterruptibly(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doAcquireSharedNanos(IJ)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->doReleaseShared()V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enq(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->findNodeFromTail(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->fullyRelease(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)I +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->enqueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getFirstQueuedThread()Ljava/lang/Thread; HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->getState()I HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedPredecessors()Z +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasQueuedThreads()Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->hasWaiters(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->initializeSyncQueue()V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isOnSyncQueue(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->isEnqueued(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->owns(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->parkAndCheckInterrupt()Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->release(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;,Ljava/util/concurrent/ThreadPoolExecutor$Worker; HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->releaseShared(I)Z+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync;,Ljava/util/concurrent/CountDownLatch$Sync; -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->selfInterrupt()V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHead(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setHeadAndPropagate(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;I)V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->setState(I)V -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->shouldParkAfterFailedAcquire(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferAfterCancelledWait(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->transferForSignal(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)Z +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNext(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V+]Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode;,Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode; +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->signalNextIfShared(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireNanos(IJ)Z HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryAcquireSharedNanos(IJ)Z -HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->unparkSuccessor(Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node;)V +HSPLjava/util/concurrent/locks/AbstractQueuedSynchronizer;->tryInitializeHead()V +HSPLjava/util/concurrent/locks/LockSupport;->park()V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/locks/LockSupport;->park(Ljava/lang/Object;)V HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(J)V HSPLjava/util/concurrent/locks/LockSupport;->parkNanos(Ljava/lang/Object;J)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; -HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V -HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V +HSPLjava/util/concurrent/locks/LockSupport;->setBlocker(Ljava/lang/Thread;Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/LockSupport;->setCurrentBlocker(Ljava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjava/util/concurrent/locks/LockSupport;->unpark(Ljava/lang/Thread;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;-><init>()V +HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$FairSync;Ljava/util/concurrent/locks/ReentrantLock$FairSync; HSPLjava/util/concurrent/locks/ReentrantLock$FairSync;->tryAcquire(I)Z HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;-><init>()V +HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->initialTryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock$NonfairSync;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$NonfairSync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock$Sync;-><init>()V -HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->isHeldExclusively()Z +HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->lockInterruptibly()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->newCondition()Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject; -HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->nonfairTryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryLock()Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock$Sync;->tryRelease(I)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock;-><init>()V HSPLjava/util/concurrent/locks/ReentrantLock;-><init>(Z)V -HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; +HSPLjava/util/concurrent/locks/ReentrantLock;->hasWaiters(Ljava/util/concurrent/locks/Condition;)Z HSPLjava/util/concurrent/locks/ReentrantLock;->isHeldByCurrentThread()Z HSPLjava/util/concurrent/locks/ReentrantLock;->lock()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantLock;->lockInterruptibly()V+]Ljava/util/concurrent/locks/ReentrantLock$Sync;Ljava/util/concurrent/locks/ReentrantLock$NonfairSync; @@ -7251,11 +7347,11 @@ HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$FairSync;-><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,12 +7359,11 @@ HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounte HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;-><init>()V HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->exclusiveCount(I)I HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->fullTryAcquireShared(Ljava/lang/Thread;)I -HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; -HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; -HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getWriteHoldCount()I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; -HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; +HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadHoldCount()I +HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->getReadLockCount()I +HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->isHeldExclusively()Z HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->sharedCount(I)I -HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z +HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquire(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryAcquireShared(I)I+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryRelease(I)Z HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$Sync;->tryReleaseShared(I)Z+]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter;]Ljava/util/concurrent/locks/ReentrantReadWriteLock$Sync;Ljava/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync; @@ -7277,8 +7372,7 @@ HSPLjava/util/concurrent/locks/ReentrantReadWriteLock$WriteLock;->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;->getReadHoldCount()I HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/Lock;+]Ljava/util/concurrent/locks/ReentrantReadWriteLock;Ljava/util/concurrent/locks/ReentrantReadWriteLock; HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->readLock()Ljava/util/concurrent/locks/ReentrantReadWriteLock$ReadLock; HSPLjava/util/concurrent/locks/ReentrantReadWriteLock;->writeLock()Ljava/util/concurrent/locks/Lock; @@ -7288,7 +7382,6 @@ HSPLjava/util/function/BinaryOperator;->maxBy(Ljava/util/Comparator;)Ljava/util/ HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;-><init>(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;)V HSPLjava/util/function/DoubleUnaryOperator$$ExternalSyntheticLambda1;->applyAsDouble(D)D HSPLjava/util/function/DoubleUnaryOperator;->andThen(Ljava/util/function/DoubleUnaryOperator;)Ljava/util/function/DoubleUnaryOperator; -HSPLjava/util/function/DoubleUnaryOperator;->lambda$andThen$1(Ljava/util/function/DoubleUnaryOperator;Ljava/util/function/DoubleUnaryOperator;D)D+]Ljava/util/function/DoubleUnaryOperator;Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda3;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda1;,Landroid/graphics/ColorSpace$Rgb$$ExternalSyntheticLambda0; HSPLjava/util/function/Function$$ExternalSyntheticLambda1;-><init>()V HSPLjava/util/function/Function$$ExternalSyntheticLambda1;->apply(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/function/Function$$ExternalSyntheticLambda2;->apply(Ljava/lang/Object;)Ljava/lang/Object; @@ -7296,21 +7389,19 @@ HSPLjava/util/function/Function;->identity()Ljava/util/function/Function; HSPLjava/util/function/Function;->lambda$identity$2(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/jar/Attributes$Name;-><init>(Ljava/lang/String;)V HSPLjava/util/jar/Attributes$Name;->equals(Ljava/lang/Object;)Z +HSPLjava/util/jar/Attributes$Name;->hash(Ljava/lang/String;)I HSPLjava/util/jar/Attributes$Name;->hashCode()I -HSPLjava/util/jar/Attributes$Name;->isAlpha(C)Z -HSPLjava/util/jar/Attributes$Name;->isDigit(C)Z -HSPLjava/util/jar/Attributes$Name;->isValid(C)Z -HSPLjava/util/jar/Attributes$Name;->isValid(Ljava/lang/String;)Z HSPLjava/util/jar/Attributes$Name;->toString()Ljava/lang/String; HSPLjava/util/jar/Attributes;-><init>()V HSPLjava/util/jar/Attributes;-><init>(I)V HSPLjava/util/jar/Attributes;->entrySet()Ljava/util/Set; HSPLjava/util/jar/Attributes;->get(Ljava/lang/Object;)Ljava/lang/Object; HSPLjava/util/jar/Attributes;->getValue(Ljava/util/jar/Attributes$Name;)Ljava/lang/String; -HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; -HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +HSPLjava/util/jar/Attributes;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/Map;Ljava/util/LinkedHashMap; +HSPLjava/util/jar/Attributes;->putValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes; HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[B)V -HSPLjava/util/jar/Attributes;->size()I +HSPLjava/util/jar/Attributes;->read(Ljava/util/jar/Manifest$FastInputStream;[BLjava/lang/String;I)I+]Ljava/util/jar/Attributes;Ljava/util/jar/Attributes;]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream; +HSPLjava/util/jar/Attributes;->size()I+]Ljava/util/Map;Ljava/util/LinkedHashMap; HSPLjava/util/jar/JarEntry;-><init>(Ljava/util/zip/ZipEntry;)V HSPLjava/util/jar/JarFile$JarFileEntry;-><init>(Ljava/util/jar/JarFile;Ljava/util/zip/ZipEntry;)V HSPLjava/util/jar/JarFile;-><init>(Ljava/io/File;ZI)V @@ -7330,7 +7421,6 @@ HSPLjava/util/jar/JarVerifier$VerifierStream;->available()I HSPLjava/util/jar/JarVerifier$VerifierStream;->close()V HSPLjava/util/jar/JarVerifier$VerifierStream;->read()I HSPLjava/util/jar/JarVerifier$VerifierStream;->read([BII)I -HSPLjava/util/jar/JarVerifier;-><init>([B)V HSPLjava/util/jar/JarVerifier;->beginEntry(Ljava/util/jar/JarEntry;Lsun/security/util/ManifestEntryVerifier;)V HSPLjava/util/jar/JarVerifier;->doneWithMeta()V HSPLjava/util/jar/JarVerifier;->mapSignersToCertArray([Ljava/security/CodeSigner;)[Ljava/security/cert/Certificate; @@ -7342,7 +7432,7 @@ HSPLjava/util/jar/Manifest$FastInputStream;-><init>(Ljava/io/InputStream;)V HSPLjava/util/jar/Manifest$FastInputStream;-><init>(Ljava/io/InputStream;I)V HSPLjava/util/jar/Manifest$FastInputStream;->fill()V HSPLjava/util/jar/Manifest$FastInputStream;->peek()B -HSPLjava/util/jar/Manifest$FastInputStream;->readLine([B)I +HSPLjava/util/jar/Manifest$FastInputStream;->readLine([B)I+]Ljava/util/jar/Manifest$FastInputStream;Ljava/util/jar/Manifest$FastInputStream; HSPLjava/util/jar/Manifest$FastInputStream;->readLine([BII)I HSPLjava/util/jar/Manifest;-><init>()V HSPLjava/util/jar/Manifest;-><init>(Ljava/io/InputStream;)V @@ -7361,7 +7451,7 @@ HSPLjava/util/logging/FileHandler$InitializationErrorManager;-><init>(Ljava/util HSPLjava/util/logging/FileHandler$MeteredStream;-><init>(Ljava/util/logging/FileHandler;Ljava/io/OutputStream;I)V HSPLjava/util/logging/FileHandler$MeteredStream;->close()V HSPLjava/util/logging/FileHandler$MeteredStream;->flush()V -HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V +HSPLjava/util/logging/FileHandler$MeteredStream;->write([BII)V+]Ljava/io/OutputStream;Ljava/io/BufferedOutputStream; HSPLjava/util/logging/FileHandler;->-$$Nest$mrotate(Ljava/util/logging/FileHandler;)V HSPLjava/util/logging/FileHandler;-><clinit>()V HSPLjava/util/logging/FileHandler;-><init>(Ljava/lang/String;IIZ)V @@ -7370,7 +7460,7 @@ HSPLjava/util/logging/FileHandler;->generate(Ljava/lang/String;II)Ljava/io/File; HSPLjava/util/logging/FileHandler;->isParentWritable(Ljava/nio/file/Path;)Z HSPLjava/util/logging/FileHandler;->open(Ljava/io/File;Z)V HSPLjava/util/logging/FileHandler;->openFiles()V -HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V +HSPLjava/util/logging/FileHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/FileHandler;Ljava/util/logging/FileHandler; HSPLjava/util/logging/FileHandler;->rotate()V HSPLjava/util/logging/Formatter;-><init>()V HSPLjava/util/logging/Formatter;->getHead(Ljava/util/logging/Handler;)Ljava/lang/String; @@ -7381,7 +7471,7 @@ HSPLjava/util/logging/Handler;->getEncoding()Ljava/lang/String; HSPLjava/util/logging/Handler;->getFilter()Ljava/util/logging/Filter; HSPLjava/util/logging/Handler;->getFormatter()Ljava/util/logging/Formatter; HSPLjava/util/logging/Handler;->getLevel()Ljava/util/logging/Level; -HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z +HSPLjava/util/logging/Handler;->isLoggable(Ljava/util/logging/LogRecord;)Z+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/Level;Ljava/util/logging/Level;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord; HSPLjava/util/logging/Handler;->setEncoding(Ljava/lang/String;)V HSPLjava/util/logging/Handler;->setErrorManager(Ljava/util/logging/ErrorManager;)V HSPLjava/util/logging/Handler;->setFilter(Ljava/util/logging/Filter;)V @@ -7448,7 +7538,7 @@ HSPLjava/util/logging/LogManager;->loadLoggerHandlers(Ljava/util/logging/Logger; HSPLjava/util/logging/LogManager;->parseClassNames(Ljava/lang/String;)[Ljava/lang/String; HSPLjava/util/logging/LogManager;->reset()V HSPLjava/util/logging/LogManager;->resetLogger(Ljava/util/logging/Logger;)V -HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V +HSPLjava/util/logging/LogRecord;-><init>(Ljava/util/logging/Level;Ljava/lang/String;)V+]Ljava/lang/Object;Ljava/util/logging/Level;]Ljava/util/concurrent/atomic/AtomicLong;Ljava/util/concurrent/atomic/AtomicLong; HSPLjava/util/logging/LogRecord;->defaultThreadID()I HSPLjava/util/logging/LogRecord;->getLevel()Ljava/util/logging/Level; HSPLjava/util/logging/LogRecord;->getLoggerName()Ljava/lang/String; @@ -7469,12 +7559,12 @@ HSPLjava/util/logging/Logger;->accessCheckedHandlers()[Ljava/util/logging/Handle HSPLjava/util/logging/Logger;->addHandler(Ljava/util/logging/Handler;)V HSPLjava/util/logging/Logger;->checkPermission()V HSPLjava/util/logging/Logger;->demandLogger(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)Ljava/util/logging/Logger; -HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V +HSPLjava/util/logging/Logger;->doLog(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger; HSPLjava/util/logging/Logger;->doSetParent(Ljava/util/logging/Logger;)V HSPLjava/util/logging/Logger;->findResourceBundle(Ljava/lang/String;Z)Ljava/util/ResourceBundle; HSPLjava/util/logging/Logger;->findSystemResourceBundle(Ljava/util/Locale;)Ljava/util/ResourceBundle; HSPLjava/util/logging/Logger;->getCallersClassLoader()Ljava/lang/ClassLoader; -HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle; +HSPLjava/util/logging/Logger;->getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle;+]Ljava/util/logging/Logger$LoggerBundle;Ljava/util/logging/Logger$LoggerBundle;]Ljava/util/logging/Logger;Ljava/util/logging/LogManager$RootLogger;,Ljava/util/logging/Logger; HSPLjava/util/logging/Logger;->getHandlers()[Ljava/util/logging/Handler; HSPLjava/util/logging/Logger;->getLogger(Ljava/lang/String;)Ljava/util/logging/Logger; HSPLjava/util/logging/Logger;->getName()Ljava/lang/String; @@ -7486,8 +7576,8 @@ HSPLjava/util/logging/Logger;->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;->log(Ljava/util/logging/Level;Ljava/lang/String;)V -HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V -HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V +HSPLjava/util/logging/Logger;->log(Ljava/util/logging/LogRecord;)V+]Ljava/util/logging/Handler;Ljava/util/logging/FileHandler;]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger; +HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V+]Ljava/util/logging/LogRecord;Ljava/util/logging/LogRecord;]Ljava/util/logging/Logger;Ljava/util/logging/Logger; HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V HSPLjava/util/logging/Logger;->logp(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V HSPLjava/util/logging/Logger;->removeChildLogger(Ljava/util/logging/LogManager$LoggerWeakRef;)V @@ -7506,36 +7596,39 @@ HSPLjava/util/logging/SimpleFormatter;-><init>()V HSPLjava/util/logging/StreamHandler;-><init>()V HSPLjava/util/logging/StreamHandler;->close()V HSPLjava/util/logging/StreamHandler;->configure()V -HSPLjava/util/logging/StreamHandler;->flush()V +HSPLjava/util/logging/StreamHandler;->flush()V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter; HSPLjava/util/logging/StreamHandler;->flushAndClose()V HSPLjava/util/logging/StreamHandler;->isLoggable(Ljava/util/logging/LogRecord;)Z -HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V +HSPLjava/util/logging/StreamHandler;->publish(Ljava/util/logging/LogRecord;)V+]Ljava/io/Writer;Ljava/io/OutputStreamWriter;]Ljava/util/logging/StreamHandler;Ljava/util/logging/FileHandler; HSPLjava/util/logging/StreamHandler;->setEncoding(Ljava/lang/String;)V HSPLjava/util/logging/StreamHandler;->setOutputStream(Ljava/io/OutputStream;)V HSPLjava/util/logging/XMLFormatter;-><init>()V HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; -HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuffer;Ljava/lang/String;)V +HSPLjava/util/regex/Matcher;->appendEvaluated(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuffer;Ljava/lang/String;)Ljava/util/regex/Matcher; +HSPLjava/util/regex/Matcher;->appendReplacement(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/util/regex/Matcher;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; +HSPLjava/util/regex/Matcher;->appendReplacementInternal(Ljava/lang/StringBuilder;Ljava/lang/String;)V+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer; -HSPLjava/util/regex/Matcher;->end()I+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; -HSPLjava/util/regex/Matcher;->end(I)I+]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; +HSPLjava/util/regex/Matcher;->appendTail(Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String; +HSPLjava/util/regex/Matcher;->end()I +HSPLjava/util/regex/Matcher;->end(I)I HSPLjava/util/regex/Matcher;->ensureMatch()V HSPLjava/util/regex/Matcher;->find()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative; -HSPLjava/util/regex/Matcher;->find(I)Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; -HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence;+]Ljava/lang/String;Ljava/lang/String; +HSPLjava/util/regex/Matcher;->find(I)Z +HSPLjava/util/regex/Matcher;->getSubSequence(II)Ljava/lang/CharSequence; HSPLjava/util/regex/Matcher;->getTextLength()I HSPLjava/util/regex/Matcher;->group()Ljava/lang/String; -HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String;+]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; +HSPLjava/util/regex/Matcher;->group(I)Ljava/lang/String; HSPLjava/util/regex/Matcher;->groupCount()I+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative; HSPLjava/util/regex/Matcher;->hitEnd()Z -HSPLjava/util/regex/Matcher;->lookingAt()Z -HSPLjava/util/regex/Matcher;->matches()Z +HSPLjava/util/regex/Matcher;->lookingAt()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative; +HSPLjava/util/regex/Matcher;->matches()Z+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative; HSPLjava/util/regex/Matcher;->pattern()Ljava/util/regex/Pattern; HSPLjava/util/regex/Matcher;->region(II)Ljava/util/regex/Matcher; -HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String; +HSPLjava/util/regex/Matcher;->replaceAll(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher; HSPLjava/util/regex/Matcher;->replaceFirst(Ljava/lang/String;)Ljava/lang/String; HSPLjava/util/regex/Matcher;->reset()Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String; -HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String; +HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String;,Ljava/lang/StringBuilder; HSPLjava/util/regex/Matcher;->reset(Ljava/lang/CharSequence;II)Ljava/util/regex/Matcher;+]Ljava/lang/CharSequence;Ljava/lang/String; HSPLjava/util/regex/Matcher;->resetForInput()V+]Lcom/android/icu/util/regex/MatcherNative;Lcom/android/icu/util/regex/MatcherNative; HSPLjava/util/regex/Matcher;->start()I @@ -7553,13 +7646,12 @@ HSPLjava/util/regex/Pattern;->matches(Ljava/lang/String;Ljava/lang/CharSequence; HSPLjava/util/regex/Pattern;->pattern()Ljava/lang/String; HSPLjava/util/regex/Pattern;->quote(Ljava/lang/String;)Ljava/lang/String; HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;)[Ljava/lang/String; -HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String; +HSPLjava/util/regex/Pattern;->split(Ljava/lang/CharSequence;I)[Ljava/lang/String;+]Ljava/util/List;Ljava/util/ArrayList$SubList;]Ljava/lang/CharSequence;Ljava/lang/String;]Ljava/util/regex/Pattern;Ljava/util/regex/Pattern;]Ljava/util/regex/Matcher;Ljava/util/regex/Matcher;]Ljava/util/ArrayList;Ljava/util/ArrayList; HSPLjava/util/regex/Pattern;->toString()Ljava/lang/String; HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/Spliterator;IZ)V -HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V +HSPLjava/util/stream/AbstractPipeline;-><init>(Ljava/util/stream/AbstractPipeline;I)V+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/SortedOps$OfRef;,Ljava/util/stream/ReferencePipeline$3;,Ljava/util/stream/ReferencePipeline$4; HSPLjava/util/stream/AbstractPipeline;->close()V -HSPLjava/util/stream/AbstractPipeline;->copyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V -HSPLjava/util/stream/AbstractPipeline;->copyIntoWithCancel(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V +HSPLjava/util/stream/AbstractPipeline;->copyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)V+]Ljava/util/Spliterator;Ljava/util/Spliterators$IntArraySpliterator;,Ljava/util/HashMap$KeySpliterator;]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4;]Ljava/util/stream/Sink;Ljava/util/stream/ReferencePipeline$4$1;,Ljava/util/stream/IntPipeline$4$1;]Ljava/util/stream/StreamOpFlag;Ljava/util/stream/StreamOpFlag; HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/Spliterator;ZLjava/util/function/IntFunction;)Ljava/util/stream/Node; HSPLjava/util/stream/AbstractPipeline;->evaluate(Ljava/util/stream/TerminalOp;)Ljava/lang/Object; HSPLjava/util/stream/AbstractPipeline;->evaluateToArrayNode(Ljava/util/function/IntFunction;)Ljava/util/stream/Node; @@ -7568,7 +7660,7 @@ HSPLjava/util/stream/AbstractPipeline;->getStreamAndOpFlags()I HSPLjava/util/stream/AbstractPipeline;->isParallel()Z HSPLjava/util/stream/AbstractPipeline;->onClose(Ljava/lang/Runnable;)Ljava/util/stream/BaseStream; HSPLjava/util/stream/AbstractPipeline;->sequential()Ljava/util/stream/BaseStream; -HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator; +HSPLjava/util/stream/AbstractPipeline;->sourceSpliterator(I)Ljava/util/Spliterator;+]Ljava/util/stream/AbstractPipeline;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$4; HSPLjava/util/stream/AbstractPipeline;->sourceStageSpliterator()Ljava/util/Spliterator; HSPLjava/util/stream/AbstractPipeline;->spliterator()Ljava/util/Spliterator; HSPLjava/util/stream/AbstractPipeline;->wrapAndCopyInto(Ljava/util/stream/Sink;Ljava/util/Spliterator;)Ljava/util/stream/Sink; @@ -7576,6 +7668,7 @@ HSPLjava/util/stream/AbstractPipeline;->wrapSink(Ljava/util/stream/Sink;)Ljava/u HSPLjava/util/stream/AbstractSpinedBuffer;-><init>()V HSPLjava/util/stream/AbstractSpinedBuffer;->count()J HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;-><init>()V +HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;Ljava/lang/Object;)V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda15;-><init>()V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda1;-><init>()V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda20;->accept(Ljava/lang/Object;Ljava/lang/Object;)V @@ -7585,7 +7678,7 @@ HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda39;->accept(Ljava/lang/O HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;-><init>()V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda41;->get()Ljava/lang/Object; HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;-><init>()V -HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/Set;Ljava/util/HashSet; +HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda42;->accept(Ljava/lang/Object;Ljava/lang/Object;)V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda50;->get()Ljava/lang/Object; HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda51;-><init>()V @@ -7598,7 +7691,7 @@ HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda66;->get()Ljava/lang/Obj HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;-><init>()V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda74;->get()Ljava/lang/Object; HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;-><init>()V -HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V+]Ljava/util/List;Ljava/util/ArrayList; +HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda75;->accept(Ljava/lang/Object;Ljava/lang/Object;)V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda76;-><init>()V HSPLjava/util/stream/Collectors$$ExternalSyntheticLambda87;-><init>()V HSPLjava/util/stream/Collectors$CollectorImpl;-><init>(Ljava/util/function/Supplier;Ljava/util/function/BiConsumer;Ljava/util/function/BinaryOperator;Ljava/util/Set;)V @@ -7658,16 +7751,18 @@ HSPLjava/util/stream/ForEachOps$ForEachOp;->evaluateSequential(Ljava/util/stream HSPLjava/util/stream/ForEachOps$ForEachOp;->get()Ljava/lang/Void; HSPLjava/util/stream/ForEachOps$ForEachOp;->getOpFlags()I HSPLjava/util/stream/ForEachOps;->makeRef(Ljava/util/function/Consumer;Z)Ljava/util/stream/TerminalOp; +HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda13;->applyAsInt(II)I HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;-><init>()V HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda7;->apply(I)Ljava/lang/Object; +HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;-><init>()V HSPLjava/util/stream/IntPipeline$$ExternalSyntheticLambda8;->apply(I)Ljava/lang/Object; HSPLjava/util/stream/IntPipeline$4$1;-><init>(Ljava/util/stream/IntPipeline$4;Ljava/util/stream/Sink;)V HSPLjava/util/stream/IntPipeline$4$1;->accept(I)V HSPLjava/util/stream/IntPipeline$4;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntFunction;)V HSPLjava/util/stream/IntPipeline$4;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink; HSPLjava/util/stream/IntPipeline$9$1;-><init>(Ljava/util/stream/IntPipeline$9;Ljava/util/stream/Sink;)V -HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V+]Ljava/util/stream/Sink;megamorphic_types]Ljava/util/function/IntPredicate;Landroid/telephony/TelephonyManager$$ExternalSyntheticLambda11; -HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V+]Ljava/util/stream/Sink;Ljava/util/stream/IntPipeline$4$1; +HSPLjava/util/stream/IntPipeline$9$1;->accept(I)V +HSPLjava/util/stream/IntPipeline$9$1;->begin(J)V HSPLjava/util/stream/IntPipeline$9;-><init>(Ljava/util/stream/IntPipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/IntPredicate;)V HSPLjava/util/stream/IntPipeline$9;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink; HSPLjava/util/stream/IntPipeline$Head;-><init>(Ljava/util/Spliterator;IZ)V @@ -7683,7 +7778,6 @@ HSPLjava/util/stream/IntPipeline;->allMatch(Ljava/util/function/IntPredicate;)Z HSPLjava/util/stream/IntPipeline;->boxed()Ljava/util/stream/Stream; HSPLjava/util/stream/IntPipeline;->distinct()Ljava/util/stream/IntStream; HSPLjava/util/stream/IntPipeline;->filter(Ljava/util/function/IntPredicate;)Ljava/util/stream/IntStream; -HSPLjava/util/stream/IntPipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V HSPLjava/util/stream/IntPipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder; HSPLjava/util/stream/IntPipeline;->mapToObj(Ljava/util/function/IntFunction;)Ljava/util/stream/Stream; HSPLjava/util/stream/IntPipeline;->reduce(ILjava/util/function/IntBinaryOperator;)I @@ -7708,7 +7802,7 @@ HSPLjava/util/stream/MatchOps$$ExternalSyntheticLambda3;->get()Ljava/lang/Object HSPLjava/util/stream/MatchOps$1MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/Predicate;)V HSPLjava/util/stream/MatchOps$1MatchSink;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/MatchOps$2MatchSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;Ljava/util/function/IntPredicate;)V -HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V+]Ljava/util/function/IntPredicate;missing_types +HSPLjava/util/stream/MatchOps$2MatchSink;->accept(I)V HSPLjava/util/stream/MatchOps$BooleanTerminalSink;-><init>(Ljava/util/stream/MatchOps$MatchKind;)V HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->cancellationRequested()Z HSPLjava/util/stream/MatchOps$BooleanTerminalSink;->getAndClearState()Z @@ -7741,6 +7835,11 @@ HSPLjava/util/stream/Nodes$IntFixedNodeBuilder;->build()Ljava/util/stream/Node; HSPLjava/util/stream/Nodes$IntFixedNodeBuilder;->end()V HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><clinit>()V HSPLjava/util/stream/Nodes$SpinedNodeBuilder;-><init>()V +HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object; +HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->begin(J)V +HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->build()Ljava/util/stream/Node; +HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->copyInto([Ljava/lang/Object;I)V +HSPLjava/util/stream/Nodes$SpinedNodeBuilder;->end()V HSPLjava/util/stream/Nodes;->builder()Ljava/util/stream/Node$Builder; HSPLjava/util/stream/Nodes;->builder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder; HSPLjava/util/stream/Nodes;->flatten(Ljava/util/stream/Node;Ljava/util/function/IntFunction;)Ljava/util/stream/Node; @@ -7774,6 +7873,7 @@ HSPLjava/util/stream/ReduceOps$5;-><init>(Ljava/util/stream/StreamShape;Ljava/ut HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$5ReducingSink; HSPLjava/util/stream/ReduceOps$5;->makeSink()Ljava/util/stream/ReduceOps$AccumulatingSink; HSPLjava/util/stream/ReduceOps$5ReducingSink;-><init>(ILjava/util/function/IntBinaryOperator;)V +HSPLjava/util/stream/ReduceOps$5ReducingSink;->accept(I)V HSPLjava/util/stream/ReduceOps$5ReducingSink;->begin(J)V HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Integer; HSPLjava/util/stream/ReduceOps$5ReducingSink;->get()Ljava/lang/Object; @@ -7793,7 +7893,7 @@ HSPLjava/util/stream/ReduceOps;->makeDouble(Ljava/util/function/DoubleBinaryOper HSPLjava/util/stream/ReduceOps;->makeInt(ILjava/util/function/IntBinaryOperator;)Ljava/util/stream/TerminalOp; HSPLjava/util/stream/ReduceOps;->makeLong(JLjava/util/function/LongBinaryOperator;)Ljava/util/stream/TerminalOp; HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/function/BinaryOperator;)Ljava/util/stream/TerminalOp; -HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/stream/Collector;)Ljava/util/stream/TerminalOp; +HSPLjava/util/stream/ReduceOps;->makeRef(Ljava/util/stream/Collector;)Ljava/util/stream/TerminalOp;+]Ljava/util/stream/Collector;Ljava/util/stream/Collectors$CollectorImpl; HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;-><init>()V HSPLjava/util/stream/ReferencePipeline$$ExternalSyntheticLambda2;->applyAsLong(Ljava/lang/Object;)J HSPLjava/util/stream/ReferencePipeline$2$1;-><init>(Ljava/util/stream/ReferencePipeline$2;Ljava/util/stream/Sink;)V @@ -7818,7 +7918,7 @@ HSPLjava/util/stream/ReferencePipeline$6$1;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/ReferencePipeline$6;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/ToDoubleFunction;)V HSPLjava/util/stream/ReferencePipeline$6;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink; HSPLjava/util/stream/ReferencePipeline$7$1;-><init>(Ljava/util/stream/ReferencePipeline$7;Ljava/util/stream/Sink;)V -HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V+]Ljava/util/function/Function;missing_types]Ljava/util/stream/Stream;Ljava/util/stream/IntPipeline$4;,Ljava/util/stream/ReferencePipeline$Head;,Ljava/util/stream/ReferencePipeline$3; +HSPLjava/util/stream/ReferencePipeline$7$1;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/ReferencePipeline$7$1;->begin(J)V HSPLjava/util/stream/ReferencePipeline$7;-><init>(Ljava/util/stream/ReferencePipeline;Ljava/util/stream/AbstractPipeline;Ljava/util/stream/StreamShape;ILjava/util/function/Function;)V HSPLjava/util/stream/ReferencePipeline$7;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink; @@ -7840,7 +7940,7 @@ HSPLjava/util/stream/ReferencePipeline;->findAny()Ljava/util/Optional; HSPLjava/util/stream/ReferencePipeline;->findFirst()Ljava/util/Optional; HSPLjava/util/stream/ReferencePipeline;->flatMap(Ljava/util/function/Function;)Ljava/util/stream/Stream; HSPLjava/util/stream/ReferencePipeline;->forEach(Ljava/util/function/Consumer;)V -HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)V +HSPLjava/util/stream/ReferencePipeline;->forEachWithCancel(Ljava/util/Spliterator;Ljava/util/stream/Sink;)Z HSPLjava/util/stream/ReferencePipeline;->lambda$count$2(Ljava/lang/Object;)J HSPLjava/util/stream/ReferencePipeline;->makeNodeBuilder(JLjava/util/function/IntFunction;)Ljava/util/stream/Node$Builder; HSPLjava/util/stream/ReferencePipeline;->map(Ljava/util/function/Function;)Ljava/util/stream/Stream; @@ -7867,18 +7967,24 @@ HSPLjava/util/stream/SortedOps$AbstractRefSortingSink;-><init>(Ljava/util/stream HSPLjava/util/stream/SortedOps$OfRef;-><init>(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)V HSPLjava/util/stream/SortedOps$OfRef;->opWrapSink(ILjava/util/stream/Sink;)Ljava/util/stream/Sink; HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;-><init>(Ljava/util/stream/Sink;)V +HSPLjava/util/stream/SortedOps$RefSortingSink$$ExternalSyntheticLambda0;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/SortedOps$RefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V +HSPLjava/util/stream/SortedOps$RefSortingSink;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/SortedOps$RefSortingSink;->begin(J)V HSPLjava/util/stream/SortedOps$RefSortingSink;->end()V HSPLjava/util/stream/SortedOps$SizedRefSortingSink;-><init>(Ljava/util/stream/Sink;Ljava/util/Comparator;)V HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->accept(Ljava/lang/Object;)V HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->begin(J)V -HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V+]Ljava/util/stream/Sink;Ljava/util/stream/ReduceOps$3ReducingSink;,Ljava/util/stream/ForEachOps$ForEachOp$OfRef; +HSPLjava/util/stream/SortedOps$SizedRefSortingSink;->end()V HSPLjava/util/stream/SortedOps;->makeRef(Ljava/util/stream/AbstractPipeline;Ljava/util/Comparator;)Ljava/util/stream/Stream; HSPLjava/util/stream/SpinedBuffer;-><init>()V HSPLjava/util/stream/SpinedBuffer;->accept(Ljava/lang/Object;)V +HSPLjava/util/stream/SpinedBuffer;->asArray(Ljava/util/function/IntFunction;)[Ljava/lang/Object; +HSPLjava/util/stream/SpinedBuffer;->capacity()J HSPLjava/util/stream/SpinedBuffer;->clear()V +HSPLjava/util/stream/SpinedBuffer;->copyInto([Ljava/lang/Object;I)V HSPLjava/util/stream/SpinedBuffer;->count()J +HSPLjava/util/stream/SpinedBuffer;->ensureCapacity(J)V HSPLjava/util/stream/Stream;->builder()Ljava/util/stream/Stream$Builder; HSPLjava/util/stream/Stream;->concat(Ljava/util/stream/Stream;Ljava/util/stream/Stream;)Ljava/util/stream/Stream; HSPLjava/util/stream/Stream;->of([Ljava/lang/Object;)Ljava/util/stream/Stream; @@ -7910,6 +8016,8 @@ HSPLjava/util/zip/CRC32;->reset()V HSPLjava/util/zip/CRC32;->update(I)V HSPLjava/util/zip/CRC32;->update([B)V HSPLjava/util/zip/CRC32;->update([BII)V +HSPLjava/util/zip/CRC32;->updateByteBuffer(IJII)I +HSPLjava/util/zip/CRC32;->updateBytes(I[BII)I HSPLjava/util/zip/CheckedInputStream;-><init>(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V HSPLjava/util/zip/CheckedInputStream;->read()I HSPLjava/util/zip/CheckedInputStream;->read([BII)I @@ -7961,14 +8069,14 @@ HSPLjava/util/zip/Inflater;-><init>()V HSPLjava/util/zip/Inflater;-><init>(Z)V HSPLjava/util/zip/Inflater;->end()V HSPLjava/util/zip/Inflater;->ended()Z -HSPLjava/util/zip/Inflater;->ensureOpen()V+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef; +HSPLjava/util/zip/Inflater;->ensureOpen()V HSPLjava/util/zip/Inflater;->finalize()V HSPLjava/util/zip/Inflater;->finished()Z HSPLjava/util/zip/Inflater;->getBytesRead()J HSPLjava/util/zip/Inflater;->getBytesWritten()J HSPLjava/util/zip/Inflater;->getRemaining()I HSPLjava/util/zip/Inflater;->getTotalOut()I -HSPLjava/util/zip/Inflater;->inflate([BII)I+]Ljava/util/zip/ZStreamRef;Ljava/util/zip/ZStreamRef; +HSPLjava/util/zip/Inflater;->inflate([BII)I HSPLjava/util/zip/Inflater;->needsDictionary()Z HSPLjava/util/zip/Inflater;->needsInput()Z HSPLjava/util/zip/Inflater;->reset()V @@ -7978,23 +8086,24 @@ HSPLjava/util/zip/InflaterInputStream;-><init>(Ljava/io/InputStream;Ljava/util/z HSPLjava/util/zip/InflaterInputStream;->available()I HSPLjava/util/zip/InflaterInputStream;->close()V HSPLjava/util/zip/InflaterInputStream;->ensureOpen()V -HSPLjava/util/zip/InflaterInputStream;->fill()V+]Ljava/io/InputStream;Ljava/io/PushbackInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater; +HSPLjava/util/zip/InflaterInputStream;->fill()V HSPLjava/util/zip/InflaterInputStream;->read()I -HSPLjava/util/zip/InflaterInputStream;->read([BII)I+]Ljava/util/zip/InflaterInputStream;Ljava/util/zip/ZipInputStream;]Ljava/util/zip/Inflater;Ljava/util/zip/Inflater; +HSPLjava/util/zip/InflaterInputStream;->read([BII)I HSPLjava/util/zip/ZStreamRef;-><init>(J)V HSPLjava/util/zip/ZStreamRef;->address()J HSPLjava/util/zip/ZStreamRef;->clear()V HSPLjava/util/zip/ZipCoder;-><init>(Ljava/nio/charset/Charset;)V -HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder; +HSPLjava/util/zip/ZipCoder;->decoder()Ljava/nio/charset/CharsetDecoder;+]Ljava/nio/charset/Charset;Lcom/android/icu/charset/CharsetICU;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; HSPLjava/util/zip/ZipCoder;->encoder()Ljava/nio/charset/CharsetEncoder; HSPLjava/util/zip/ZipCoder;->get(Ljava/nio/charset/Charset;)Ljava/util/zip/ZipCoder; -HSPLjava/util/zip/ZipCoder;->getBytes(Ljava/lang/String;)[B +HSPLjava/util/zip/ZipCoder;->getBytes(Ljava/lang/String;)[B+]Ljava/lang/String;Ljava/lang/String;]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult; HSPLjava/util/zip/ZipCoder;->isUTF8()Z -HSPLjava/util/zip/ZipCoder;->toString([BI)Ljava/lang/String; +HSPLjava/util/zip/ZipCoder;->toString([BI)Ljava/lang/String;+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult;]Ljava/nio/charset/CharsetDecoder;Lcom/android/icu/charset/CharsetDecoderICU; HSPLjava/util/zip/ZipEntry;-><init>()V HSPLjava/util/zip/ZipEntry;-><init>(Ljava/lang/String;)V HSPLjava/util/zip/ZipEntry;-><init>(Ljava/util/zip/ZipEntry;)V HSPLjava/util/zip/ZipEntry;->getCompressedSize()J +HSPLjava/util/zip/ZipEntry;->getCrc()J HSPLjava/util/zip/ZipEntry;->getMethod()I HSPLjava/util/zip/ZipEntry;->getName()Ljava/lang/String; HSPLjava/util/zip/ZipEntry;->getSize()J @@ -8028,10 +8137,11 @@ HSPLjava/util/zip/ZipFile;->ensureOpen()V HSPLjava/util/zip/ZipFile;->ensureOpenOrZipException()V HSPLjava/util/zip/ZipFile;->entries()Ljava/util/Enumeration; HSPLjava/util/zip/ZipFile;->finalize()V -HSPLjava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry; +HSPLjava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;+]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder; HSPLjava/util/zip/ZipFile;->getInflater()Ljava/util/zip/Inflater; HSPLjava/util/zip/ZipFile;->getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream; HSPLjava/util/zip/ZipFile;->getZipEntry(Ljava/lang/String;J)Ljava/util/zip/ZipEntry; +HSPLjava/util/zip/ZipFile;->onZipEntryAccess([BI)V+]Ldalvik/system/ZipPathValidator$Callback;Ldalvik/system/ZipPathValidator$1;]Ljava/util/zip/ZipCoder;Ljava/util/zip/ZipCoder; HSPLjava/util/zip/ZipFile;->releaseInflater(Ljava/util/zip/Inflater;)V HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;)V HSPLjava/util/zip/ZipInputStream;-><init>(Ljava/io/InputStream;Ljava/nio/charset/Charset;)V @@ -8040,7 +8150,7 @@ HSPLjava/util/zip/ZipInputStream;->closeEntry()V HSPLjava/util/zip/ZipInputStream;->createZipEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry; HSPLjava/util/zip/ZipInputStream;->ensureOpen()V HSPLjava/util/zip/ZipInputStream;->getNextEntry()Ljava/util/zip/ZipEntry; -HSPLjava/util/zip/ZipInputStream;->read([BII)I+]Ljava/util/zip/CRC32;Ljava/util/zip/CRC32;]Ljava/io/InputStream;Ljava/io/PushbackInputStream; +HSPLjava/util/zip/ZipInputStream;->read([BII)I HSPLjava/util/zip/ZipInputStream;->readEnd(Ljava/util/zip/ZipEntry;)V HSPLjava/util/zip/ZipInputStream;->readFully([BII)V HSPLjava/util/zip/ZipInputStream;->readLOC()Ljava/util/zip/ZipEntry; @@ -8078,11 +8188,13 @@ HSPLjavax/crypto/Cipher;->tokenizeTransformation(Ljava/lang/String;)[Ljava/lang/ HSPLjavax/crypto/Cipher;->tryCombinations(Ljavax/crypto/Cipher$InitParams;Ljava/security/Provider;[Ljava/lang/String;)Ljavax/crypto/Cipher$CipherSpiAndProvider; HSPLjavax/crypto/Cipher;->tryTransformWithProvider(Ljavax/crypto/Cipher$InitParams;[Ljava/lang/String;Ljavax/crypto/Cipher$NeedToSet;Ljava/security/Provider$Service;)Ljavax/crypto/Cipher$CipherSpiAndProvider; HSPLjavax/crypto/Cipher;->unwrap([BLjava/lang/String;I)Ljava/security/Key; -HSPLjavax/crypto/Cipher;->update([BII[BI)I+]Ljavax/crypto/Cipher;Ljavax/crypto/Cipher;]Ljavax/crypto/CipherSpi;Lcom/android/org/conscrypt/OpenSSLEvpCipherAES$AES$CBC$PKCS5Padding;,Lcom/android/org/conscrypt/OpenSSLEvpCipherAES$AES$CTR; +HSPLjavax/crypto/Cipher;->update([BII[BI)I HSPLjavax/crypto/Cipher;->updateAAD([B)V HSPLjavax/crypto/Cipher;->updateAAD([BII)V HSPLjavax/crypto/Cipher;->updateProviderIfNeeded()V HSPLjavax/crypto/CipherSpi;-><init>()V +HSPLjavax/crypto/CipherSpi;->bufferCrypt(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Z)I +HSPLjavax/crypto/CipherSpi;->engineDoFinal(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I HSPLjavax/crypto/JarVerifier;-><init>(Ljava/net/URL;Z)V HSPLjavax/crypto/JarVerifier;->verify()V HSPLjavax/crypto/JceSecurity$1;-><init>(Ljava/lang/Class;)V @@ -8250,11 +8362,11 @@ HSPLjavax/xml/parsers/SAXParserFactory;->newInstance()Ljavax/xml/parsers/SAXPars HSPLjdk/internal/math/FDBigInteger;-><init>(J[CII)V HSPLjdk/internal/math/FDBigInteger;-><init>([II)V HSPLjdk/internal/math/FDBigInteger;->add(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger; -HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FDBigInteger;->addAndCmp(Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger;)I HSPLjdk/internal/math/FDBigInteger;->big5pow(I)Ljdk/internal/math/FDBigInteger; HSPLjdk/internal/math/FDBigInteger;->checkZeroTail([II)I HSPLjdk/internal/math/FDBigInteger;->cmp(Ljdk/internal/math/FDBigInteger;)I -HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FDBigInteger;->cmpPow52(II)I HSPLjdk/internal/math/FDBigInteger;->getNormalizationBias()I HSPLjdk/internal/math/FDBigInteger;->leftInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger; HSPLjdk/internal/math/FDBigInteger;->leftShift(I)Ljdk/internal/math/FDBigInteger; @@ -8271,20 +8383,20 @@ HSPLjdk/internal/math/FDBigInteger;->quoRemIteration(Ljdk/internal/math/FDBigInt HSPLjdk/internal/math/FDBigInteger;->rightInplaceSub(Ljdk/internal/math/FDBigInteger;)Ljdk/internal/math/FDBigInteger; HSPLjdk/internal/math/FDBigInteger;->size()I HSPLjdk/internal/math/FDBigInteger;->trimLeadingZeros()V -HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FDBigInteger;->valueOfMulPow52(JII)Ljdk/internal/math/FDBigInteger; HSPLjdk/internal/math/FDBigInteger;->valueOfPow2(I)Ljdk/internal/math/FDBigInteger; -HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger;+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FDBigInteger;->valueOfPow52(II)Ljdk/internal/math/FDBigInteger; HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljava/lang/Object; HSPLjdk/internal/math/FloatingDecimal$1;->initialValue()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer; HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;-><init>(ZI[CI)V HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->doubleValue()D+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; -HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer;->floatValue()F HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$mdtoa(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;IJIZ)V HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->-$$Nest$msetSign(Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;Z)V HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;-><init>()V -HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; +HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->appendTo(Ljava/lang/Appendable;)V HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->developLongDigits(IJI)V -HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V+]Ljdk/internal/math/FDBigInteger;Ljdk/internal/math/FDBigInteger; +HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->dtoa(IJIZ)V HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->estimateDecExp(JI)I HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getChars([C)I HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->getDecimalExponent()I @@ -8296,12 +8408,12 @@ HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->setSign(Z)V HSPLjdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;->toJavaFormatString()Ljava/lang/String; HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->doubleValue()D HSPLjdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer;->floatValue()F -HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V+]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer; -HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FloatingDecimal$1; +HSPLjdk/internal/math/FloatingDecimal;->appendTo(FLjava/lang/Appendable;)V +HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIBuffer()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer; HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter; HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(DZ)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter; HSPLjdk/internal/math/FloatingDecimal;->getBinaryToASCIIConverter(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter; -HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D+]Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer; +HSPLjdk/internal/math/FloatingDecimal;->parseDouble(Ljava/lang/String;)D HSPLjdk/internal/math/FloatingDecimal;->parseFloat(Ljava/lang/String;)F HSPLjdk/internal/math/FloatingDecimal;->readJavaFormatString(Ljava/lang/String;)Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLjdk/internal/math/FloatingDecimal;->toJavaFormatString(D)Ljava/lang/String; @@ -8313,15 +8425,19 @@ HSPLjdk/internal/math/FormattedFloatingDecimal$Form;-><clinit>()V HSPLjdk/internal/math/FormattedFloatingDecimal$Form;-><init>(Ljava/lang/String;I)V HSPLjdk/internal/math/FormattedFloatingDecimal$Form;->values()[Ljdk/internal/math/FormattedFloatingDecimal$Form; HSPLjdk/internal/math/FormattedFloatingDecimal;-><clinit>()V -HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V+]Ljdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FormattedFloatingDecimal$Form;]Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer; +HSPLjdk/internal/math/FormattedFloatingDecimal;-><init>(ILjdk/internal/math/FormattedFloatingDecimal$Form;Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;)V HSPLjdk/internal/math/FormattedFloatingDecimal;->applyPrecision(I[CII)I HSPLjdk/internal/math/FormattedFloatingDecimal;->create(ZI)[C HSPLjdk/internal/math/FormattedFloatingDecimal;->fillDecimal(I[CIIZ)V -HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C+]Ljava/lang/ThreadLocal;Ljdk/internal/math/FormattedFloatingDecimal$1; +HSPLjdk/internal/math/FormattedFloatingDecimal;->getBuffer()[C +HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponent()[C +HSPLjdk/internal/math/FormattedFloatingDecimal;->getExponentRounded()I HSPLjdk/internal/math/FormattedFloatingDecimal;->getMantissa()[C HSPLjdk/internal/math/FormattedFloatingDecimal;->valueOf(DILjdk/internal/math/FormattedFloatingDecimal$Form;)Ljdk/internal/math/FormattedFloatingDecimal; +HSPLjdk/internal/misc/Unsafe;->compareAndSetObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z HSPLjdk/internal/misc/Unsafe;->getAndAddInt(Ljava/lang/Object;JI)I HSPLjdk/internal/misc/Unsafe;->getAndAddLong(Ljava/lang/Object;JJ)J +HSPLjdk/internal/misc/Unsafe;->getAndBitwiseAndInt(Ljava/lang/Object;JI)I+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjdk/internal/misc/Unsafe;->getAndSetInt(Ljava/lang/Object;JI)I HSPLjdk/internal/misc/Unsafe;->getAndSetLong(Ljava/lang/Object;JJ)J HSPLjdk/internal/misc/Unsafe;->getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; @@ -8329,15 +8445,26 @@ HSPLjdk/internal/misc/Unsafe;->getIntAcquire(Ljava/lang/Object;J)I HSPLjdk/internal/misc/Unsafe;->getIntUnaligned(Ljava/lang/Object;J)I HSPLjdk/internal/misc/Unsafe;->getLongAcquire(Ljava/lang/Object;J)J HSPLjdk/internal/misc/Unsafe;->getLongUnaligned(Ljava/lang/Object;J)J +HSPLjdk/internal/misc/Unsafe;->getObject(Ljava/lang/Object;J)Ljava/lang/Object; HSPLjdk/internal/misc/Unsafe;->getObjectAcquire(Ljava/lang/Object;J)Ljava/lang/Object; +HSPLjdk/internal/misc/Unsafe;->getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object; +HSPLjdk/internal/misc/Unsafe;->getReferenceAcquire(Ljava/lang/Object;J)Ljava/lang/Object;+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjdk/internal/misc/Unsafe;->getUnsafe()Ljdk/internal/misc/Unsafe; HSPLjdk/internal/misc/Unsafe;->makeLong(II)J HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/Class;Ljava/lang/String;)J HSPLjdk/internal/misc/Unsafe;->objectFieldOffset(Ljava/lang/reflect/Field;)J +HSPLjdk/internal/misc/Unsafe;->pickPos(II)I +HSPLjdk/internal/misc/Unsafe;->putIntOpaque(Ljava/lang/Object;JI)V HSPLjdk/internal/misc/Unsafe;->putIntRelease(Ljava/lang/Object;JI)V HSPLjdk/internal/misc/Unsafe;->putLongRelease(Ljava/lang/Object;JJ)V +HSPLjdk/internal/misc/Unsafe;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V HSPLjdk/internal/misc/Unsafe;->putObjectRelease(Ljava/lang/Object;JLjava/lang/Object;)V +HSPLjdk/internal/misc/Unsafe;->putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V +HSPLjdk/internal/misc/Unsafe;->putReferenceOpaque(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; +HSPLjdk/internal/misc/Unsafe;->putReferenceRelease(Ljava/lang/Object;JLjava/lang/Object;)V+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjdk/internal/misc/Unsafe;->toUnsignedLong(I)J +HSPLjdk/internal/misc/Unsafe;->weakCompareAndSetInt(Ljava/lang/Object;JII)Z +HSPLjdk/internal/misc/Unsafe;->weakCompareAndSetReference(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z+]Ljdk/internal/misc/Unsafe;Ljdk/internal/misc/Unsafe; HSPLjdk/internal/misc/VM;->getSavedProperty(Ljava/lang/String;)Ljava/lang/String; HSPLjdk/internal/reflect/Reflection;->getCallerClass()Ljava/lang/Class; HSPLjdk/internal/util/ArraysSupport;->mismatch([B[BI)I @@ -8369,8 +8496,10 @@ HSPLlibcore/icu/DecimalFormatData;->getDecimalSeparator()C HSPLlibcore/icu/DecimalFormatData;->getExponentSeparator()Ljava/lang/String; HSPLlibcore/icu/DecimalFormatData;->getGroupingSeparator()C HSPLlibcore/icu/DecimalFormatData;->getInfinity()Ljava/lang/String; -HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData;+]Ljava/util/Locale;Ljava/util/Locale;]Ljava/util/concurrent/ConcurrentHashMap;Ljava/util/concurrent/ConcurrentHashMap; +HSPLlibcore/icu/DecimalFormatData;->getInstance(Ljava/util/Locale;)Llibcore/icu/DecimalFormatData; HSPLlibcore/icu/DecimalFormatData;->getMinusSign()Ljava/lang/String; +HSPLlibcore/icu/DecimalFormatData;->getMonetaryGroupSeparator()Ljava/lang/String; +HSPLlibcore/icu/DecimalFormatData;->getMonetarySeparator()Ljava/lang/String; HSPLlibcore/icu/DecimalFormatData;->getNaN()Ljava/lang/String; HSPLlibcore/icu/DecimalFormatData;->getNumberPattern()Ljava/lang/String; HSPLlibcore/icu/DecimalFormatData;->getPatternSeparator()C @@ -8391,7 +8520,7 @@ HSPLlibcore/icu/ICU;->setDefaultLocale(Ljava/lang/String;)V HSPLlibcore/icu/ICU;->transformIcuDateTimePattern(Ljava/lang/String;)Ljava/lang/String; HSPLlibcore/icu/ICU;->transformIcuDateTimePattern_forJavaText(Ljava/lang/String;)Ljava/lang/String; HSPLlibcore/icu/LocaleData;->get(Ljava/util/Locale;)Llibcore/icu/LocaleData; -HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale; +HSPLlibcore/icu/LocaleData;->getCompatibleLocaleForBug159514442(Ljava/util/Locale;)Ljava/util/Locale;+]Ldalvik/system/VMRuntime;Ldalvik/system/VMRuntime;]Ljava/util/Locale;Ljava/util/Locale; HSPLlibcore/icu/LocaleData;->initLocaleData(Ljava/util/Locale;)Llibcore/icu/LocaleData; HSPLlibcore/icu/LocaleData;->initializeCalendarData(Ljava/util/Locale;)V HSPLlibcore/icu/LocaleData;->initializeDateFormatData(Ljava/util/Locale;)V @@ -8403,13 +8532,13 @@ HSPLlibcore/internal/StringPool;-><init>()V HSPLlibcore/internal/StringPool;->contentEquals(Ljava/lang/String;[CII)Z HSPLlibcore/internal/StringPool;->get([CII)Ljava/lang/String; HSPLlibcore/io/BlockGuardOs;->accept(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)Ljava/io/FileDescriptor; -HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; +HSPLlibcore/io/BlockGuardOs;->access(Ljava/lang/String;I)Z+]Ldalvik/system/BlockGuard$VmPolicy;Ldalvik/system/BlockGuard$2;,Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; HSPLlibcore/io/BlockGuardOs;->android_getaddrinfo(Ljava/lang/String;Landroid/system/StructAddrinfo;I)[Ljava/net/InetAddress; HSPLlibcore/io/BlockGuardOs;->chmod(Ljava/lang/String;I)V HSPLlibcore/io/BlockGuardOs;->close(Ljava/io/FileDescriptor;)V HSPLlibcore/io/BlockGuardOs;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V HSPLlibcore/io/BlockGuardOs;->fdatasync(Ljava/io/FileDescriptor;)V -HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat;+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLlibcore/io/BlockGuardOs;->fstat(Ljava/io/FileDescriptor;)Landroid/system/StructStat; HSPLlibcore/io/BlockGuardOs;->ftruncate(Ljava/io/FileDescriptor;J)V HSPLlibcore/io/BlockGuardOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B HSPLlibcore/io/BlockGuardOs;->isInetDomain(I)Z @@ -8422,7 +8551,7 @@ HSPLlibcore/io/BlockGuardOs;->isUnixSocket(Ljava/io/FileDescriptor;)Z HSPLlibcore/io/BlockGuardOs;->lseek(Ljava/io/FileDescriptor;JI)J HSPLlibcore/io/BlockGuardOs;->lstat(Ljava/lang/String;)Landroid/system/StructStat; HSPLlibcore/io/BlockGuardOs;->mkdir(Ljava/lang/String;I)V -HSPLlibcore/io/BlockGuardOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor;+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; +HSPLlibcore/io/BlockGuardOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor; HSPLlibcore/io/BlockGuardOs;->poll([Landroid/system/StructPollfd;I)I HSPLlibcore/io/BlockGuardOs;->posix_fallocate(Ljava/io/FileDescriptor;JJ)V HSPLlibcore/io/BlockGuardOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I @@ -8434,17 +8563,17 @@ HSPLlibcore/io/BlockGuardOs;->rename(Ljava/lang/String;Ljava/lang/String;)V HSPLlibcore/io/BlockGuardOs;->sendto(Ljava/io/FileDescriptor;[BIIILjava/net/InetAddress;I)I HSPLlibcore/io/BlockGuardOs;->socket(III)Ljava/io/FileDescriptor; HSPLlibcore/io/BlockGuardOs;->socketpair(IIILjava/io/FileDescriptor;Ljava/io/FileDescriptor;)V -HSPLlibcore/io/BlockGuardOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;+]Ldalvik/system/BlockGuard$VmPolicy;Landroid/os/StrictMode$5;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLlibcore/io/BlockGuardOs;->stat(Ljava/lang/String;)Landroid/system/StructStat; HSPLlibcore/io/BlockGuardOs;->statvfs(Ljava/lang/String;)Landroid/system/StructStatVfs; HSPLlibcore/io/BlockGuardOs;->tagSocket(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor; -HSPLlibcore/io/BlockGuardOs;->write(Ljava/io/FileDescriptor;[BII)I+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLlibcore/io/BlockGuardOs;->write(Ljava/io/FileDescriptor;[BII)I+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection$1;-><init>(Llibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;Ljava/io/InputStream;)V HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection$1;->close()V HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;-><init>(Llibcore/io/ClassPathURLStreamHandler;Ljava/net/URL;)V HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->connect()V HSPLlibcore/io/ClassPathURLStreamHandler$ClassPathURLConnection;->getInputStream()Ljava/io/InputStream; HSPLlibcore/io/ClassPathURLStreamHandler;-><init>(Ljava/lang/String;)V -HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL; +HSPLlibcore/io/ClassPathURLStreamHandler;->getEntryUrlOrNull(Ljava/lang/String;)Ljava/net/URL;+]Ljava/util/jar/JarFile;Ljava/util/jar/JarFile; HSPLlibcore/io/ClassPathURLStreamHandler;->isEntryStored(Ljava/lang/String;)Z HSPLlibcore/io/ClassPathURLStreamHandler;->openConnection(Ljava/net/URL;)Ljava/net/URLConnection; HSPLlibcore/io/ForwardingOs;-><init>(Llibcore/io/Os;)V @@ -8474,13 +8603,13 @@ HSPLlibcore/io/ForwardingOs;->getpid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs HSPLlibcore/io/ForwardingOs;->getsockname(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress; HSPLlibcore/io/ForwardingOs;->getsockoptInt(Ljava/io/FileDescriptor;II)I HSPLlibcore/io/ForwardingOs;->getsockoptLinger(Ljava/io/FileDescriptor;II)Landroid/system/StructLinger; -HSPLlibcore/io/ForwardingOs;->gettid()I +HSPLlibcore/io/ForwardingOs;->gettid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; HSPLlibcore/io/ForwardingOs;->getuid()I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; HSPLlibcore/io/ForwardingOs;->getxattr(Ljava/lang/String;Ljava/lang/String;)[B HSPLlibcore/io/ForwardingOs;->if_nametoindex(Ljava/lang/String;)I HSPLlibcore/io/ForwardingOs;->ioctlInt(Ljava/io/FileDescriptor;I)I HSPLlibcore/io/ForwardingOs;->listen(Ljava/io/FileDescriptor;I)V -HSPLlibcore/io/ForwardingOs;->lseek(Ljava/io/FileDescriptor;JI)J+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; +HSPLlibcore/io/ForwardingOs;->lseek(Ljava/io/FileDescriptor;JI)J HSPLlibcore/io/ForwardingOs;->lstat(Ljava/lang/String;)Landroid/system/StructStat; HSPLlibcore/io/ForwardingOs;->mkdir(Ljava/lang/String;I)V HSPLlibcore/io/ForwardingOs;->mmap(JJIILjava/io/FileDescriptor;J)J @@ -8488,7 +8617,7 @@ HSPLlibcore/io/ForwardingOs;->open(Ljava/lang/String;II)Ljava/io/FileDescriptor; HSPLlibcore/io/ForwardingOs;->pipe2(I)[Ljava/io/FileDescriptor; HSPLlibcore/io/ForwardingOs;->poll([Landroid/system/StructPollfd;I)I HSPLlibcore/io/ForwardingOs;->posix_fallocate(Ljava/io/FileDescriptor;JJ)V -HSPLlibcore/io/ForwardingOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; +HSPLlibcore/io/ForwardingOs;->pread(Ljava/io/FileDescriptor;[BIIJ)I HSPLlibcore/io/ForwardingOs;->read(Ljava/io/FileDescriptor;[BII)I+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; HSPLlibcore/io/ForwardingOs;->readlink(Ljava/lang/String;)Ljava/lang/String; HSPLlibcore/io/ForwardingOs;->recvfrom(Ljava/io/FileDescriptor;[BIIILjava/net/InetSocketAddress;)I @@ -8504,7 +8633,7 @@ HSPLlibcore/io/ForwardingOs;->setsockoptTimeval(Ljava/io/FileDescriptor;IILandro HSPLlibcore/io/ForwardingOs;->shutdown(Ljava/io/FileDescriptor;I)V HSPLlibcore/io/ForwardingOs;->socket(III)Ljava/io/FileDescriptor; HSPLlibcore/io/ForwardingOs;->socketpair(IIILjava/io/FileDescriptor;Ljava/io/FileDescriptor;)V -HSPLlibcore/io/ForwardingOs;->stat(Ljava/lang/String;)Landroid/system/StructStat;+]Llibcore/io/Os;Llibcore/io/BlockGuardOs;,Llibcore/io/Linux; +HSPLlibcore/io/ForwardingOs;->stat(Ljava/lang/String;)Landroid/system/StructStat; HSPLlibcore/io/ForwardingOs;->statvfs(Ljava/lang/String;)Landroid/system/StructStatVfs; HSPLlibcore/io/ForwardingOs;->strerror(I)Ljava/lang/String; HSPLlibcore/io/ForwardingOs;->sysconf(I)J @@ -8512,7 +8641,7 @@ HSPLlibcore/io/ForwardingOs;->write(Ljava/io/FileDescriptor;[BII)I+]Llibcore/io/ HSPLlibcore/io/IoBridge;->bind(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V HSPLlibcore/io/IoBridge;->booleanFromInt(I)Z HSPLlibcore/io/IoBridge;->booleanToInt(Z)I -HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Ljava/io/FileDescriptor;Ljava/io/FileDescriptor; +HSPLlibcore/io/IoBridge;->closeAndSignalBlockedThreads(Ljava/io/FileDescriptor;)V HSPLlibcore/io/IoBridge;->connect(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V HSPLlibcore/io/IoBridge;->connectErrno(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V HSPLlibcore/io/IoBridge;->createMessageForException(Ljava/io/FileDescriptor;Ljava/net/InetAddress;IILjava/lang/Exception;)Ljava/lang/String; @@ -8520,7 +8649,7 @@ HSPLlibcore/io/IoBridge;->getLocalInetSocketAddress(Ljava/io/FileDescriptor;)Lja HSPLlibcore/io/IoBridge;->getSocketOption(Ljava/io/FileDescriptor;I)Ljava/lang/Object; HSPLlibcore/io/IoBridge;->getSocketOptionErrno(Ljava/io/FileDescriptor;I)Ljava/lang/Object; HSPLlibcore/io/IoBridge;->isConnected(Ljava/io/FileDescriptor;Ljava/net/InetAddress;III)Z -HSPLlibcore/io/IoBridge;->open(Ljava/lang/String;I)Ljava/io/FileDescriptor;+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; +HSPLlibcore/io/IoBridge;->open(Ljava/lang/String;I)Ljava/io/FileDescriptor; HSPLlibcore/io/IoBridge;->poll(Ljava/io/FileDescriptor;II)V HSPLlibcore/io/IoBridge;->postRecvfrom(ZLjava/net/DatagramPacket;Ljava/net/InetSocketAddress;I)I HSPLlibcore/io/IoBridge;->read(Ljava/io/FileDescriptor;[BII)I+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; @@ -8532,7 +8661,7 @@ HSPLlibcore/io/IoBridge;->socket(III)Ljava/io/FileDescriptor; HSPLlibcore/io/IoBridge;->write(Ljava/io/FileDescriptor;[BII)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs; HSPLlibcore/io/IoTracker;-><init>()V HSPLlibcore/io/IoTracker;->reset()V -HSPLlibcore/io/IoTracker;->trackIo(I)V+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLlibcore/io/IoTracker;->trackIo(I)V+]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1;,Landroid/os/StrictMode$AndroidBlockGuardPolicy; HSPLlibcore/io/IoTracker;->trackIo(ILlibcore/io/IoTracker$Mode;)V+]Llibcore/io/IoTracker;Llibcore/io/IoTracker; HSPLlibcore/io/IoUtils;->acquireRawFd(Ljava/io/FileDescriptor;)I HSPLlibcore/io/IoUtils;->canOpenReadOnly(Ljava/lang/String;)Z @@ -8542,7 +8671,7 @@ HSPLlibcore/io/IoUtils;->closeQuietly(Ljava/lang/AutoCloseable;)V HSPLlibcore/io/IoUtils;->generateFdOwnerId(Ljava/lang/Object;)J HSPLlibcore/io/IoUtils;->isParcelFileDescriptor(Ljava/lang/Object;)Z HSPLlibcore/io/IoUtils;->setBlocking(Ljava/io/FileDescriptor;Z)V -HSPLlibcore/io/IoUtils;->setFdOwner(Ljava/io/FileDescriptor;Ljava/lang/Object;)V+]Llibcore/io/Os;Landroid/app/ActivityThread$AndroidOs;]Ljava/io/FileDescriptor;Ljava/io/FileDescriptor; +HSPLlibcore/io/IoUtils;->setFdOwner(Ljava/io/FileDescriptor;Ljava/lang/Object;)V HSPLlibcore/io/Libcore;->compareAndSetOs(Llibcore/io/Os;Llibcore/io/Os;)Z HSPLlibcore/io/Libcore;->getOs()Llibcore/io/Os; HSPLlibcore/io/Linux;->pread(Ljava/io/FileDescriptor;[BIIJ)I @@ -8579,37 +8708,37 @@ HSPLlibcore/net/http/HttpURLConnectionFactory;->createInstance()Llibcore/net/htt HSPLlibcore/net/http/HttpURLConnectionFactory;->openConnection(Ljava/net/URL;Ljavax/net/SocketFactory;Ljava/net/Proxy;)Ljava/net/URLConnection; HSPLlibcore/net/http/HttpURLConnectionFactory;->setDns(Llibcore/net/http/Dns;)V HSPLlibcore/net/http/HttpURLConnectionFactory;->setNewConnectionPool(IJLjava/util/concurrent/TimeUnit;)V -HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V +HSPLlibcore/reflect/AnnotationFactory;-><init>(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)V+]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember; HSPLlibcore/reflect/AnnotationFactory;->createAnnotation(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation; HSPLlibcore/reflect/AnnotationFactory;->getElementsDescription(Ljava/lang/Class;)[Llibcore/reflect/AnnotationMember; -HSPLlibcore/reflect/AnnotationFactory;->invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; +HSPLlibcore/reflect/AnnotationFactory;->invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember; HSPLlibcore/reflect/AnnotationMember;-><init>(Ljava/lang/String;Ljava/lang/Object;)V HSPLlibcore/reflect/AnnotationMember;-><init>(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V HSPLlibcore/reflect/AnnotationMember;->copyValue()Ljava/lang/Object; HSPLlibcore/reflect/AnnotationMember;->setDefinition(Llibcore/reflect/AnnotationMember;)Llibcore/reflect/AnnotationMember; -HSPLlibcore/reflect/AnnotationMember;->validateValue()Ljava/lang/Object; +HSPLlibcore/reflect/AnnotationMember;->validateValue()Ljava/lang/Object;+]Ljava/lang/Object;missing_types]Llibcore/reflect/AnnotationMember;Llibcore/reflect/AnnotationMember; HSPLlibcore/reflect/GenericArrayTypeImpl;->getGenericComponentType()Ljava/lang/reflect/Type; HSPLlibcore/reflect/GenericSignatureParser;-><init>(Ljava/lang/ClassLoader;)V -HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V +HSPLlibcore/reflect/GenericSignatureParser;->expect(C)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/GenericSignatureParser;->isStopSymbol(C)Z HSPLlibcore/reflect/GenericSignatureParser;->parseClassSignature()V -HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type; +HSPLlibcore/reflect/GenericSignatureParser;->parseClassTypeSignature()Ljava/lang/reflect/Type;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/GenericSignatureParser;->parseFieldTypeSignature()Ljava/lang/reflect/Type; HSPLlibcore/reflect/GenericSignatureParser;->parseForClass(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V HSPLlibcore/reflect/GenericSignatureParser;->parseForConstructor(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V -HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V +HSPLlibcore/reflect/GenericSignatureParser;->parseForField(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/GenericSignatureParser;->parseForMethod(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;[Ljava/lang/Class;)V HSPLlibcore/reflect/GenericSignatureParser;->parseFormalTypeParameter()Llibcore/reflect/TypeVariableImpl; HSPLlibcore/reflect/GenericSignatureParser;->parseMethodTypeSignature([Ljava/lang/Class;)V HSPLlibcore/reflect/GenericSignatureParser;->parseOptFormalTypeParameters()V -HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes; +HSPLlibcore/reflect/GenericSignatureParser;->parseOptTypeArguments()Llibcore/reflect/ListOfTypes;+]Llibcore/reflect/ListOfTypes;Llibcore/reflect/ListOfTypes;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/GenericSignatureParser;->parseReturnType()Ljava/lang/reflect/Type; HSPLlibcore/reflect/GenericSignatureParser;->parseTypeArgument()Ljava/lang/reflect/Type; HSPLlibcore/reflect/GenericSignatureParser;->parseTypeSignature()Ljava/lang/reflect/Type; HSPLlibcore/reflect/GenericSignatureParser;->parseTypeVariableSignature()Llibcore/reflect/TypeVariableImpl; -HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V +HSPLlibcore/reflect/GenericSignatureParser;->scanIdentifier()V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/GenericSignatureParser;->scanSymbol()V -HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V +HSPLlibcore/reflect/GenericSignatureParser;->setInput(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String;]Llibcore/reflect/GenericSignatureParser;Llibcore/reflect/GenericSignatureParser; HSPLlibcore/reflect/ListOfTypes;-><init>(I)V HSPLlibcore/reflect/ListOfTypes;-><init>([Ljava/lang/reflect/Type;)V HSPLlibcore/reflect/ListOfTypes;->add(Ljava/lang/reflect/Type;)V @@ -8620,10 +8749,11 @@ HSPLlibcore/reflect/ListOfVariables;-><init>()V HSPLlibcore/reflect/ListOfVariables;->add(Ljava/lang/reflect/TypeVariable;)V HSPLlibcore/reflect/ListOfVariables;->getArray()[Ljava/lang/reflect/TypeVariable; HSPLlibcore/reflect/ParameterizedTypeImpl;-><init>(Llibcore/reflect/ParameterizedTypeImpl;Ljava/lang/String;Llibcore/reflect/ListOfTypes;Ljava/lang/ClassLoader;)V +HSPLlibcore/reflect/ParameterizedTypeImpl;->equals(Ljava/lang/Object;)Z HSPLlibcore/reflect/ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type; HSPLlibcore/reflect/ParameterizedTypeImpl;->getOwnerType()Ljava/lang/reflect/Type; HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/Class; -HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type; +HSPLlibcore/reflect/ParameterizedTypeImpl;->getRawType()Ljava/lang/reflect/Type;+]Llibcore/reflect/ParameterizedTypeImpl;Llibcore/reflect/ParameterizedTypeImpl; HSPLlibcore/reflect/ParameterizedTypeImpl;->getResolvedType()Ljava/lang/reflect/Type; HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;)V HSPLlibcore/reflect/TypeVariableImpl;-><init>(Ljava/lang/reflect/GenericDeclaration;Ljava/lang/String;Llibcore/reflect/ListOfTypes;)V @@ -8680,11 +8810,11 @@ HSPLlibcore/util/XmlObjectFactory;->newXmlPullParser()Lorg/xmlpull/v1/XmlPullPar HSPLlibcore/util/ZoneInfo;-><init>(Lcom/android/i18n/timezone/ZoneInfoData;IZ)V+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Llibcore/util/ZoneInfo;Llibcore/util/ZoneInfo; HSPLlibcore/util/ZoneInfo;->clone()Ljava/lang/Object; HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;)Llibcore/util/ZoneInfo; -HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo;+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData;]Ljava/lang/Integer;Ljava/lang/Integer; +HSPLlibcore/util/ZoneInfo;->createZoneInfo(Lcom/android/i18n/timezone/ZoneInfoData;J)Llibcore/util/ZoneInfo; HSPLlibcore/util/ZoneInfo;->getDSTSavings()I -HSPLlibcore/util/ZoneInfo;->getOffset(J)I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData; -HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I -HSPLlibcore/util/ZoneInfo;->getRawOffset()I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData; +HSPLlibcore/util/ZoneInfo;->getOffset(J)I +HSPLlibcore/util/ZoneInfo;->getOffsetsByUtcTime(J[I)I+]Lcom/android/i18n/timezone/ZoneInfoData;Lcom/android/i18n/timezone/ZoneInfoData; +HSPLlibcore/util/ZoneInfo;->getRawOffset()I HSPLlibcore/util/ZoneInfo;->hasSameRules(Ljava/util/TimeZone;)Z HSPLlibcore/util/ZoneInfo;->hashCode()I HSPLlibcore/util/ZoneInfo;->inDaylightTime(Ljava/util/Date;)Z @@ -8708,6 +8838,9 @@ HSPLorg/apache/harmony/xml/ExpatParser;->startElement(Ljava/lang/String;Ljava/la HSPLorg/apache/harmony/xml/ExpatReader;-><init>()V HSPLorg/apache/harmony/xml/ExpatReader;->parse(Lorg/xml/sax/InputSource;)V HSPLorg/apache/harmony/xml/ExpatReader;->setContentHandler(Lorg/xml/sax/ContentHandler;)V +HSPLorg/apache/harmony/xml/dom/AttrImpl;->getNodeType()S +HSPLorg/apache/harmony/xml/dom/AttrImpl;->getOwnerElement()Lorg/w3c/dom/Element; +HSPLorg/apache/harmony/xml/dom/AttrImpl;->setValue(Ljava/lang/String;)V HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;Ljava/lang/String;)V HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getData()Ljava/lang/String; HSPLorg/apache/harmony/xml/dom/CharacterDataImpl;->getNodeValue()Ljava/lang/String; @@ -8741,7 +8874,7 @@ HSPLorg/apache/harmony/xml/dom/LeafNodeImpl;->getNextSibling()Lorg/w3c/dom/Node; HSPLorg/apache/harmony/xml/dom/LeafNodeImpl;->isParentOf(Lorg/w3c/dom/Node;)Z HSPLorg/apache/harmony/xml/dom/NodeImpl;-><init>(Lorg/apache/harmony/xml/dom/DocumentImpl;)V HSPLorg/apache/harmony/xml/dom/NodeImpl;->getTextContent()Ljava/lang/String; -HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V+]Lorg/apache/harmony/xml/dom/NodeImpl;Lorg/apache/harmony/xml/dom/AttrImpl;,Lorg/apache/harmony/xml/dom/ElementImpl;]Ljava/lang/String;Ljava/lang/String; +HSPLorg/apache/harmony/xml/dom/NodeImpl;->setName(Lorg/apache/harmony/xml/dom/NodeImpl;Ljava/lang/String;)V HSPLorg/apache/harmony/xml/dom/NodeListImpl;-><init>()V HSPLorg/apache/harmony/xml/dom/NodeListImpl;->add(Lorg/apache/harmony/xml/dom/NodeImpl;)V HSPLorg/apache/harmony/xml/dom/NodeListImpl;->getLength()I @@ -8752,8 +8885,8 @@ HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;-><init>()V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;->newDocumentBuilder()Ljavax/xml/parsers/DocumentBuilder; HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><clinit>()V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;-><init>()V -HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl; -HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V+]Lorg/w3c/dom/Node;Lorg/apache/harmony/xml/dom/ElementImpl;,Lorg/apache/harmony/xml/dom/DocumentImpl;]Lorg/w3c/dom/Element;Lorg/apache/harmony/xml/dom/ElementImpl;]Lcom/android/org/kxml2/io/KXmlParser;Lcom/android/org/kxml2/io/KXmlParser;]Lorg/w3c/dom/Attr;Lorg/apache/harmony/xml/dom/AttrImpl;]Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/apache/harmony/xml/dom/DocumentImpl; +HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V +HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document; HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setCoalescing(Z)V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->setIgnoreComments(Z)V @@ -8841,17 +8974,17 @@ HSPLorg/json/JSONStringer;->key(Ljava/lang/String;)Lorg/json/JSONStringer; HSPLorg/json/JSONStringer;->newline()V HSPLorg/json/JSONStringer;->object()Lorg/json/JSONStringer; HSPLorg/json/JSONStringer;->open(Lorg/json/JSONStringer$Scope;Ljava/lang/String;)Lorg/json/JSONStringer; -HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope; -HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V -HSPLorg/json/JSONStringer;->string(Ljava/lang/String;)V +HSPLorg/json/JSONStringer;->peek()Lorg/json/JSONStringer$Scope;+]Ljava/util/List;Ljava/util/ArrayList; +HSPLorg/json/JSONStringer;->replaceTop(Lorg/json/JSONStringer$Scope;)V+]Ljava/util/List;Ljava/util/ArrayList; +HSPLorg/json/JSONStringer;->string(Ljava/lang/String;)V+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; HSPLorg/json/JSONStringer;->toString()Ljava/lang/String; HSPLorg/json/JSONStringer;->value(Ljava/lang/Object;)Lorg/json/JSONStringer; HSPLorg/json/JSONTokener;-><init>(Ljava/lang/String;)V HSPLorg/json/JSONTokener;->nextCleanInternal()I -HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String; -HSPLorg/json/JSONTokener;->nextToInternal(Ljava/lang/String;)Ljava/lang/String; -HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object; -HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray; +HSPLorg/json/JSONTokener;->nextString(C)Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String; +HSPLorg/json/JSONTokener;->nextToInternal(Ljava/lang/String;)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String; +HSPLorg/json/JSONTokener;->nextValue()Ljava/lang/Object;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener; +HSPLorg/json/JSONTokener;->readArray()Lorg/json/JSONArray;+]Lorg/json/JSONTokener;Lorg/json/JSONTokener;]Lorg/json/JSONArray;Lorg/json/JSONArray; HSPLorg/json/JSONTokener;->readEscapeCharacter()C HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object; HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject; @@ -8891,14 +9024,14 @@ HSPLsun/misc/ASCIICaseInsensitiveComparator;->lowerCaseHashCode(Ljava/lang/Strin HSPLsun/misc/ASCIICaseInsensitiveComparator;->toLower(I)I HSPLsun/misc/Cleaner;-><init>(Ljava/lang/Object;Ljava/lang/Runnable;)V HSPLsun/misc/Cleaner;->add(Lsun/misc/Cleaner;)Lsun/misc/Cleaner; -HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Llibcore/util/NativeAllocationRegistry$CleanerThunk;,Lsun/nio/ch/FileChannelImpl$Unmapper; +HSPLsun/misc/Cleaner;->clean()V+]Ljava/lang/Runnable;Landroid/graphics/HardwareRenderer$DestroyContextRunnable;,Llibcore/util/NativeAllocationRegistry$CleanerThunk; HSPLsun/misc/Cleaner;->create(Ljava/lang/Object;Ljava/lang/Runnable;)Lsun/misc/Cleaner; HSPLsun/misc/Cleaner;->remove(Lsun/misc/Cleaner;)Z HSPLsun/misc/CompoundEnumeration;-><init>([Ljava/util/Enumeration;)V HSPLsun/misc/CompoundEnumeration;->hasMoreElements()Z HSPLsun/misc/CompoundEnumeration;->next()Z HSPLsun/misc/CompoundEnumeration;->nextElement()Ljava/lang/Object; -HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B +HSPLsun/misc/IOUtils;->readFully(Ljava/io/InputStream;IZ)[B+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;,Ljava/io/ByteArrayInputStream; HSPLsun/misc/LRUCache;-><init>(I)V HSPLsun/misc/LRUCache;->forName(Ljava/lang/Object;)Ljava/lang/Object; HSPLsun/misc/LRUCache;->moveToFront([Ljava/lang/Object;I)V @@ -8950,7 +9083,7 @@ HSPLsun/nio/ch/FileChannelImpl$Unmapper;-><init>(JJILjava/io/FileDescriptor;)V HSPLsun/nio/ch/FileChannelImpl$Unmapper;-><init>(JJILjava/io/FileDescriptor;Lsun/nio/ch/FileChannelImpl$Unmapper-IA;)V HSPLsun/nio/ch/FileChannelImpl$Unmapper;->run()V HSPLsun/nio/ch/FileChannelImpl;-><init>(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)V -HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V+]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl; +HSPLsun/nio/ch/FileChannelImpl;->ensureOpen()V HSPLsun/nio/ch/FileChannelImpl;->fileLockTable()Lsun/nio/ch/FileLockTable; HSPLsun/nio/ch/FileChannelImpl;->finalize()V HSPLsun/nio/ch/FileChannelImpl;->force(Z)V @@ -8961,7 +9094,7 @@ HSPLsun/nio/ch/FileChannelImpl;->map(Ljava/nio/channels/FileChannel$MapMode;JJ)L HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZLjava/lang/Object;)Ljava/nio/channels/FileChannel; HSPLsun/nio/ch/FileChannelImpl;->open(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZLjava/lang/Object;)Ljava/nio/channels/FileChannel; HSPLsun/nio/ch/FileChannelImpl;->position()J -HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel;+]Lsun/nio/ch/NativeThreadSet;Lsun/nio/ch/NativeThreadSet;]Lsun/nio/ch/FileChannelImpl;Lsun/nio/ch/FileChannelImpl;]Ldalvik/system/BlockGuard$Policy;Ldalvik/system/BlockGuard$1; +HSPLsun/nio/ch/FileChannelImpl;->position(J)Ljava/nio/channels/FileChannel; HSPLsun/nio/ch/FileChannelImpl;->read(Ljava/nio/ByteBuffer;)I HSPLsun/nio/ch/FileChannelImpl;->release(Lsun/nio/ch/FileLockImpl;)V HSPLsun/nio/ch/FileChannelImpl;->size()J @@ -9119,11 +9252,11 @@ HSPLsun/nio/cs/StreamEncoder;->forOutputStreamWriter(Ljava/io/OutputStream;Ljava HSPLsun/nio/cs/StreamEncoder;->implClose()V HSPLsun/nio/cs/StreamEncoder;->implFlush()V HSPLsun/nio/cs/StreamEncoder;->implFlushBuffer()V -HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V +HSPLsun/nio/cs/StreamEncoder;->implWrite([CII)V+]Ljava/nio/CharBuffer;Ljava/nio/HeapCharBuffer;]Ljava/nio/charset/CharsetEncoder;Lcom/android/icu/charset/CharsetEncoderICU;]Ljava/nio/charset/CoderResult;Ljava/nio/charset/CoderResult; HSPLsun/nio/cs/StreamEncoder;->write(I)V HSPLsun/nio/cs/StreamEncoder;->write(Ljava/lang/String;II)V HSPLsun/nio/cs/StreamEncoder;->write([CII)V -HSPLsun/nio/cs/StreamEncoder;->writeBytes()V +HSPLsun/nio/cs/StreamEncoder;->writeBytes()V+]Ljava/nio/ByteBuffer;Ljava/nio/HeapByteBuffer;]Ljava/io/OutputStream;Ljava/util/logging/FileHandler$MeteredStream;,Ljava/io/FileOutputStream; HSPLsun/nio/cs/ThreadLocalCoders$1;->create(Ljava/lang/Object;)Ljava/lang/Object; HSPLsun/nio/cs/ThreadLocalCoders$1;->hasName(Ljava/lang/Object;Ljava/lang/Object;)Z HSPLsun/nio/cs/ThreadLocalCoders$2;->create(Ljava/lang/Object;)Ljava/lang/Object; @@ -9160,6 +9293,8 @@ HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;-><clinit>()V HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;-><init>(Lsun/nio/fs/UnixDirectoryStream;Ljava/nio/file/DirectoryStream;)V HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->hasNext()Z HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->isSelfOrParent([B)Z +HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/lang/Object; +HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->next()Ljava/nio/file/Path; HSPLsun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator;->readNextEntry()Ljava/nio/file/Path; HSPLsun/nio/fs/UnixDirectoryStream;->-$$Nest$fgetdp(Lsun/nio/fs/UnixDirectoryStream;)J HSPLsun/nio/fs/UnixDirectoryStream;-><init>(Lsun/nio/fs/UnixPath;JLjava/nio/file/DirectoryStream$Filter;)V @@ -9172,6 +9307,7 @@ HSPLsun/nio/fs/UnixDirectoryStream;->readLock()Ljava/util/concurrent/locks/Lock; HSPLsun/nio/fs/UnixDirectoryStream;->writeLock()Ljava/util/concurrent/locks/Lock; HSPLsun/nio/fs/UnixException;-><init>(I)V HSPLsun/nio/fs/UnixException;->errno()I +HSPLsun/nio/fs/UnixException;->fillInStackTrace()Ljava/lang/Throwable; HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;)V HSPLsun/nio/fs/UnixException;->rethrowAsIOException(Lsun/nio/fs/UnixPath;Lsun/nio/fs/UnixPath;)V HSPLsun/nio/fs/UnixException;->translateToIOException(Ljava/lang/String;Ljava/lang/String;)Ljava/io/IOException; @@ -9182,6 +9318,7 @@ HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;-><init>(Lsun/nio/fs HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->creationTime()Ljava/nio/file/attribute/FileTime; HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isDirectory()Z HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isRegularFile()Z +HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->isSymbolicLink()Z HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastAccessTime()Ljava/nio/file/attribute/FileTime; HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->lastModifiedTime()Ljava/nio/file/attribute/FileTime; HSPLsun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes;->size()J @@ -9232,7 +9369,7 @@ HSPLsun/nio/fs/UnixPath;->getParent()Lsun/nio/fs/UnixPath; HSPLsun/nio/fs/UnixPath;->getPathForExceptionMessage()Ljava/lang/String; HSPLsun/nio/fs/UnixPath;->initOffsets()V HSPLsun/nio/fs/UnixPath;->isEmpty()Z -HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder; +HSPLsun/nio/fs/UnixPath;->normalize(Ljava/lang/String;II)Ljava/lang/String; HSPLsun/nio/fs/UnixPath;->normalizeAndCheck(Ljava/lang/String;)Ljava/lang/String; HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Ljava/nio/file/Path; HSPLsun/nio/fs/UnixPath;->resolve(Ljava/nio/file/Path;)Lsun/nio/fs/UnixPath; @@ -9260,11 +9397,11 @@ HSPLsun/security/jca/GetInstance$Instance;-><init>(Ljava/security/Provider;Ljava HSPLsun/security/jca/GetInstance$Instance;-><init>(Ljava/security/Provider;Ljava/lang/Object;Lsun/security/jca/GetInstance$Instance-IA;)V HSPLsun/security/jca/GetInstance$Instance;->toArray()[Ljava/lang/Object; HSPLsun/security/jca/GetInstance;->checkSuperClass(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Class;)V -HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance; +HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList; HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance; HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Lsun/security/jca/GetInstance$Instance; HSPLsun/security/jca/GetInstance;->getInstance(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/security/Provider;)Lsun/security/jca/GetInstance$Instance; -HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance; +HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;)Lsun/security/jca/GetInstance$Instance;+]Ljava/security/Provider$Service;Ljava/security/Provider$Service; HSPLsun/security/jca/GetInstance;->getInstance(Ljava/security/Provider$Service;Ljava/lang/Class;Ljava/lang/Object;)Lsun/security/jca/GetInstance$Instance; HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service; HSPLsun/security/jca/GetInstance;->getService(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service; @@ -9284,6 +9421,7 @@ HSPLsun/security/jca/ProviderList$ServiceList$1;-><init>(Lsun/security/jca/Provi HSPLsun/security/jca/ProviderList$ServiceList$1;->hasNext()Z HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/lang/Object; HSPLsun/security/jca/ProviderList$ServiceList$1;->next()Ljava/security/Provider$Service; +HSPLsun/security/jca/ProviderList$ServiceList;->-$$Nest$mtryGet(Lsun/security/jca/ProviderList$ServiceList;I)Ljava/security/Provider$Service; HSPLsun/security/jca/ProviderList$ServiceList;-><init>(Lsun/security/jca/ProviderList;Ljava/lang/String;Ljava/lang/String;)V HSPLsun/security/jca/ProviderList$ServiceList;->addService(Ljava/security/Provider$Service;)V HSPLsun/security/jca/ProviderList$ServiceList;->iterator()Ljava/util/Iterator; @@ -9295,7 +9433,7 @@ HSPLsun/security/jca/ProviderList;->getJarList([Ljava/lang/String;)Lsun/security HSPLsun/security/jca/ProviderList;->getProvider(I)Ljava/security/Provider; HSPLsun/security/jca/ProviderList;->getProvider(Ljava/lang/String;)Ljava/security/Provider; HSPLsun/security/jca/ProviderList;->getProviderConfig(Ljava/lang/String;)Lsun/security/jca/ProviderConfig; -HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service; +HSPLsun/security/jca/ProviderList;->getService(Ljava/lang/String;Ljava/lang/String;)Ljava/security/Provider$Service;+]Lsun/security/jca/ProviderList;Lsun/security/jca/ProviderList;]Ljava/security/Provider;missing_types HSPLsun/security/jca/ProviderList;->getServices(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List; HSPLsun/security/jca/ProviderList;->insertAt(Lsun/security/jca/ProviderList;Ljava/security/Provider;I)Lsun/security/jca/ProviderList; HSPLsun/security/jca/ProviderList;->loadAll()I @@ -9429,7 +9567,7 @@ HSPLsun/security/provider/certpath/PolicyChecker;->mergePolicyMapping(ILsun/secu HSPLsun/security/provider/certpath/PolicyChecker;->processParents(IZZLsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;Z)Z HSPLsun/security/provider/certpath/PolicyChecker;->processPolicies(ILjava/util/Set;IIIZLsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/x509/X509CertImpl;Z)Lsun/security/provider/certpath/PolicyNodeImpl; HSPLsun/security/provider/certpath/PolicyChecker;->processPolicyMappings(Lsun/security/x509/X509CertImpl;IILsun/security/provider/certpath/PolicyNodeImpl;ZLjava/util/Set;)Lsun/security/provider/certpath/PolicyNodeImpl; -HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V +HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Ljava/lang/String;Ljava/util/Set;ZLjava/util/Set;Z)V+]Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl; HSPLsun/security/provider/certpath/PolicyNodeImpl;-><init>(Lsun/security/provider/certpath/PolicyNodeImpl;Lsun/security/provider/certpath/PolicyNodeImpl;)V HSPLsun/security/provider/certpath/PolicyNodeImpl;->addChild(Lsun/security/provider/certpath/PolicyNodeImpl;)V HSPLsun/security/provider/certpath/PolicyNodeImpl;->copyTree()Lsun/security/provider/certpath/PolicyNodeImpl; @@ -9467,6 +9605,7 @@ HSPLsun/security/util/AlgorithmDecomposer;->decomposeImpl(Ljava/lang/String;)Lja HSPLsun/security/util/AlgorithmDecomposer;->decomposeOneHash(Ljava/lang/String;)Ljava/util/Set; HSPLsun/security/util/AlgorithmDecomposer;->hasLoop(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)V HSPLsun/security/util/BitArray;-><init>(I[B)V +HSPLsun/security/util/BitArray;-><init>(I[BI)V HSPLsun/security/util/BitArray;->get(I)Z HSPLsun/security/util/BitArray;->length()I HSPLsun/security/util/BitArray;->position(I)I @@ -9482,7 +9621,7 @@ HSPLsun/security/util/DerIndefLenConverter;->isIndefinite(I)Z HSPLsun/security/util/DerIndefLenConverter;->isLongForm(I)Z HSPLsun/security/util/DerInputBuffer;-><init>([B)V HSPLsun/security/util/DerInputBuffer;-><init>([BII)V -HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer; +HSPLsun/security/util/DerInputBuffer;->dup()Lsun/security/util/DerInputBuffer;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/lang/Object;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputBuffer;->getBigInteger(IZ)Ljava/math/BigInteger; HSPLsun/security/util/DerInputBuffer;->getBitString()[B HSPLsun/security/util/DerInputBuffer;->getBitString(I)[B @@ -9495,34 +9634,34 @@ HSPLsun/security/util/DerInputBuffer;->getUTCTime(I)Ljava/util/Date; HSPLsun/security/util/DerInputBuffer;->getUnalignedBitString()Lsun/security/util/BitArray; HSPLsun/security/util/DerInputBuffer;->peek()I HSPLsun/security/util/DerInputBuffer;->toByteArray()[B -HSPLsun/security/util/DerInputBuffer;->truncate(I)V -HSPLsun/security/util/DerInputStream;-><init>(Lsun/security/util/DerInputBuffer;)V +HSPLsun/security/util/DerInputBuffer;->truncate(I)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; +HSPLsun/security/util/DerInputStream;-><init>(Lsun/security/util/DerInputBuffer;)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;-><init>([B)V -HSPLsun/security/util/DerInputStream;->available()I +HSPLsun/security/util/DerInputStream;->available()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;->getBigInteger()Ljava/math/BigInteger; -HSPLsun/security/util/DerInputStream;->getByte()I +HSPLsun/security/util/DerInputStream;->getByte()I+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;->getBytes([B)V HSPLsun/security/util/DerInputStream;->getDerValue()Lsun/security/util/DerValue; HSPLsun/security/util/DerInputStream;->getEnumerated()I HSPLsun/security/util/DerInputStream;->getGeneralizedTime()Ljava/util/Date; HSPLsun/security/util/DerInputStream;->getLength()I HSPLsun/security/util/DerInputStream;->getLength(ILjava/io/InputStream;)I -HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I +HSPLsun/security/util/DerInputStream;->getLength(Ljava/io/InputStream;)I+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;->getOID()Lsun/security/util/ObjectIdentifier; HSPLsun/security/util/DerInputStream;->getOctetString()[B HSPLsun/security/util/DerInputStream;->getSequence(I)[Lsun/security/util/DerValue; -HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue; -HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue; +HSPLsun/security/util/DerInputStream;->getSequence(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; +HSPLsun/security/util/DerInputStream;->getSet(I)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/util/DerInputStream;->getSet(IZ)[Lsun/security/util/DerValue; HSPLsun/security/util/DerInputStream;->getSet(IZZ)[Lsun/security/util/DerValue; HSPLsun/security/util/DerInputStream;->getUTCTime()Ljava/util/Date; HSPLsun/security/util/DerInputStream;->getUnalignedBitString()Lsun/security/util/BitArray; -HSPLsun/security/util/DerInputStream;->init([BIIZ)V +HSPLsun/security/util/DerInputStream;->init([BIIZ)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;->mark(I)V HSPLsun/security/util/DerInputStream;->peekByte()I HSPLsun/security/util/DerInputStream;->readVector(I)[Lsun/security/util/DerValue; HSPLsun/security/util/DerInputStream;->readVector(IZ)[Lsun/security/util/DerValue;+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Ljava/util/Vector;Ljava/util/Vector;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; -HSPLsun/security/util/DerInputStream;->reset()V +HSPLsun/security/util/DerInputStream;->reset()V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer; HSPLsun/security/util/DerInputStream;->subStream(IZ)Lsun/security/util/DerInputStream; HSPLsun/security/util/DerInputStream;->toByteArray()[B HSPLsun/security/util/DerOutputStream;-><init>()V @@ -9541,12 +9680,12 @@ HSPLsun/security/util/DerValue;-><init>(Ljava/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>([B)V -HSPLsun/security/util/DerValue;->encode(Lsun/security/util/DerOutputStream;)V +HSPLsun/security/util/DerValue;->encode(Lsun/security/util/DerOutputStream;)V+]Lsun/security/util/DerInputBuffer;Lsun/security/util/DerInputBuffer;]Lsun/security/util/DerOutputStream;Lsun/security/util/DerOutputStream; HSPLsun/security/util/DerValue;->getBigInteger()Ljava/math/BigInteger; HSPLsun/security/util/DerValue;->getBitString()[B HSPLsun/security/util/DerValue;->getBoolean()Z HSPLsun/security/util/DerValue;->getData()Lsun/security/util/DerInputStream; -HSPLsun/security/util/DerValue;->getDataBytes()[B +HSPLsun/security/util/DerValue;->getDataBytes()[B+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/util/DerValue;->getIA5String()Ljava/lang/String; HSPLsun/security/util/DerValue;->getInteger()I HSPLsun/security/util/DerValue;->getOID()Lsun/security/util/ObjectIdentifier; @@ -9555,14 +9694,14 @@ HSPLsun/security/util/DerValue;->getOriginalEncodedForm()[B HSPLsun/security/util/DerValue;->getTag()B HSPLsun/security/util/DerValue;->getUnalignedBitString()Lsun/security/util/BitArray; HSPLsun/security/util/DerValue;->init(BLjava/lang/String;)Lsun/security/util/DerInputStream; -HSPLsun/security/util/DerValue;->init(ZLjava/io/InputStream;)Lsun/security/util/DerInputStream; +HSPLsun/security/util/DerValue;->init(ZLjava/io/InputStream;)Lsun/security/util/DerInputStream;+]Ljava/io/InputStream;Lsun/security/util/DerInputBuffer;,Ljava/io/ByteArrayInputStream; HSPLsun/security/util/DerValue;->isConstructed()Z HSPLsun/security/util/DerValue;->isContextSpecific()Z HSPLsun/security/util/DerValue;->isContextSpecific(B)Z HSPLsun/security/util/DerValue;->isPrintableStringChar(C)Z HSPLsun/security/util/DerValue;->length()I HSPLsun/security/util/DerValue;->resetTag(B)V -HSPLsun/security/util/DerValue;->toByteArray()[B +HSPLsun/security/util/DerValue;->toByteArray()[B+]Lsun/security/util/DerValue;Lsun/security/util/DerValue;]Lsun/security/util/DerOutputStream;Lsun/security/util/DerOutputStream;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/util/DerValue;->toDerInputStream()Lsun/security/util/DerInputStream; HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->getConstraints(Ljava/lang/String;)Ljava/util/Set; HSPLsun/security/util/DisabledAlgorithmConstraints$Constraints;->permits(Ljava/security/Key;)Z @@ -9597,12 +9736,12 @@ HSPLsun/security/util/MemoryCache;->get(Ljava/lang/Object;)Ljava/lang/Object; HSPLsun/security/util/MemoryCache;->newEntry(Ljava/lang/Object;Ljava/lang/Object;JLjava/lang/ref/ReferenceQueue;)Lsun/security/util/MemoryCache$CacheEntry; HSPLsun/security/util/MemoryCache;->put(Ljava/lang/Object;Ljava/lang/Object;)V HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputBuffer;)V -HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputStream;)V +HSPLsun/security/util/ObjectIdentifier;-><init>(Lsun/security/util/DerInputStream;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/util/ObjectIdentifier;->check([B)V HSPLsun/security/util/ObjectIdentifier;->encode(Lsun/security/util/DerOutputStream;)V HSPLsun/security/util/ObjectIdentifier;->equals(Ljava/lang/Object;)Z HSPLsun/security/util/ObjectIdentifier;->hashCode()I -HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String; +HSPLsun/security/util/ObjectIdentifier;->toString()Ljava/lang/String;+]Ljava/lang/StringBuffer;Ljava/lang/StringBuffer; HSPLsun/security/util/SignatureFileVerifier;-><init>(Ljava/util/ArrayList;Lsun/security/util/ManifestDigester;Ljava/lang/String;[B)V HSPLsun/security/util/SignatureFileVerifier;->getDigest(Ljava/lang/String;)Ljava/security/MessageDigest; HSPLsun/security/util/SignatureFileVerifier;->getSigners([Lsun/security/pkcs/SignerInfo;Lsun/security/pkcs/PKCS7;)[Ljava/security/CodeSigner; @@ -9616,16 +9755,16 @@ HSPLsun/security/util/SignatureFileVerifier;->updateSigners([Ljava/security/Code HSPLsun/security/util/SignatureFileVerifier;->verifyManifestHash(Ljava/util/jar/Manifest;Lsun/security/util/ManifestDigester;Ljava/util/List;)Z HSPLsun/security/x509/AVA;-><init>(Ljava/io/Reader;ILjava/util/Map;)V HSPLsun/security/x509/AVA;-><init>(Ljava/io/Reader;Ljava/util/Map;)V -HSPLsun/security/x509/AVA;-><init>(Lsun/security/util/DerValue;)V +HSPLsun/security/x509/AVA;-><init>(Lsun/security/util/DerValue;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/x509/AVA;->derEncode(Ljava/io/OutputStream;)V HSPLsun/security/x509/AVA;->isDerString(Lsun/security/util/DerValue;Z)Z HSPLsun/security/x509/AVA;->isTerminator(II)Z HSPLsun/security/x509/AVA;->parseString(Ljava/io/Reader;IILjava/lang/StringBuilder;)Lsun/security/util/DerValue; HSPLsun/security/x509/AVA;->readChar(Ljava/io/Reader;Ljava/lang/String;)I HSPLsun/security/x509/AVA;->toKeyword(ILjava/util/Map;)Ljava/lang/String; -HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;+]Ljava/lang/String;Ljava/lang/String;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/util/DerValue;Lsun/security/util/DerValue; +HSPLsun/security/x509/AVA;->toRFC2253CanonicalString()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Ljava/lang/String;Ljava/lang/String;]Lsun/security/util/DerValue;Lsun/security/util/DerValue; HSPLsun/security/x509/AVA;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String; -HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String; +HSPLsun/security/x509/AVAKeyword;->getKeyword(Lsun/security/util/ObjectIdentifier;ILjava/util/Map;)Ljava/lang/String;+]Lsun/security/util/ObjectIdentifier;Lsun/security/util/ObjectIdentifier;]Ljava/util/Map;Ljava/util/HashMap;,Ljava/util/Collections$EmptyMap; HSPLsun/security/x509/AVAKeyword;->getOID(Ljava/lang/String;ILjava/util/Map;)Lsun/security/util/ObjectIdentifier; HSPLsun/security/x509/AVAKeyword;->isCompliant(I)Z HSPLsun/security/x509/AccessDescription;-><init>(Lsun/security/util/DerValue;)V @@ -9708,7 +9847,7 @@ HSPLsun/security/x509/PolicyInformation;-><init>(Lsun/security/util/DerValue;)V HSPLsun/security/x509/PolicyInformation;->getPolicyIdentifier()Lsun/security/x509/CertificatePolicyId; HSPLsun/security/x509/PolicyInformation;->getPolicyQualifiers()Ljava/util/Set; HSPLsun/security/x509/RDN;-><init>(Ljava/lang/String;Ljava/util/Map;)V -HSPLsun/security/x509/RDN;-><init>(Lsun/security/util/DerValue;)V +HSPLsun/security/x509/RDN;-><init>(Lsun/security/util/DerValue;)V+]Lsun/security/util/DerValue;Lsun/security/util/DerValue;]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/x509/RDN;->encode(Lsun/security/util/DerOutputStream;)V HSPLsun/security/x509/RDN;->toRFC2253String(Ljava/util/Map;)Ljava/lang/String; HSPLsun/security/x509/RDN;->toRFC2253String(Z)Ljava/lang/String; @@ -9732,20 +9871,20 @@ HSPLsun/security/x509/X500Name;->asX500Name(Ljavax/security/auth/x500/X500Princi HSPLsun/security/x509/X500Name;->asX500Principal()Ljavax/security/auth/x500/X500Principal; HSPLsun/security/x509/X500Name;->checkNoNewLinesNorTabsAtBeginningOfDN(Ljava/lang/String;)V HSPLsun/security/x509/X500Name;->countQuotes(Ljava/lang/String;II)I -HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z +HSPLsun/security/x509/X500Name;->equals(Ljava/lang/Object;)Z+]Lsun/security/x509/X500Name;Lsun/security/x509/X500Name; HSPLsun/security/x509/X500Name;->escaped(IILjava/lang/String;)Z HSPLsun/security/x509/X500Name;->generateRFC2253DN(Ljava/util/Map;)Ljava/lang/String; HSPLsun/security/x509/X500Name;->getEncoded()[B HSPLsun/security/x509/X500Name;->getEncodedInternal()[B -HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String; +HSPLsun/security/x509/X500Name;->getRFC2253CanonicalName()Ljava/lang/String;+]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Lsun/security/x509/RDN;Lsun/security/x509/RDN; HSPLsun/security/x509/X500Name;->getRFC2253Name()Ljava/lang/String; HSPLsun/security/x509/X500Name;->getRFC2253Name(Ljava/util/Map;)Ljava/lang/String; HSPLsun/security/x509/X500Name;->hashCode()I HSPLsun/security/x509/X500Name;->intern(Lsun/security/util/ObjectIdentifier;)Lsun/security/util/ObjectIdentifier; HSPLsun/security/x509/X500Name;->isEmpty()Z -HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V +HSPLsun/security/x509/X500Name;->parseDER(Lsun/security/util/DerInputStream;)V+]Lsun/security/util/DerInputStream;Lsun/security/util/DerInputStream; HSPLsun/security/x509/X500Name;->parseDN(Ljava/lang/String;Ljava/util/Map;)V -HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V +HSPLsun/security/x509/X509AttributeName;-><init>(Ljava/lang/String;)V+]Ljava/lang/String;Ljava/lang/String; HSPLsun/security/x509/X509AttributeName;->getPrefix()Ljava/lang/String; HSPLsun/security/x509/X509AttributeName;->getSuffix()Ljava/lang/String; HSPLsun/security/x509/X509CertImpl;-><init>([B)V @@ -9805,7 +9944,7 @@ HSPLsun/util/calendar/BaseCalendar;-><init>()V HSPLsun/util/calendar/BaseCalendar;->getCalendarDateFromFixedDate(Lsun/util/calendar/CalendarDate;J)V+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian; HSPLsun/util/calendar/BaseCalendar;->getDayOfWeekFromFixedDate(J)I HSPLsun/util/calendar/BaseCalendar;->getDayOfYear(III)J -HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J +HSPLsun/util/calendar/BaseCalendar;->getFixedDate(IIILsun/util/calendar/BaseCalendar$Date;)J+]Lsun/util/calendar/BaseCalendar$Date;Lsun/util/calendar/Gregorian$Date;]Lsun/util/calendar/BaseCalendar;Lsun/util/calendar/Gregorian; HSPLsun/util/calendar/BaseCalendar;->getFixedDate(Lsun/util/calendar/CalendarDate;)J HSPLsun/util/calendar/BaseCalendar;->getGregorianYearFromFixedDate(J)I HSPLsun/util/calendar/BaseCalendar;->isLeapYear(I)Z @@ -9814,7 +9953,7 @@ HSPLsun/util/calendar/BaseCalendar;->normalizeMonth(Lsun/util/calendar/CalendarD HSPLsun/util/calendar/CalendarDate;-><init>(Ljava/util/TimeZone;)V HSPLsun/util/calendar/CalendarDate;->clone()Ljava/lang/Object; HSPLsun/util/calendar/CalendarDate;->getDayOfMonth()I -HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I +HSPLsun/util/calendar/CalendarDate;->getDayOfWeek()I+]Lsun/util/calendar/CalendarDate;Lsun/util/calendar/Gregorian$Date; HSPLsun/util/calendar/CalendarDate;->getEra()Lsun/util/calendar/Era; HSPLsun/util/calendar/CalendarDate;->getHours()I HSPLsun/util/calendar/CalendarDate;->getMillis()I @@ -9856,7 +9995,7 @@ HSPLsun/util/calendar/CalendarUtils;->mod(II)I HSPLsun/util/calendar/CalendarUtils;->mod(JJ)J HSPLsun/util/calendar/CalendarUtils;->sprintf0d(Ljava/lang/StringBuilder;II)Ljava/lang/StringBuilder; HSPLsun/util/calendar/Gregorian$Date;-><init>(Ljava/util/TimeZone;)V -HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I +HSPLsun/util/calendar/Gregorian$Date;->getNormalizedYear()I+]Lsun/util/calendar/Gregorian$Date;Lsun/util/calendar/Gregorian$Date; HSPLsun/util/calendar/Gregorian$Date;->setNormalizedYear(I)V HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/CalendarDate; HSPLsun/util/calendar/Gregorian;->getCalendarDate(JLjava/util/TimeZone;)Lsun/util/calendar/Gregorian$Date; @@ -9878,20 +10017,19 @@ HSPLsun/util/locale/BaseLocale$Cache;->createObject(Ljava/lang/Object;)Ljava/lan HSPLsun/util/locale/BaseLocale$Cache;->createObject(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale; HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object; HSPLsun/util/locale/BaseLocale$Cache;->normalizeKey(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key; -HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetlang(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference; -HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetregn(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference; -HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetscrt(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference; -HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$fgetvart(Lsun/util/locale/BaseLocale$Key;)Ljava/lang/ref/SoftReference; -HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V +HSPLsun/util/locale/BaseLocale$Key;->-$$Nest$mgetBaseLocale(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale; HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V -HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z+]Ljava/lang/ref/SoftReference;Ljava/lang/ref/SoftReference; +HSPLsun/util/locale/BaseLocale$Key;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale$Key-IA;)V +HSPLsun/util/locale/BaseLocale$Key;->equals(Ljava/lang/Object;)Z +HSPLsun/util/locale/BaseLocale$Key;->getBaseLocale()Lsun/util/locale/BaseLocale; HSPLsun/util/locale/BaseLocale$Key;->hashCode()I +HSPLsun/util/locale/BaseLocale$Key;->hashCode(Lsun/util/locale/BaseLocale;)I HSPLsun/util/locale/BaseLocale$Key;->normalize(Lsun/util/locale/BaseLocale$Key;)Lsun/util/locale/BaseLocale$Key; -HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V -HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lsun/util/locale/BaseLocale-IA;)V +HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V+]Ljava/lang/String;Ljava/lang/String; +HSPLsun/util/locale/BaseLocale;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLsun/util/locale/BaseLocale-IA;)V HSPLsun/util/locale/BaseLocale;->cleanCache()V HSPLsun/util/locale/BaseLocale;->equals(Ljava/lang/Object;)Z -HSPLsun/util/locale/BaseLocale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/BaseLocale;+]Lsun/util/locale/BaseLocale$Cache;Lsun/util/locale/BaseLocale$Cache; +HSPLsun/util/locale/BaseLocale;->getInstance(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lsun/util/locale/BaseLocale; HSPLsun/util/locale/BaseLocale;->getLanguage()Ljava/lang/String; HSPLsun/util/locale/BaseLocale;->getRegion()Ljava/lang/String; HSPLsun/util/locale/BaseLocale;->getScript()Ljava/lang/String; @@ -9936,8 +10074,8 @@ HSPLsun/util/locale/LanguageTag;->parseScript(Lsun/util/locale/StringTokenIterat HSPLsun/util/locale/LanguageTag;->parseVariants(Lsun/util/locale/StringTokenIterator;Lsun/util/locale/ParseStatus;)Z HSPLsun/util/locale/LocaleObjectCache$CacheEntry;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V HSPLsun/util/locale/LocaleObjectCache$CacheEntry;->getKey()Ljava/lang/Object; -HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V+]Ljava/lang/ref/ReferenceQueue;Ljava/lang/ref/ReferenceQueue; -HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Ljava/util/Locale$Cache;,Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry; +HSPLsun/util/locale/LocaleObjectCache;->cleanStaleEntries()V +HSPLsun/util/locale/LocaleObjectCache;->get(Ljava/lang/Object;)Ljava/lang/Object;+]Ljava/util/concurrent/ConcurrentMap;Ljava/util/concurrent/ConcurrentHashMap;]Lsun/util/locale/LocaleObjectCache;Lsun/util/locale/BaseLocale$Cache;]Lsun/util/locale/LocaleObjectCache$CacheEntry;Lsun/util/locale/LocaleObjectCache$CacheEntry; HSPLsun/util/locale/LocaleObjectCache;->normalizeKey(Ljava/lang/Object;)Ljava/lang/Object; HSPLsun/util/locale/LocaleUtils;->caseIgnoreMatch(Ljava/lang/String;Ljava/lang/String;)Z HSPLsun/util/locale/LocaleUtils;->isAlpha(C)Z @@ -9967,6 +10105,7 @@ HSPLsun/util/locale/StringTokenIterator;->isDone()Z HSPLsun/util/locale/StringTokenIterator;->next()Ljava/lang/String; HSPLsun/util/locale/StringTokenIterator;->nextDelimiter(I)I HSPLsun/util/locale/StringTokenIterator;->setStart(I)Lsun/util/locale/StringTokenIterator; +HSPLsun/util/locale/provider/CalendarDataUtility;->retrieveFirstDayOfWeek(Ljava/util/Locale;I)I HSPLsun/util/logging/LoggingSupport$2;-><init>()V HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/Object; HSPLsun/util/logging/LoggingSupport$2;->run()Ljava/lang/String; @@ -10044,6 +10183,7 @@ Lcom/android/okhttp/HttpUrl$Builder; Lcom/android/okhttp/HttpUrl; Lcom/android/okhttp/HttpsHandler; Lcom/android/okhttp/Interceptor$Chain; +Lcom/android/okhttp/MediaType; Lcom/android/okhttp/OkCacheContainer; Lcom/android/okhttp/OkHttpClient$1; Lcom/android/okhttp/OkHttpClient; @@ -10366,7 +10506,10 @@ Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DES$Mappings; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DES; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede$Mappings; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/DESede; +Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2; +Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$BasePBKDF2WithHmacSHA1; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$Mappings; +Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2$PBKDF2WithHmacSHA1UTF8; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12$Mappings; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12; @@ -10388,6 +10531,7 @@ Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher$Inva Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/BlockCipherProvider; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/ClassUtil; +Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil$2; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE$Util; Lcom/android/org/bouncycastle/jcajce/provider/symmetric/util/PBE; @@ -10396,6 +10540,7 @@ Lcom/android/org/bouncycastle/jcajce/provider/util/AsymmetricAlgorithmProvider; Lcom/android/org/bouncycastle/jcajce/provider/util/AsymmetricKeyInfoConverter; Lcom/android/org/bouncycastle/jcajce/provider/util/DigestFactory; Lcom/android/org/bouncycastle/jcajce/spec/AEADParameterSpec; +Lcom/android/org/bouncycastle/jcajce/spec/PBKDF2KeySpec; Lcom/android/org/bouncycastle/jcajce/util/BCJcaJceHelper; Lcom/android/org/bouncycastle/jcajce/util/DefaultJcaJceHelper; Lcom/android/org/bouncycastle/jcajce/util/JcaJceHelper; @@ -10404,6 +10549,7 @@ Lcom/android/org/bouncycastle/jce/X509Principal; Lcom/android/org/bouncycastle/jce/interfaces/BCKeyStore; Lcom/android/org/bouncycastle/jce/interfaces/PKCS12BagAttributeCarrier; Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$1; +Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider$PrivateProvider; Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProvider; Lcom/android/org/bouncycastle/jce/provider/BouncyCastleProviderConfiguration; Lcom/android/org/bouncycastle/jce/provider/CertStoreCollectionSpi; @@ -10469,13 +10615,16 @@ Ldalvik/system/SocketTagger$1; Ldalvik/system/SocketTagger; Ldalvik/system/VMDebug; Ldalvik/system/VMRuntime$HiddenApiUsageLogger; +Ldalvik/system/VMRuntime$SdkVersionContainer; Ldalvik/system/VMRuntime; Ldalvik/system/VMStack; +Ldalvik/system/ZipPathValidator$1; +Ldalvik/system/ZipPathValidator$Callback; +Ldalvik/system/ZipPathValidator; Ldalvik/system/ZygoteHooks; Ljava/awt/font/NumericShaper; Ljava/awt/font/TextAttribute; Ljava/io/Bits; -Ljava/io/BufferedInputStream$$ExternalSyntheticBackportWithForwarding0; Ljava/io/BufferedInputStream; Ljava/io/BufferedOutputStream; Ljava/io/BufferedReader; @@ -10586,6 +10735,7 @@ Ljava/io/StreamCorruptedException; Ljava/io/StringBufferInputStream; Ljava/io/StringReader; Ljava/io/StringWriter; +Ljava/io/SyncFailedException; Ljava/io/UTFDataFormatException; Ljava/io/UncheckedIOException; Ljava/io/UnixFileSystem; @@ -10593,6 +10743,8 @@ Ljava/io/UnsupportedEncodingException; Ljava/io/WriteAbortedException; Ljava/io/Writer; Ljava/lang/AbstractMethodError; +Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda0; +Ljava/lang/AbstractStringBuilder$$ExternalSyntheticLambda1; Ljava/lang/AbstractStringBuilder; Ljava/lang/AndroidHardcodedSystemProperties; Ljava/lang/Appendable; @@ -10623,6 +10775,8 @@ Ljava/lang/ClassFormatError; Ljava/lang/ClassLoader$SystemClassLoader; Ljava/lang/ClassLoader; Ljava/lang/ClassNotFoundException; +Ljava/lang/ClassValue$Entry; +Ljava/lang/ClassValue; Ljava/lang/CloneNotSupportedException; Ljava/lang/Cloneable; Ljava/lang/Comparable; @@ -10653,8 +10807,6 @@ Ljava/lang/IndexOutOfBoundsException; Ljava/lang/InheritableThreadLocal; Ljava/lang/InstantiationError; Ljava/lang/InstantiationException; -Ljava/lang/Integer$$ExternalSyntheticBackport0; -Ljava/lang/Integer$$ExternalSyntheticBackport1; Ljava/lang/Integer$IntegerCache; Ljava/lang/Integer; Ljava/lang/InternalError; @@ -10700,9 +10852,19 @@ Ljava/lang/SecurityException; Ljava/lang/SecurityManager; Ljava/lang/Short$ShortCache; Ljava/lang/Short; +Ljava/lang/StackFrameInfo; Ljava/lang/StackOverflowError; +Ljava/lang/StackStreamFactory$AbstractStackWalker; +Ljava/lang/StackStreamFactory; Ljava/lang/StackTraceElement; +Ljava/lang/StackWalker$Option; +Ljava/lang/StackWalker$StackFrame; +Ljava/lang/StackWalker; Ljava/lang/StrictMath; +Ljava/lang/String$$ExternalSyntheticLambda0; +Ljava/lang/String$$ExternalSyntheticLambda1; +Ljava/lang/String$$ExternalSyntheticLambda2; +Ljava/lang/String$$ExternalSyntheticLambda3; Ljava/lang/String$CaseInsensitiveComparator-IA; Ljava/lang/String$CaseInsensitiveComparator; Ljava/lang/String; @@ -10710,8 +10872,13 @@ Ljava/lang/StringBuffer; Ljava/lang/StringBuilder; Ljava/lang/StringFactory; Ljava/lang/StringIndexOutOfBoundsException; +Ljava/lang/StringLatin1$CharsSpliterator; +Ljava/lang/StringLatin1$LinesSpliterator; +Ljava/lang/StringLatin1; Ljava/lang/StringUTF16$CharsSpliterator; +Ljava/lang/StringUTF16$CharsSpliteratorForString; Ljava/lang/StringUTF16$CodePointsSpliterator; +Ljava/lang/StringUTF16$CodePointsSpliteratorForString; Ljava/lang/StringUTF16; Ljava/lang/System$PropertiesWithNonOverrideableDefaults; Ljava/lang/System; @@ -10725,6 +10892,7 @@ Ljava/lang/ThreadDeath; Ljava/lang/ThreadGroup; Ljava/lang/ThreadLocal$SuppliedThreadLocal; Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; +Ljava/lang/ThreadLocal$ThreadLocalMap-IA; Ljava/lang/ThreadLocal$ThreadLocalMap; Ljava/lang/ThreadLocal; Ljava/lang/Throwable$PrintStreamOrWriter; @@ -10742,6 +10910,7 @@ Ljava/lang/UNIXProcess$ProcessReaperThreadFactory$1; Ljava/lang/UNIXProcess$ProcessReaperThreadFactory; Ljava/lang/UNIXProcess; Ljava/lang/UnsatisfiedLinkError; +Ljava/lang/UnsupportedClassVersionError; Ljava/lang/UnsupportedOperationException; Ljava/lang/VMClassLoader; Ljava/lang/VerifyError; @@ -10819,10 +10988,14 @@ Ljava/lang/invoke/Transformers$ReferenceArrayElementGetter; Ljava/lang/invoke/Transformers$ReferenceArrayElementSetter; Ljava/lang/invoke/Transformers$ReferenceIdentity; Ljava/lang/invoke/Transformers$Spreader; +Ljava/lang/invoke/Transformers$TableSwitch; Ljava/lang/invoke/Transformers$Transformer; Ljava/lang/invoke/Transformers$TryFinally; Ljava/lang/invoke/Transformers$VarargsCollector; Ljava/lang/invoke/Transformers$ZeroValue; +Ljava/lang/invoke/TypeDescriptor$OfField; +Ljava/lang/invoke/TypeDescriptor$OfMethod; +Ljava/lang/invoke/TypeDescriptor; Ljava/lang/invoke/VarHandle$1; Ljava/lang/invoke/VarHandle$AccessMode; Ljava/lang/invoke/VarHandle$AccessType; @@ -10956,6 +11129,7 @@ Ljava/net/Proxy$Type; Ljava/net/Proxy; Ljava/net/ProxySelector; Ljava/net/ResponseCache; +Ljava/net/ServerSocket$1; Ljava/net/ServerSocket; Ljava/net/Socket$1; Ljava/net/Socket$2; @@ -10989,6 +11163,7 @@ Ljava/net/UnknownHostException; Ljava/net/UnknownServiceException; Ljava/nio/Bits; Ljava/nio/Buffer; +Ljava/nio/BufferMismatch; Ljava/nio/BufferOverflowException; Ljava/nio/BufferUnderflowException; Ljava/nio/ByteBuffer; @@ -11062,6 +11237,7 @@ Ljava/nio/channels/spi/AbstractSelectableChannel; Ljava/nio/channels/spi/AbstractSelectionKey; Ljava/nio/channels/spi/AbstractSelector$1; Ljava/nio/channels/spi/AbstractSelector; +Ljava/nio/channels/spi/AsynchronousChannelProvider; Ljava/nio/channels/spi/SelectorProvider$1; Ljava/nio/channels/spi/SelectorProvider; Ljava/nio/charset/CharacterCodingException; @@ -11069,8 +11245,6 @@ Ljava/nio/charset/Charset; Ljava/nio/charset/CharsetDecoder; Ljava/nio/charset/CharsetEncoder; Ljava/nio/charset/CoderMalfunctionError; -Ljava/nio/charset/CoderResult$1; -Ljava/nio/charset/CoderResult$2; Ljava/nio/charset/CoderResult$Cache; Ljava/nio/charset/CoderResult; Ljava/nio/charset/CodingErrorAction; @@ -11091,6 +11265,8 @@ Ljava/nio/file/FileSystemException; Ljava/nio/file/FileSystems$DefaultFileSystemHolder$1; Ljava/nio/file/FileSystems$DefaultFileSystemHolder; Ljava/nio/file/FileSystems; +Ljava/nio/file/FileVisitResult; +Ljava/nio/file/FileVisitor; Ljava/nio/file/Files$AcceptAllFilter; Ljava/nio/file/Files; Ljava/nio/file/InvalidPathException; @@ -11102,6 +11278,7 @@ Ljava/nio/file/Path; Ljava/nio/file/Paths; Ljava/nio/file/ProviderMismatchException; Ljava/nio/file/SecureDirectoryStream; +Ljava/nio/file/SimpleFileVisitor; Ljava/nio/file/StandardCopyOption; Ljava/nio/file/StandardOpenOption; Ljava/nio/file/Watchable; @@ -11320,6 +11497,7 @@ Ljava/time/DayOfWeek; Ljava/time/Duration; Ljava/time/Instant$1; Ljava/time/Instant; +Ljava/time/InstantSource; Ljava/time/LocalDate$1; Ljava/time/LocalDate; Ljava/time/LocalDateTime; @@ -11349,10 +11527,11 @@ Ljava/time/format/DateTimeFormatter; Ljava/time/format/DateTimeFormatterBuilder$$ExternalSyntheticLambda0; Ljava/time/format/DateTimeFormatterBuilder$1; Ljava/time/format/DateTimeFormatterBuilder$2; -Ljava/time/format/DateTimeFormatterBuilder$3; Ljava/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser; Ljava/time/format/DateTimeFormatterBuilder$CompositePrinterParser; Ljava/time/format/DateTimeFormatterBuilder$DateTimePrinterParser; +Ljava/time/format/DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0; +Ljava/time/format/DateTimeFormatterBuilder$DayPeriod; Ljava/time/format/DateTimeFormatterBuilder$FractionPrinterParser; Ljava/time/format/DateTimeFormatterBuilder$InstantPrinterParser; Ljava/time/format/DateTimeFormatterBuilder$NumberPrinterParser; @@ -11437,14 +11616,15 @@ Ljava/util/AbstractMap; Ljava/util/AbstractQueue; Ljava/util/AbstractSequentialList; Ljava/util/AbstractSet; +Ljava/util/ArrayDeque$$ExternalSyntheticLambda1; Ljava/util/ArrayDeque$DeqIterator; Ljava/util/ArrayDeque$DescendingIterator; Ljava/util/ArrayDeque; Ljava/util/ArrayList$ArrayListSpliterator; -Ljava/util/ArrayList$Itr-IA; Ljava/util/ArrayList$Itr; Ljava/util/ArrayList$ListItr; Ljava/util/ArrayList$SubList$1; +Ljava/util/ArrayList$SubList$2; Ljava/util/ArrayList$SubList; Ljava/util/ArrayList; Ljava/util/ArrayPrefixHelpers$CumulateTask; @@ -11459,18 +11639,12 @@ Ljava/util/Arrays$ArrayItr; Ljava/util/Arrays$ArrayList; Ljava/util/Arrays$NaturalOrder; Ljava/util/Arrays; -Ljava/util/ArraysParallelSortHelpers$FJByte$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJChar$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJDouble$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJFloat$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJInt$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJLong$Sorter; Ljava/util/ArraysParallelSortHelpers$FJObject$Sorter; -Ljava/util/ArraysParallelSortHelpers$FJShort$Sorter; Ljava/util/Base64$Decoder; Ljava/util/Base64$Encoder; Ljava/util/Base64; Ljava/util/BitSet; +Ljava/util/Calendar$$ExternalSyntheticLambda0; Ljava/util/Calendar$Builder; Ljava/util/Calendar; Ljava/util/Collection; @@ -11545,6 +11719,7 @@ Ljava/util/Currency; Ljava/util/Date; Ljava/util/Deque; Ljava/util/Dictionary; +Ljava/util/DualPivotQuicksort$Sorter; Ljava/util/DualPivotQuicksort; Ljava/util/DuplicateFormatFlagsException; Ljava/util/EmptyStackException; @@ -11619,13 +11794,13 @@ Ljava/util/ImmutableCollections$AbstractImmutableSet; Ljava/util/ImmutableCollections$List12; Ljava/util/ImmutableCollections$ListItr; Ljava/util/ImmutableCollections$ListN; -Ljava/util/ImmutableCollections$Map0; Ljava/util/ImmutableCollections$Map1; +Ljava/util/ImmutableCollections$MapN$1; +Ljava/util/ImmutableCollections$MapN$MapNIterator; Ljava/util/ImmutableCollections$MapN; -Ljava/util/ImmutableCollections$Set0; -Ljava/util/ImmutableCollections$Set1; -Ljava/util/ImmutableCollections$Set2; +Ljava/util/ImmutableCollections$Set12; Ljava/util/ImmutableCollections$SetN; +Ljava/util/ImmutableCollections$SubList; Ljava/util/ImmutableCollections; Ljava/util/InputMismatchException; Ljava/util/Iterator; @@ -11654,6 +11829,10 @@ Ljava/util/Locale$Builder; Ljava/util/Locale$Cache; Ljava/util/Locale$Category; Ljava/util/Locale$FilteringMode; +Ljava/util/Locale$IsoCountryCode$1; +Ljava/util/Locale$IsoCountryCode$2; +Ljava/util/Locale$IsoCountryCode$3; +Ljava/util/Locale$IsoCountryCode; Ljava/util/Locale$LanguageRange; Ljava/util/Locale$LocaleKey; Ljava/util/Locale$NoImagePreloadHolder; @@ -11690,14 +11869,16 @@ Ljava/util/ResourceBundle$1; Ljava/util/ResourceBundle$BundleReference; Ljava/util/ResourceBundle$CacheKey; Ljava/util/ResourceBundle$CacheKeyReference; +Ljava/util/ResourceBundle$Control$$ExternalSyntheticLambda0; Ljava/util/ResourceBundle$Control$1; Ljava/util/ResourceBundle$Control$CandidateListCache; Ljava/util/ResourceBundle$Control; -Ljava/util/ResourceBundle$LoaderReference; +Ljava/util/ResourceBundle$KeyElementReference; +Ljava/util/ResourceBundle$RBClassLoader$1; Ljava/util/ResourceBundle$RBClassLoader; Ljava/util/ResourceBundle$SingleFormatControl; Ljava/util/ResourceBundle; -Ljava/util/Scanner$1; +Ljava/util/Scanner$PatternLRUCache; Ljava/util/Scanner; Ljava/util/ServiceConfigurationError; Ljava/util/ServiceLoader$1; @@ -11879,12 +12060,13 @@ Ljava/util/concurrent/Executors$FinalizableDelegatedExecutorService; Ljava/util/concurrent/Executors$RunnableAdapter; Ljava/util/concurrent/Executors; Ljava/util/concurrent/ForkJoinPool$1; +Ljava/util/concurrent/ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory; Ljava/util/concurrent/ForkJoinPool$DefaultForkJoinWorkerThreadFactory; Ljava/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory; Ljava/util/concurrent/ForkJoinPool$ManagedBlocker; Ljava/util/concurrent/ForkJoinPool$WorkQueue; Ljava/util/concurrent/ForkJoinPool; -Ljava/util/concurrent/ForkJoinTask$ExceptionNode; +Ljava/util/concurrent/ForkJoinTask$Aux; Ljava/util/concurrent/ForkJoinTask; Ljava/util/concurrent/ForkJoinWorkerThread; Ljava/util/concurrent/Future; @@ -11897,6 +12079,7 @@ Ljava/util/concurrent/LinkedBlockingDeque; Ljava/util/concurrent/LinkedBlockingQueue$Itr; Ljava/util/concurrent/LinkedBlockingQueue$Node; Ljava/util/concurrent/LinkedBlockingQueue; +Ljava/util/concurrent/Phaser; Ljava/util/concurrent/PriorityBlockingQueue; Ljava/util/concurrent/RejectedExecutionException; Ljava/util/concurrent/RejectedExecutionHandler; @@ -11946,8 +12129,11 @@ Ljava/util/concurrent/atomic/Striped64$1; Ljava/util/concurrent/atomic/Striped64$Cell; Ljava/util/concurrent/atomic/Striped64; Ljava/util/concurrent/locks/AbstractOwnableSynchronizer; +Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionNode; Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject; +Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$ExclusiveNode; Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$Node; +Ljava/util/concurrent/locks/AbstractQueuedSynchronizer$SharedNode; Ljava/util/concurrent/locks/AbstractQueuedSynchronizer; Ljava/util/concurrent/locks/Condition; Ljava/util/concurrent/locks/Lock; @@ -11994,9 +12180,13 @@ Ljava/util/function/IntToLongFunction; Ljava/util/function/IntUnaryOperator; Ljava/util/function/LongBinaryOperator; Ljava/util/function/LongConsumer; +Ljava/util/function/LongFunction; Ljava/util/function/LongPredicate; Ljava/util/function/LongSupplier; Ljava/util/function/LongUnaryOperator; +Ljava/util/function/ObjDoubleConsumer; +Ljava/util/function/ObjIntConsumer; +Ljava/util/function/ObjLongConsumer; Ljava/util/function/Predicate; Ljava/util/function/Supplier; Ljava/util/function/ToDoubleBiFunction; @@ -12073,6 +12263,7 @@ Ljava/util/stream/BaseStream; Ljava/util/stream/Collector$Characteristics; Ljava/util/stream/Collector; Ljava/util/stream/Collectors$$ExternalSyntheticLambda0; +Ljava/util/stream/Collectors$$ExternalSyntheticLambda13; Ljava/util/stream/Collectors$$ExternalSyntheticLambda15; Ljava/util/stream/Collectors$$ExternalSyntheticLambda1; Ljava/util/stream/Collectors$$ExternalSyntheticLambda20; @@ -12262,6 +12453,7 @@ Ljava/util/stream/Tripwire; Ljava/util/zip/Adler32; Ljava/util/zip/CRC32; Ljava/util/zip/CheckedInputStream; +Ljava/util/zip/Checksum$1; Ljava/util/zip/Checksum; Ljava/util/zip/DataFormatException; Ljava/util/zip/Deflater; @@ -12372,6 +12564,7 @@ Ljavax/net/ssl/SSLSessionContext; Ljavax/net/ssl/SSLSocket; Ljavax/net/ssl/SSLSocketFactory$1; Ljavax/net/ssl/SSLSocketFactory; +Ljavax/net/ssl/StandardConstants; Ljavax/net/ssl/TrustManager; Ljavax/net/ssl/TrustManagerFactory$1; Ljavax/net/ssl/TrustManagerFactory; @@ -12391,6 +12584,7 @@ Ljavax/security/cert/CertificateEncodingException; Ljavax/security/cert/CertificateException; Ljavax/security/cert/X509Certificate$1; Ljavax/security/cert/X509Certificate; +Ljavax/xml/datatype/DatatypeConfigurationException; Ljavax/xml/datatype/DatatypeConstants$Field; Ljavax/xml/datatype/DatatypeConstants; Ljavax/xml/datatype/Duration; @@ -12407,6 +12601,7 @@ Ljdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter; Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer; Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter; Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer; +Ljdk/internal/math/FloatingDecimal$HexFloatPattern; Ljdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer; Ljdk/internal/math/FloatingDecimal; Ljdk/internal/math/FormattedFloatingDecimal$1; @@ -12415,11 +12610,15 @@ Ljdk/internal/math/FormattedFloatingDecimal$Form; Ljdk/internal/math/FormattedFloatingDecimal; Ljdk/internal/misc/JavaObjectInputStreamAccess; Ljdk/internal/misc/SharedSecrets; +Ljdk/internal/misc/TerminatingThreadLocal$1; +Ljdk/internal/misc/TerminatingThreadLocal; Ljdk/internal/misc/Unsafe; +Ljdk/internal/misc/UnsafeConstants; Ljdk/internal/misc/VM; Ljdk/internal/reflect/Reflection; Ljdk/internal/util/ArraysSupport; Ljdk/internal/util/Preconditions; +Ljdk/internal/util/StaticProperty; Llibcore/content/type/MimeMap$$ExternalSyntheticLambda0; Llibcore/content/type/MimeMap$Builder$Element; Llibcore/content/type/MimeMap$Builder; @@ -12427,7 +12626,6 @@ Llibcore/content/type/MimeMap$MemoizingSupplier; Llibcore/content/type/MimeMap; Llibcore/icu/CollationKeyICU; Llibcore/icu/DateIntervalFormat; -Llibcore/icu/DateUtilsBridge; Llibcore/icu/DecimalFormatData; Llibcore/icu/ICU; Llibcore/icu/LocaleData; @@ -12590,7 +12788,6 @@ Lsun/misc/IOUtils; Lsun/misc/JavaIOFileDescriptorAccess; Lsun/misc/LRUCache; Lsun/misc/SharedSecrets; -Lsun/misc/Unsafe$$ExternalSyntheticBackportWithForwarding0; Lsun/misc/Unsafe; Lsun/misc/VM; Lsun/misc/Version; @@ -12630,6 +12827,7 @@ Lsun/nio/ch/FileLockTable; Lsun/nio/ch/IOStatus; Lsun/nio/ch/IOUtil; Lsun/nio/ch/Interruptible; +Lsun/nio/ch/LinuxAsynchronousChannelProvider; Lsun/nio/ch/NativeDispatcher; Lsun/nio/ch/NativeObject; Lsun/nio/ch/NativeThread; @@ -12721,6 +12919,7 @@ Lsun/security/jca/ProviderList; Lsun/security/jca/Providers; Lsun/security/jca/ServiceId; Lsun/security/pkcs/ContentInfo; +Lsun/security/pkcs/ESSCertId; Lsun/security/pkcs/PKCS7$VerbatimX509Certificate; Lsun/security/pkcs/PKCS7$WrappedX509Certificate; Lsun/security/pkcs/PKCS7; @@ -12780,6 +12979,7 @@ Lsun/security/util/DisabledAlgorithmConstraints$Constraint; Lsun/security/util/DisabledAlgorithmConstraints$Constraints; Lsun/security/util/DisabledAlgorithmConstraints$KeySizeConstraint; Lsun/security/util/DisabledAlgorithmConstraints; +Lsun/security/util/FilePaths; Lsun/security/util/KeyUtil; Lsun/security/util/Length; Lsun/security/util/ManifestDigester$Entry; @@ -12792,6 +12992,7 @@ Lsun/security/util/MemoryCache$HardCacheEntry; Lsun/security/util/MemoryCache$SoftCacheEntry; Lsun/security/util/MemoryCache; Lsun/security/util/ObjectIdentifier; +Lsun/security/util/PropertyExpander; Lsun/security/util/Resources; Lsun/security/util/ResourcesMgr$1; Lsun/security/util/ResourcesMgr; @@ -12869,6 +13070,7 @@ Lsun/util/calendar/AbstractCalendar; Lsun/util/calendar/BaseCalendar$Date; Lsun/util/calendar/BaseCalendar; Lsun/util/calendar/CalendarDate; +Lsun/util/calendar/CalendarSystem$GregorianHolder; Lsun/util/calendar/CalendarSystem; Lsun/util/calendar/CalendarUtils; Lsun/util/calendar/Era; @@ -12894,6 +13096,7 @@ Lsun/util/locale/LocaleUtils; Lsun/util/locale/ParseStatus; Lsun/util/locale/StringTokenIterator; Lsun/util/locale/UnicodeLocaleExtension; +Lsun/util/locale/provider/CalendarDataUtility; Lsun/util/logging/LoggingProxy; Lsun/util/logging/LoggingSupport$1; Lsun/util/logging/LoggingSupport$2; @@ -12947,6 +13150,7 @@ Lsun/util/logging/PlatformLogger; [Ljava/lang/Character; [Ljava/lang/Class; [Ljava/lang/ClassLoader; +[Ljava/lang/ClassValue$Entry; [Ljava/lang/Comparable; [Ljava/lang/Daemons$Daemon; [Ljava/lang/Double; @@ -12954,11 +13158,13 @@ Lsun/util/logging/PlatformLogger; [Ljava/lang/Float; [Ljava/lang/Integer; [Ljava/lang/Long; +[Ljava/lang/Number; [Ljava/lang/Object; [Ljava/lang/Package; [Ljava/lang/Runnable; [Ljava/lang/Short; [Ljava/lang/StackTraceElement; +[Ljava/lang/StackWalker$Option; [Ljava/lang/String; [Ljava/lang/StringBuilder; [Ljava/lang/Thread$State; @@ -12970,6 +13176,7 @@ Lsun/util/logging/PlatformLogger; [Ljava/lang/annotation/Annotation; [Ljava/lang/invoke/MethodHandle; [Ljava/lang/invoke/MethodType; +[Ljava/lang/invoke/TypeDescriptor$OfField; [Ljava/lang/invoke/VarHandle$AccessMode; [Ljava/lang/invoke/VarHandle$AccessType; [Ljava/lang/ref/WeakReference; @@ -12992,8 +13199,10 @@ Lsun/util/logging/PlatformLogger; [Ljava/net/StandardProtocolFamily; [Ljava/nio/ByteBuffer; [Ljava/nio/channels/SelectionKey; +[Ljava/nio/charset/CoderResult; [Ljava/nio/file/AccessMode; [Ljava/nio/file/CopyOption; +[Ljava/nio/file/FileVisitResult; [Ljava/nio/file/LinkOption; [Ljava/nio/file/OpenOption; [Ljava/nio/file/StandardCopyOption; @@ -13001,6 +13210,7 @@ Lsun/util/logging/PlatformLogger; [Ljava/nio/file/attribute/FileAttribute; [Ljava/security/CodeSigner; [Ljava/security/CryptoPrimitive; +[Ljava/security/MessageDigest; [Ljava/security/Permission; [Ljava/security/Principal; [Ljava/security/ProtectionDomain; @@ -13040,15 +13250,16 @@ Lsun/util/logging/PlatformLogger; [Ljava/util/Comparators$NaturalOrderComparator; [Ljava/util/Enumeration; [Ljava/util/Formatter$Flags; -[Ljava/util/Formatter$FormatString; [Ljava/util/HashMap$Node; [Ljava/util/HashMap; [Ljava/util/Hashtable$HashtableEntry; [Ljava/util/List; [Ljava/util/Locale$Category; [Ljava/util/Locale$FilteringMode; +[Ljava/util/Locale$IsoCountryCode; [Ljava/util/Locale; [Ljava/util/Map$Entry; +[Ljava/util/Set; [Ljava/util/TimerTask; [Ljava/util/UUID; [Ljava/util/WeakHashMap$Entry; @@ -13056,7 +13267,6 @@ Lsun/util/logging/PlatformLogger; [Ljava/util/concurrent/ConcurrentHashMap$Node; [Ljava/util/concurrent/ConcurrentHashMap$Segment; [Ljava/util/concurrent/ForkJoinPool$WorkQueue; -[Ljava/util/concurrent/ForkJoinTask$ExceptionNode; [Ljava/util/concurrent/ForkJoinTask; [Ljava/util/concurrent/RunnableScheduledFuture; [Ljava/util/concurrent/TimeUnit; @@ -13120,6 +13330,9 @@ Lsun/util/logging/PlatformLogger; [[Ljava/lang/annotation/Annotation; [[Ljava/lang/invoke/MethodHandle; [[Ljava/math/BigInteger; +[[Ljava/security/cert/Certificate; +[[Ljava/security/cert/X509Certificate; [[S +[[Z [[[B [[[I diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes index 4d22e22953..2264b9510e 100644 --- a/build/boot/preloaded-classes +++ b/build/boot/preloaded-classes @@ -88,6 +88,7 @@ com.android.okhttp.HttpUrl$Builder 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.URLFilter 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.ASN1String 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.DLBitString com.android.org.bouncycastle.asn1.DLExternal com.android.org.bouncycastle.asn1.DLFactory com.android.org.bouncycastle.asn1.DLSequence +com.android.org.bouncycastle.asn1.DLSet com.android.org.bouncycastle.asn1.DefiniteLengthInputStream com.android.org.bouncycastle.asn1.InMemoryRepresentable com.android.org.bouncycastle.asn1.IndefiniteLengthInputStream @@ -398,7 +411,10 @@ com.android.org.bouncycastle.jcajce.provider.symmetric.DES$Mappings com.android.org.bouncycastle.jcajce.provider.symmetric.DES com.android.org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings com.android.org.bouncycastle.jcajce.provider.symmetric.DESede +com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2 +com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$BasePBKDF2WithHmacSHA1 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings +com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$PBKDF2WithHmacSHA1UTF8 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2 com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings com.android.org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12 @@ -420,6 +436,7 @@ com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher$Inval com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher com.android.org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider com.android.org.bouncycastle.jcajce.provider.symmetric.util.ClassUtil +com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil$2 com.android.org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE$Util com.android.org.bouncycastle.jcajce.provider.symmetric.util.PBE @@ -428,6 +445,7 @@ com.android.org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider com.android.org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter com.android.org.bouncycastle.jcajce.provider.util.DigestFactory com.android.org.bouncycastle.jcajce.spec.AEADParameterSpec +com.android.org.bouncycastle.jcajce.spec.PBKDF2KeySpec com.android.org.bouncycastle.jcajce.util.BCJcaJceHelper com.android.org.bouncycastle.jcajce.util.DefaultJcaJceHelper com.android.org.bouncycastle.jcajce.util.JcaJceHelper @@ -436,6 +454,7 @@ com.android.org.bouncycastle.jce.X509Principal com.android.org.bouncycastle.jce.interfaces.BCKeyStore com.android.org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$1 +com.android.org.bouncycastle.jce.provider.BouncyCastleProvider$PrivateProvider com.android.org.bouncycastle.jce.provider.BouncyCastleProvider com.android.org.bouncycastle.jce.provider.BouncyCastleProviderConfiguration com.android.org.bouncycastle.jce.provider.CertStoreCollectionSpi @@ -501,13 +520,16 @@ dalvik.system.SocketTagger$1 dalvik.system.SocketTagger dalvik.system.VMDebug dalvik.system.VMRuntime$HiddenApiUsageLogger +dalvik.system.VMRuntime$SdkVersionContainer dalvik.system.VMRuntime dalvik.system.VMStack +dalvik.system.ZipPathValidator$1 +dalvik.system.ZipPathValidator$Callback +dalvik.system.ZipPathValidator dalvik.system.ZygoteHooks java.awt.font.NumericShaper java.awt.font.TextAttribute java.io.Bits -java.io.BufferedInputStream$$ExternalSyntheticBackportWithForwarding0 java.io.BufferedInputStream java.io.BufferedOutputStream java.io.BufferedReader @@ -516,6 +538,7 @@ java.io.ByteArrayInputStream java.io.ByteArrayOutputStream java.io.CharArrayReader java.io.CharArrayWriter +java.io.CharConversionException java.io.Closeable java.io.Console java.io.DataInput @@ -601,6 +624,8 @@ java.io.ObjectStreamField java.io.OptionalDataException java.io.OutputStream java.io.OutputStreamWriter +java.io.PipedInputStream +java.io.PipedOutputStream java.io.PrintStream java.io.PrintWriter java.io.PushbackInputStream @@ -622,6 +647,8 @@ java.io.UnsupportedEncodingException java.io.WriteAbortedException java.io.Writer java.lang.AbstractMethodError +java.lang.AbstractStringBuilder$$ExternalSyntheticLambda0 +java.lang.AbstractStringBuilder$$ExternalSyntheticLambda1 java.lang.AbstractStringBuilder java.lang.AndroidHardcodedSystemProperties java.lang.Appendable @@ -652,6 +679,8 @@ java.lang.ClassFormatError java.lang.ClassLoader$SystemClassLoader java.lang.ClassLoader java.lang.ClassNotFoundException +java.lang.ClassValue$Entry +java.lang.ClassValue java.lang.CloneNotSupportedException java.lang.Cloneable java.lang.Comparable @@ -682,8 +711,6 @@ java.lang.IndexOutOfBoundsException java.lang.InheritableThreadLocal java.lang.InstantiationError java.lang.InstantiationException -java.lang.Integer$$ExternalSyntheticBackport0 -java.lang.Integer$$ExternalSyntheticBackport1 java.lang.Integer$IntegerCache java.lang.Integer java.lang.InternalError @@ -706,10 +733,11 @@ java.lang.NumberFormatException java.lang.Object java.lang.OutOfMemoryError java.lang.Package -java.lang.Process$PipeInputStream java.lang.Process java.lang.ProcessBuilder$NullInputStream java.lang.ProcessBuilder$NullOutputStream +java.lang.ProcessBuilder$Redirect$1 +java.lang.ProcessBuilder$Redirect$2 java.lang.ProcessBuilder$Redirect java.lang.ProcessBuilder java.lang.ProcessEnvironment$ExternalData @@ -717,13 +745,6 @@ java.lang.ProcessEnvironment$StringEnvironment java.lang.ProcessEnvironment$Value java.lang.ProcessEnvironment$Variable java.lang.ProcessEnvironment -java.lang.ProcessHandleImpl$ExitCompletion -java.lang.ProcessHandleImpl$Info -java.lang.ProcessHandleImpl -java.lang.ProcessImpl$DeferredCloseInputStream -java.lang.ProcessImpl$DeferredCloseProcessPipeInputStream -java.lang.ProcessImpl$ProcessPipeInputStream -java.lang.ProcessImpl$ProcessPipeOutputStream java.lang.ProcessImpl java.lang.Readable java.lang.ReflectiveOperationException @@ -735,9 +756,19 @@ java.lang.SecurityException java.lang.SecurityManager java.lang.Short$ShortCache java.lang.Short +java.lang.StackFrameInfo java.lang.StackOverflowError +java.lang.StackStreamFactory$AbstractStackWalker +java.lang.StackStreamFactory java.lang.StackTraceElement +java.lang.StackWalker$Option +java.lang.StackWalker$StackFrame +java.lang.StackWalker java.lang.StrictMath +java.lang.String$$ExternalSyntheticLambda0 +java.lang.String$$ExternalSyntheticLambda1 +java.lang.String$$ExternalSyntheticLambda2 +java.lang.String$$ExternalSyntheticLambda3 java.lang.String$CaseInsensitiveComparator-IA java.lang.String$CaseInsensitiveComparator java.lang.String @@ -745,8 +776,13 @@ java.lang.StringBuffer java.lang.StringBuilder java.lang.StringFactory java.lang.StringIndexOutOfBoundsException +java.lang.StringLatin1$CharsSpliterator +java.lang.StringLatin1$LinesSpliterator +java.lang.StringLatin1 java.lang.StringUTF16$CharsSpliterator +java.lang.StringUTF16$CharsSpliteratorForString java.lang.StringUTF16$CodePointsSpliterator +java.lang.StringUTF16$CodePointsSpliteratorForString java.lang.StringUTF16 java.lang.System$PropertiesWithNonOverrideableDefaults java.lang.System @@ -760,6 +796,7 @@ java.lang.ThreadDeath java.lang.ThreadGroup java.lang.ThreadLocal$SuppliedThreadLocal java.lang.ThreadLocal$ThreadLocalMap$Entry +java.lang.ThreadLocal$ThreadLocalMap-IA java.lang.ThreadLocal$ThreadLocalMap java.lang.ThreadLocal java.lang.Throwable$PrintStreamOrWriter @@ -777,6 +814,7 @@ java.lang.UNIXProcess$ProcessReaperThreadFactory$1 java.lang.UNIXProcess$ProcessReaperThreadFactory java.lang.UNIXProcess java.lang.UnsatisfiedLinkError +java.lang.UnsupportedClassVersionError java.lang.UnsupportedOperationException java.lang.VMClassLoader java.lang.VerifyError @@ -854,10 +892,14 @@ java.lang.invoke.Transformers$ReferenceArrayElementGetter java.lang.invoke.Transformers$ReferenceArrayElementSetter java.lang.invoke.Transformers$ReferenceIdentity java.lang.invoke.Transformers$Spreader +java.lang.invoke.Transformers$TableSwitch java.lang.invoke.Transformers$Transformer java.lang.invoke.Transformers$TryFinally java.lang.invoke.Transformers$VarargsCollector java.lang.invoke.Transformers$ZeroValue +java.lang.invoke.TypeDescriptor$OfField +java.lang.invoke.TypeDescriptor$OfMethod +java.lang.invoke.TypeDescriptor java.lang.invoke.VarHandle$1 java.lang.invoke.VarHandle$AccessMode java.lang.invoke.VarHandle$AccessType @@ -914,6 +956,7 @@ java.lang.reflect.WeakCache java.lang.reflect.WildcardType java.math.BigDecimal$1 java.math.BigDecimal$LongOverflow +java.math.BigDecimal$StringBuilderHelper java.math.BigDecimal java.math.BigInteger$UnsafeHolder java.math.BigInteger @@ -990,6 +1033,7 @@ java.net.Proxy$Type java.net.Proxy java.net.ProxySelector java.net.ResponseCache +java.net.ServerSocket$1 java.net.ServerSocket java.net.Socket$1 java.net.Socket$2 @@ -1023,6 +1067,7 @@ java.net.UnknownHostException java.net.UnknownServiceException java.nio.Bits java.nio.Buffer +java.nio.BufferMismatch java.nio.BufferOverflowException java.nio.BufferUnderflowException java.nio.ByteBuffer @@ -1096,6 +1141,7 @@ java.nio.channels.spi.AbstractSelectableChannel java.nio.channels.spi.AbstractSelectionKey java.nio.channels.spi.AbstractSelector$1 java.nio.channels.spi.AbstractSelector +java.nio.channels.spi.AsynchronousChannelProvider java.nio.channels.spi.SelectorProvider$1 java.nio.channels.spi.SelectorProvider java.nio.charset.CharacterCodingException @@ -1103,14 +1149,13 @@ java.nio.charset.Charset java.nio.charset.CharsetDecoder java.nio.charset.CharsetEncoder java.nio.charset.CoderMalfunctionError -java.nio.charset.CoderResult$1 -java.nio.charset.CoderResult$2 java.nio.charset.CoderResult$Cache java.nio.charset.CoderResult java.nio.charset.CodingErrorAction java.nio.charset.IllegalCharsetNameException java.nio.charset.StandardCharsets java.nio.charset.UnsupportedCharsetException +java.nio.charset.spi.CharsetProvider java.nio.file.AccessDeniedException java.nio.file.AccessMode java.nio.file.CopyMoveHelper @@ -1124,6 +1169,8 @@ java.nio.file.FileSystemException java.nio.file.FileSystems$DefaultFileSystemHolder$1 java.nio.file.FileSystems$DefaultFileSystemHolder java.nio.file.FileSystems +java.nio.file.FileVisitResult +java.nio.file.FileVisitor java.nio.file.Files$AcceptAllFilter java.nio.file.Files java.nio.file.InvalidPathException @@ -1135,6 +1182,7 @@ java.nio.file.Path java.nio.file.Paths java.nio.file.ProviderMismatchException java.nio.file.SecureDirectoryStream +java.nio.file.SimpleFileVisitor java.nio.file.StandardCopyOption java.nio.file.StandardOpenOption java.nio.file.Watchable @@ -1176,12 +1224,14 @@ java.security.KeyPairGenerator$Delegate java.security.KeyPairGenerator java.security.KeyPairGeneratorSpi java.security.KeyStore$1 +java.security.KeyStore$CallbackHandlerProtection java.security.KeyStore$Entry java.security.KeyStore$LoadStoreParameter java.security.KeyStore$PasswordProtection java.security.KeyStore$PrivateKeyEntry java.security.KeyStore$ProtectionParameter java.security.KeyStore$SecretKeyEntry +java.security.KeyStore$SimpleLoadStoreParameter java.security.KeyStore$TrustedCertificateEntry java.security.KeyStore java.security.KeyStoreException @@ -1351,6 +1401,7 @@ java.time.DayOfWeek java.time.Duration java.time.Instant$1 java.time.Instant +java.time.InstantSource java.time.LocalDate$1 java.time.LocalDate java.time.LocalDateTime @@ -1380,15 +1431,17 @@ java.time.format.DateTimeFormatter java.time.format.DateTimeFormatterBuilder$$ExternalSyntheticLambda0 java.time.format.DateTimeFormatterBuilder$1 java.time.format.DateTimeFormatterBuilder$2 -java.time.format.DateTimeFormatterBuilder$3 java.time.format.DateTimeFormatterBuilder$CharLiteralPrinterParser java.time.format.DateTimeFormatterBuilder$CompositePrinterParser java.time.format.DateTimeFormatterBuilder$DateTimePrinterParser +java.time.format.DateTimeFormatterBuilder$DayPeriod$$ExternalSyntheticLambda0 +java.time.format.DateTimeFormatterBuilder$DayPeriod java.time.format.DateTimeFormatterBuilder$FractionPrinterParser java.time.format.DateTimeFormatterBuilder$InstantPrinterParser java.time.format.DateTimeFormatterBuilder$NumberPrinterParser java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser java.time.format.DateTimeFormatterBuilder$PadPrinterParserDecorator +java.time.format.DateTimeFormatterBuilder$PrefixTree$CI java.time.format.DateTimeFormatterBuilder$PrefixTree java.time.format.DateTimeFormatterBuilder$SettingsParser java.time.format.DateTimeFormatterBuilder$StringLiteralPrinterParser @@ -1467,14 +1520,15 @@ java.util.AbstractMap java.util.AbstractQueue java.util.AbstractSequentialList java.util.AbstractSet +java.util.ArrayDeque$$ExternalSyntheticLambda1 java.util.ArrayDeque$DeqIterator java.util.ArrayDeque$DescendingIterator java.util.ArrayDeque java.util.ArrayList$ArrayListSpliterator -java.util.ArrayList$Itr-IA java.util.ArrayList$Itr java.util.ArrayList$ListItr java.util.ArrayList$SubList$1 +java.util.ArrayList$SubList$2 java.util.ArrayList$SubList java.util.ArrayList java.util.ArrayPrefixHelpers$CumulateTask @@ -1489,18 +1543,12 @@ java.util.Arrays$ArrayItr java.util.Arrays$ArrayList java.util.Arrays$NaturalOrder java.util.Arrays -java.util.ArraysParallelSortHelpers$FJByte$Sorter -java.util.ArraysParallelSortHelpers$FJChar$Sorter -java.util.ArraysParallelSortHelpers$FJDouble$Sorter -java.util.ArraysParallelSortHelpers$FJFloat$Sorter -java.util.ArraysParallelSortHelpers$FJInt$Sorter -java.util.ArraysParallelSortHelpers$FJLong$Sorter java.util.ArraysParallelSortHelpers$FJObject$Sorter -java.util.ArraysParallelSortHelpers$FJShort$Sorter java.util.Base64$Decoder java.util.Base64$Encoder java.util.Base64 java.util.BitSet +java.util.Calendar$$ExternalSyntheticLambda0 java.util.Calendar$Builder java.util.Calendar java.util.Collection @@ -1575,6 +1623,7 @@ java.util.Currency java.util.Date java.util.Deque java.util.Dictionary +java.util.DualPivotQuicksort$Sorter java.util.DualPivotQuicksort java.util.DuplicateFormatFlagsException java.util.EmptyStackException @@ -1647,15 +1696,15 @@ java.util.ImmutableCollections$AbstractImmutableList java.util.ImmutableCollections$AbstractImmutableMap java.util.ImmutableCollections$AbstractImmutableSet java.util.ImmutableCollections$List12 +java.util.ImmutableCollections$ListItr java.util.ImmutableCollections$ListN -java.util.ImmutableCollections$Map0 java.util.ImmutableCollections$Map1 +java.util.ImmutableCollections$MapN$1 +java.util.ImmutableCollections$MapN$MapNIterator java.util.ImmutableCollections$MapN -java.util.ImmutableCollections$Set0 -java.util.ImmutableCollections$Set1 -java.util.ImmutableCollections$Set2 +java.util.ImmutableCollections$Set12 java.util.ImmutableCollections$SetN -java.util.ImmutableCollections +java.util.ImmutableCollections$SubList java.util.InputMismatchException java.util.Iterator java.util.JumboEnumSet$EnumSetIterator @@ -1671,6 +1720,7 @@ java.util.LinkedHashMap$LinkedValueIterator java.util.LinkedHashMap$LinkedValues java.util.LinkedHashMap java.util.LinkedHashSet +java.util.LinkedList$DescendingIterator java.util.LinkedList$ListItr java.util.LinkedList$Node java.util.LinkedList @@ -1682,6 +1732,10 @@ java.util.Locale$Builder java.util.Locale$Cache java.util.Locale$Category java.util.Locale$FilteringMode +java.util.Locale$IsoCountryCode$1 +java.util.Locale$IsoCountryCode$2 +java.util.Locale$IsoCountryCode$3 +java.util.Locale$IsoCountryCode java.util.Locale$LanguageRange java.util.Locale$LocaleKey java.util.Locale$NoImagePreloadHolder @@ -1718,13 +1772,16 @@ java.util.ResourceBundle$1 java.util.ResourceBundle$BundleReference java.util.ResourceBundle$CacheKey java.util.ResourceBundle$CacheKeyReference +java.util.ResourceBundle$Control$$ExternalSyntheticLambda0 java.util.ResourceBundle$Control$1 java.util.ResourceBundle$Control$CandidateListCache java.util.ResourceBundle$Control -java.util.ResourceBundle$LoaderReference +java.util.ResourceBundle$KeyElementReference +java.util.ResourceBundle$RBClassLoader$1 +java.util.ResourceBundle$RBClassLoader java.util.ResourceBundle$SingleFormatControl java.util.ResourceBundle -java.util.Scanner$1 +java.util.Scanner$PatternLRUCache java.util.Scanner java.util.ServiceConfigurationError java.util.ServiceLoader$1 @@ -1779,6 +1836,7 @@ java.util.TreeMap$ValueIterator java.util.TreeMap$Values java.util.TreeMap java.util.TreeSet +java.util.Tripwire$$ExternalSyntheticLambda0 java.util.Tripwire java.util.UUID$Holder java.util.UUID @@ -1807,6 +1865,8 @@ java.util.concurrent.BlockingQueue java.util.concurrent.Callable java.util.concurrent.CancellationException java.util.concurrent.CompletableFuture$AltResult +java.util.concurrent.CompletableFuture$AsyncRun +java.util.concurrent.CompletableFuture$AsyncSupply java.util.concurrent.CompletableFuture$AsynchronousCompletionTask java.util.concurrent.CompletableFuture$Completion java.util.concurrent.CompletableFuture$Signaller @@ -1869,6 +1929,7 @@ java.util.concurrent.ConcurrentHashMap$ValuesView java.util.concurrent.ConcurrentHashMap java.util.concurrent.ConcurrentLinkedDeque$Node java.util.concurrent.ConcurrentLinkedDeque +java.util.concurrent.ConcurrentLinkedQueue$$ExternalSyntheticLambda0 java.util.concurrent.ConcurrentLinkedQueue$Itr java.util.concurrent.ConcurrentLinkedQueue$Node java.util.concurrent.ConcurrentLinkedQueue @@ -1902,12 +1963,13 @@ java.util.concurrent.Executors$FinalizableDelegatedExecutorService java.util.concurrent.Executors$RunnableAdapter java.util.concurrent.Executors java.util.concurrent.ForkJoinPool$1 +java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory java.util.concurrent.ForkJoinPool$ManagedBlocker java.util.concurrent.ForkJoinPool$WorkQueue java.util.concurrent.ForkJoinPool -java.util.concurrent.ForkJoinTask$ExceptionNode +java.util.concurrent.ForkJoinTask$Aux java.util.concurrent.ForkJoinTask java.util.concurrent.ForkJoinWorkerThread java.util.concurrent.Future @@ -1920,6 +1982,7 @@ java.util.concurrent.LinkedBlockingDeque java.util.concurrent.LinkedBlockingQueue$Itr java.util.concurrent.LinkedBlockingQueue$Node java.util.concurrent.LinkedBlockingQueue +java.util.concurrent.Phaser java.util.concurrent.PriorityBlockingQueue java.util.concurrent.RejectedExecutionException java.util.concurrent.RejectedExecutionHandler @@ -1935,6 +1998,7 @@ java.util.concurrent.Semaphore$FairSync java.util.concurrent.Semaphore$NonfairSync java.util.concurrent.Semaphore$Sync java.util.concurrent.Semaphore +java.util.concurrent.SynchronousQueue$TransferQueue$QNode java.util.concurrent.SynchronousQueue$TransferQueue java.util.concurrent.SynchronousQueue$TransferStack$SNode java.util.concurrent.SynchronousQueue$TransferStack @@ -1967,8 +2031,11 @@ java.util.concurrent.atomic.Striped64$1 java.util.concurrent.atomic.Striped64$Cell java.util.concurrent.atomic.Striped64 java.util.concurrent.locks.AbstractOwnableSynchronizer +java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject +java.util.concurrent.locks.AbstractQueuedSynchronizer$ExclusiveNode java.util.concurrent.locks.AbstractQueuedSynchronizer$Node +java.util.concurrent.locks.AbstractQueuedSynchronizer$SharedNode java.util.concurrent.locks.AbstractQueuedSynchronizer java.util.concurrent.locks.Condition java.util.concurrent.locks.Lock @@ -2015,8 +2082,13 @@ java.util.function.IntToLongFunction java.util.function.IntUnaryOperator java.util.function.LongBinaryOperator java.util.function.LongConsumer +java.util.function.LongFunction +java.util.function.LongPredicate java.util.function.LongSupplier java.util.function.LongUnaryOperator +java.util.function.ObjDoubleConsumer +java.util.function.ObjIntConsumer +java.util.function.ObjLongConsumer java.util.function.Predicate java.util.function.Supplier java.util.function.ToDoubleBiFunction @@ -2038,6 +2110,7 @@ java.util.jar.JarVerifier$VerifierStream java.util.jar.JarVerifier java.util.jar.Manifest$FastInputStream java.util.jar.Manifest +java.util.logging.ConsoleHandler java.util.logging.ErrorManager java.util.logging.FileHandler$1 java.util.logging.FileHandler$InitializationErrorManager @@ -2092,6 +2165,7 @@ java.util.stream.BaseStream java.util.stream.Collector$Characteristics java.util.stream.Collector java.util.stream.Collectors$$ExternalSyntheticLambda0 +java.util.stream.Collectors$$ExternalSyntheticLambda13 java.util.stream.Collectors$$ExternalSyntheticLambda15 java.util.stream.Collectors$$ExternalSyntheticLambda1 java.util.stream.Collectors$$ExternalSyntheticLambda20 @@ -2157,6 +2231,8 @@ java.util.stream.IntPipeline$$ExternalSyntheticLambda7 java.util.stream.IntPipeline$$ExternalSyntheticLambda8 java.util.stream.IntPipeline$4$1 java.util.stream.IntPipeline$4 +java.util.stream.IntPipeline$9$1 +java.util.stream.IntPipeline$9 java.util.stream.IntPipeline$Head java.util.stream.IntPipeline$StatelessOp java.util.stream.IntPipeline @@ -2229,6 +2305,7 @@ java.util.stream.ReferencePipeline$5$1 java.util.stream.ReferencePipeline$5 java.util.stream.ReferencePipeline$6$1 java.util.stream.ReferencePipeline$6 +java.util.stream.ReferencePipeline$7$1 java.util.stream.ReferencePipeline$7 java.util.stream.ReferencePipeline$Head java.util.stream.ReferencePipeline$StatefulOp @@ -2273,10 +2350,12 @@ java.util.stream.Streams$StreamBuilderImpl java.util.stream.Streams java.util.stream.TerminalOp java.util.stream.TerminalSink +java.util.stream.Tripwire$$ExternalSyntheticLambda0 java.util.stream.Tripwire java.util.zip.Adler32 java.util.zip.CRC32 java.util.zip.CheckedInputStream +java.util.zip.Checksum$1 java.util.zip.Checksum java.util.zip.DataFormatException java.util.zip.Deflater @@ -2309,6 +2388,7 @@ javax.crypto.Cipher$NeedToSet javax.crypto.Cipher$SpiAndProviderUpdater javax.crypto.Cipher$Transform javax.crypto.Cipher +javax.crypto.CipherInputStream javax.crypto.CipherOutputStream javax.crypto.CipherSpi javax.crypto.CryptoPermissions @@ -2385,6 +2465,7 @@ javax.net.ssl.SSLSessionContext javax.net.ssl.SSLSocket javax.net.ssl.SSLSocketFactory$1 javax.net.ssl.SSLSocketFactory +javax.net.ssl.StandardConstants javax.net.ssl.TrustManager javax.net.ssl.TrustManagerFactory$1 javax.net.ssl.TrustManagerFactory @@ -2394,6 +2475,9 @@ javax.net.ssl.X509ExtendedTrustManager javax.net.ssl.X509KeyManager javax.net.ssl.X509TrustManager javax.security.auth.Destroyable +javax.security.auth.callback.Callback +javax.security.auth.callback.CallbackHandler +javax.security.auth.callback.PasswordCallback javax.security.auth.callback.UnsupportedCallbackException javax.security.auth.x500.X500Principal javax.security.cert.Certificate @@ -2401,9 +2485,11 @@ javax.security.cert.CertificateEncodingException javax.security.cert.CertificateException javax.security.cert.X509Certificate$1 javax.security.cert.X509Certificate +javax.xml.datatype.DatatypeConfigurationException javax.xml.datatype.DatatypeConstants$Field javax.xml.datatype.DatatypeConstants javax.xml.datatype.Duration +javax.xml.namespace.QName javax.xml.parsers.DocumentBuilder javax.xml.parsers.DocumentBuilderFactory javax.xml.parsers.ParserConfigurationException @@ -2416,18 +2502,24 @@ jdk.internal.math.FloatingDecimal$ASCIIToBinaryConverter jdk.internal.math.FloatingDecimal$BinaryToASCIIBuffer jdk.internal.math.FloatingDecimal$BinaryToASCIIConverter jdk.internal.math.FloatingDecimal$ExceptionalBinaryToASCIIBuffer +jdk.internal.math.FloatingDecimal$HexFloatPattern jdk.internal.math.FloatingDecimal$PreparedASCIIToBinaryBuffer jdk.internal.math.FloatingDecimal +jdk.internal.math.FormattedFloatingDecimal$1 +jdk.internal.math.FormattedFloatingDecimal$2 jdk.internal.math.FormattedFloatingDecimal$Form jdk.internal.math.FormattedFloatingDecimal jdk.internal.misc.JavaObjectInputStreamAccess jdk.internal.misc.SharedSecrets +jdk.internal.misc.TerminatingThreadLocal$1 jdk.internal.misc.TerminatingThreadLocal jdk.internal.misc.Unsafe +jdk.internal.misc.UnsafeConstants jdk.internal.misc.VM jdk.internal.reflect.Reflection jdk.internal.util.ArraysSupport jdk.internal.util.Preconditions +jdk.internal.util.StaticProperty libcore.content.type.MimeMap$$ExternalSyntheticLambda0 libcore.content.type.MimeMap$Builder$Element libcore.content.type.MimeMap$Builder @@ -2435,7 +2527,6 @@ libcore.content.type.MimeMap$MemoizingSupplier libcore.content.type.MimeMap libcore.icu.CollationKeyICU libcore.icu.DateIntervalFormat -libcore.icu.DateUtilsBridge libcore.icu.DecimalFormatData libcore.icu.ICU libcore.icu.LocaleData @@ -2508,17 +2599,24 @@ org.apache.harmony.xml.ExpatAttributes org.apache.harmony.xml.ExpatException org.apache.harmony.xml.ExpatParser$CurrentAttributes org.apache.harmony.xml.ExpatParser$ExpatLocator +org.apache.harmony.xml.ExpatParser$ParseException org.apache.harmony.xml.ExpatParser org.apache.harmony.xml.ExpatReader +org.apache.harmony.xml.dom.AttrImpl +org.apache.harmony.xml.dom.CDATASectionImpl org.apache.harmony.xml.dom.CharacterDataImpl +org.apache.harmony.xml.dom.CommentImpl org.apache.harmony.xml.dom.DOMImplementationImpl org.apache.harmony.xml.dom.DocumentImpl +org.apache.harmony.xml.dom.DocumentTypeImpl org.apache.harmony.xml.dom.ElementImpl +org.apache.harmony.xml.dom.EntityReferenceImpl org.apache.harmony.xml.dom.InnerNodeImpl org.apache.harmony.xml.dom.LeafNodeImpl org.apache.harmony.xml.dom.NodeImpl$1 org.apache.harmony.xml.dom.NodeImpl org.apache.harmony.xml.dom.NodeListImpl +org.apache.harmony.xml.dom.ProcessingInstructionImpl org.apache.harmony.xml.dom.TextImpl org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl org.apache.harmony.xml.parsers.DocumentBuilderImpl @@ -2532,15 +2630,20 @@ org.json.JSONObject org.json.JSONStringer$Scope org.json.JSONStringer org.json.JSONTokener +org.w3c.dom.Attr +org.w3c.dom.CDATASection org.w3c.dom.CharacterData +org.w3c.dom.Comment org.w3c.dom.DOMException org.w3c.dom.DOMImplementation org.w3c.dom.Document org.w3c.dom.DocumentFragment org.w3c.dom.DocumentType org.w3c.dom.Element +org.w3c.dom.EntityReference org.w3c.dom.Node org.w3c.dom.NodeList +org.w3c.dom.ProcessingInstruction org.w3c.dom.Text org.w3c.dom.TypeInfo org.xml.sax.AttributeList @@ -2556,6 +2659,7 @@ org.xml.sax.Parser org.xml.sax.SAXException org.xml.sax.SAXNotRecognizedException org.xml.sax.SAXNotSupportedException +org.xml.sax.SAXParseException org.xml.sax.XMLFilter org.xml.sax.XMLReader org.xml.sax.ext.DeclHandler @@ -2564,6 +2668,7 @@ org.xml.sax.ext.EntityResolver2 org.xml.sax.ext.LexicalHandler org.xml.sax.helpers.AttributesImpl org.xml.sax.helpers.DefaultHandler +org.xml.sax.helpers.LocatorImpl org.xml.sax.helpers.NamespaceSupport org.xml.sax.helpers.XMLFilterImpl org.xmlpull.v1.XmlPullParser @@ -2584,7 +2689,6 @@ sun.misc.IOUtils sun.misc.JavaIOFileDescriptorAccess sun.misc.LRUCache sun.misc.SharedSecrets -sun.misc.Unsafe$$ExternalSyntheticBackportWithForwarding0 sun.misc.Unsafe sun.misc.VM sun.misc.Version @@ -2624,6 +2728,7 @@ sun.nio.ch.FileLockTable sun.nio.ch.IOStatus sun.nio.ch.IOUtil sun.nio.ch.Interruptible +sun.nio.ch.LinuxAsynchronousChannelProvider sun.nio.ch.NativeDispatcher sun.nio.ch.NativeObject sun.nio.ch.NativeThread @@ -2714,6 +2819,7 @@ sun.security.jca.ProviderList sun.security.jca.Providers sun.security.jca.ServiceId sun.security.pkcs.ContentInfo +sun.security.pkcs.ESSCertId sun.security.pkcs.PKCS7$VerbatimX509Certificate sun.security.pkcs.PKCS7$WrappedX509Certificate sun.security.pkcs.PKCS7 @@ -2752,6 +2858,7 @@ sun.security.timestamp.TimestampToken sun.security.util.AbstractAlgorithmConstraints$1 sun.security.util.AbstractAlgorithmConstraints sun.security.util.AlgorithmDecomposer +sun.security.util.AnchorCertificates$1 sun.security.util.AnchorCertificates sun.security.util.BitArray sun.security.util.ByteArrayLexOrder @@ -2772,6 +2879,7 @@ sun.security.util.DisabledAlgorithmConstraints$Constraint sun.security.util.DisabledAlgorithmConstraints$Constraints sun.security.util.DisabledAlgorithmConstraints$KeySizeConstraint sun.security.util.DisabledAlgorithmConstraints +sun.security.util.FilePaths sun.security.util.KeyUtil sun.security.util.Length sun.security.util.ManifestDigester$Entry @@ -2784,6 +2892,9 @@ sun.security.util.MemoryCache$HardCacheEntry sun.security.util.MemoryCache$SoftCacheEntry sun.security.util.MemoryCache sun.security.util.ObjectIdentifier +sun.security.util.PropertyExpander +sun.security.util.Resources +sun.security.util.ResourcesMgr$1 sun.security.util.ResourcesMgr sun.security.util.SecurityConstants sun.security.util.SignatureFileVerifier @@ -2859,6 +2970,7 @@ sun.util.calendar.AbstractCalendar sun.util.calendar.BaseCalendar$Date sun.util.calendar.BaseCalendar sun.util.calendar.CalendarDate +sun.util.calendar.CalendarSystem$GregorianHolder sun.util.calendar.CalendarSystem sun.util.calendar.CalendarUtils sun.util.calendar.Era @@ -2884,6 +2996,7 @@ sun.util.locale.LocaleUtils sun.util.locale.ParseStatus sun.util.locale.StringTokenIterator sun.util.locale.UnicodeLocaleExtension +sun.util.locale.provider.CalendarDataUtility sun.util.logging.LoggingProxy sun.util.logging.LoggingSupport$1 sun.util.logging.LoggingSupport$2 @@ -2901,21 +3014,26 @@ sun.util.logging.PlatformLogger [I [J [Landroid.system.StructCapUserData; +[Landroid.system.StructIfaddrs; [Landroid.system.StructPollfd; [Lcom.android.okhttp.CipherSuite; [Lcom.android.okhttp.ConnectionSpec; [Lcom.android.okhttp.HttpUrl$Builder$ParseResult; [Lcom.android.okhttp.Protocol; [Lcom.android.okhttp.TlsVersion; +[Lcom.android.okhttp.okio.ByteString; [Lcom.android.org.bouncycastle.asn1.ASN1Encodable; +[Lcom.android.org.bouncycastle.asn1.ASN1Enumerated; [Lcom.android.org.bouncycastle.asn1.ASN1ObjectIdentifier; [Lcom.android.org.bouncycastle.asn1.ASN1OctetString; +[Lcom.android.org.bouncycastle.asn1.ASN1Primitive; [Lcom.android.org.bouncycastle.crypto.params.DHParameters; [Lcom.android.org.bouncycastle.crypto.params.DSAParameters; [Lcom.android.org.bouncycastle.jcajce.provider.asymmetric.x509.PEMUtil$Boundaries; [Lcom.android.org.kxml2.io.KXmlParser$ValueContext; [Ldalvik.system.DexPathList$Element; [Ldalvik.system.DexPathList$NativeLibraryElement; +[Ljava.io.Closeable; [Ljava.io.File$PathStatus; [Ljava.io.File; [Ljava.io.FileDescriptor; @@ -2925,23 +3043,28 @@ sun.util.logging.PlatformLogger [Ljava.io.ObjectStreamClass$MemberSignature; [Ljava.io.ObjectStreamField; [Ljava.io.Serializable; +[Ljava.lang.Boolean; [Ljava.lang.Byte; [Ljava.lang.CharSequence; [Ljava.lang.Character$UnicodeBlock; [Ljava.lang.Character; [Ljava.lang.Class; [Ljava.lang.ClassLoader; +[Ljava.lang.ClassValue$Entry; [Ljava.lang.Comparable; [Ljava.lang.Daemons$Daemon; +[Ljava.lang.Double; [Ljava.lang.Enum; [Ljava.lang.Float; [Ljava.lang.Integer; [Ljava.lang.Long; +[Ljava.lang.Number; [Ljava.lang.Object; [Ljava.lang.Package; [Ljava.lang.Runnable; [Ljava.lang.Short; [Ljava.lang.StackTraceElement; +[Ljava.lang.StackWalker$Option; [Ljava.lang.String; [Ljava.lang.StringBuilder; [Ljava.lang.Thread$State; @@ -2953,6 +3076,7 @@ sun.util.logging.PlatformLogger [Ljava.lang.annotation.Annotation; [Ljava.lang.invoke.MethodHandle; [Ljava.lang.invoke.MethodType; +[Ljava.lang.invoke.TypeDescriptor$OfField; [Ljava.lang.invoke.VarHandle$AccessMode; [Ljava.lang.invoke.VarHandle$AccessType; [Ljava.lang.ref.WeakReference; @@ -2975,8 +3099,10 @@ sun.util.logging.PlatformLogger [Ljava.net.StandardProtocolFamily; [Ljava.nio.ByteBuffer; [Ljava.nio.channels.SelectionKey; +[Ljava.nio.charset.CoderResult; [Ljava.nio.file.AccessMode; [Ljava.nio.file.CopyOption; +[Ljava.nio.file.FileVisitResult; [Ljava.nio.file.LinkOption; [Ljava.nio.file.OpenOption; [Ljava.nio.file.StandardCopyOption; @@ -2984,12 +3110,15 @@ sun.util.logging.PlatformLogger [Ljava.nio.file.attribute.FileAttribute; [Ljava.security.CodeSigner; [Ljava.security.CryptoPrimitive; +[Ljava.security.MessageDigest; [Ljava.security.Permission; [Ljava.security.Principal; [Ljava.security.ProtectionDomain; [Ljava.security.Provider; [Ljava.security.cert.CRLReason; +[Ljava.security.cert.CertPathValidatorException$BasicReason; [Ljava.security.cert.Certificate; +[Ljava.security.cert.PKIXReason; [Ljava.security.cert.PKIXRevocationChecker$Option; [Ljava.security.cert.X509CRL; [Ljava.security.cert.X509Certificate; @@ -3021,15 +3150,16 @@ sun.util.logging.PlatformLogger [Ljava.util.Comparators$NaturalOrderComparator; [Ljava.util.Enumeration; [Ljava.util.Formatter$Flags; -[Ljava.util.Formatter$FormatString; [Ljava.util.HashMap$Node; [Ljava.util.HashMap; [Ljava.util.Hashtable$HashtableEntry; [Ljava.util.List; [Ljava.util.Locale$Category; [Ljava.util.Locale$FilteringMode; +[Ljava.util.Locale$IsoCountryCode; [Ljava.util.Locale; [Ljava.util.Map$Entry; +[Ljava.util.Set; [Ljava.util.TimerTask; [Ljava.util.UUID; [Ljava.util.WeakHashMap$Entry; @@ -3037,7 +3167,6 @@ sun.util.logging.PlatformLogger [Ljava.util.concurrent.ConcurrentHashMap$Node; [Ljava.util.concurrent.ConcurrentHashMap$Segment; [Ljava.util.concurrent.ForkJoinPool$WorkQueue; -[Ljava.util.concurrent.ForkJoinTask$ExceptionNode; [Ljava.util.concurrent.ForkJoinTask; [Ljava.util.concurrent.RunnableScheduledFuture; [Ljava.util.concurrent.TimeUnit; @@ -3056,9 +3185,11 @@ sun.util.logging.PlatformLogger [Ljavax.net.ssl.SSLEngineResult$HandshakeStatus; [Ljavax.net.ssl.SSLEngineResult$Status; [Ljavax.net.ssl.TrustManager; +[Ljavax.security.auth.callback.Callback; [Ljavax.security.auth.x500.X500Principal; [Ljavax.security.cert.X509Certificate; [Ljdk.internal.math.FDBigInteger; +[Ljdk.internal.math.FormattedFloatingDecimal$Form; [Llibcore.io.ClassPathURLStreamHandler; [Llibcore.io.IoTracker$Mode; [Llibcore.reflect.AnnotationMember$DefaultValues; @@ -3087,6 +3218,7 @@ sun.util.logging.PlatformLogger [Z [[B [[C +[[D [[F [[I [[J @@ -3098,6 +3230,9 @@ sun.util.logging.PlatformLogger [[Ljava.lang.annotation.Annotation; [[Ljava.lang.invoke.MethodHandle; [[Ljava.math.BigInteger; +[[Ljava.security.cert.Certificate; +[[Ljava.security.cert.X509Certificate; [[S +[[Z [[[B [[[I diff --git a/libartbase/base/compiler_filter.h b/libartbase/base/compiler_filter.h index 7e6aae8ebd..0a7b1bc2e7 100644 --- a/libartbase/base/compiler_filter.h +++ b/libartbase/base/compiler_filter.h @@ -29,6 +29,8 @@ class CompilerFilter final { public: // Note: Order here matters. Later filter choices are considered "as good // as" earlier filter choices. + // Keep supported filters in sync with `ArtShellCommand.printHelp` in + // art/libartservice/service/java/com/android/server/art/ArtShellCommand.java. enum Filter { kAssumeVerified, // Skip verification but mark all classes as verified anyway. kVerify, // Only verify classes. diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc index cb2aee2979..0573e9037b 100644 --- a/libartbase/base/file_utils.cc +++ b/libartbase/base/file_utils.cc @@ -83,6 +83,8 @@ static constexpr const char* kAndroidSystemExtRootEnvVar = "SYSTEM_EXT_ROOT"; static constexpr const char* kAndroidSystemExtRootDefaultPath = "/system_ext"; static constexpr const char* kAndroidDataEnvVar = "ANDROID_DATA"; static constexpr const char* kAndroidDataDefaultPath = "/data"; +static constexpr const char* kAndroidExpandEnvVar = "ANDROID_EXPAND"; +static constexpr const char* kAndroidExpandDefaultPath = "/mnt/expand"; static constexpr const char* kAndroidArtRootEnvVar = "ANDROID_ART_ROOT"; static constexpr const char* kAndroidConscryptRootEnvVar = "ANDROID_CONSCRYPT_ROOT"; static constexpr const char* kAndroidI18nRootEnvVar = "ANDROID_I18N_ROOT"; @@ -293,6 +295,18 @@ std::string GetAndroidDataSafe(std::string* error_msg) { std::string GetAndroidData() { return GetAndroidDir(kAndroidDataEnvVar, kAndroidDataDefaultPath); } +std::string GetAndroidExpandSafe(std::string* error_msg) { + const char* android_dir = GetAndroidDirSafe(kAndroidExpandEnvVar, + kAndroidExpandDefaultPath, + /*must_exist=*/true, + error_msg); + return (android_dir != nullptr) ? android_dir : ""; +} + +std::string GetAndroidExpand() { + return GetAndroidDir(kAndroidExpandEnvVar, kAndroidExpandDefaultPath); +} + std::string GetArtApexData() { return GetAndroidDir(kArtApexDataEnvVar, kArtApexDataDefaultPath, /*must_exist=*/false); } diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index 4ec348d9ec..8222a8a1d4 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -71,6 +71,11 @@ std::string GetAndroidData(); // Find $ANDROID_DATA, /data, or return an empty string. std::string GetAndroidDataSafe(/*out*/ std::string* error_msg); +// Find $ANDROID_EXPAND, /mnt/expand, or abort. +std::string GetAndroidExpand(); +// Find $ANDROID_EXPAND, /mnt/expand, or return an empty string. +std::string GetAndroidExpandSafe(/*out*/ std::string* error_msg); + // Find $ART_APEX_DATA, /data/misc/apexdata/com.android.art, or abort. std::string GetArtApexData(); diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp index 301ef89990..053034a369 100644 --- a/libartpalette/Android.bp +++ b/libartpalette/Android.bp @@ -58,6 +58,9 @@ art_cc_library { "libbase_headers", "jni_headers", ], + export_header_lib_headers: [ + "jni_headers", + ], target: { // Targets supporting dlopen build the client library which loads // and binds the methods in the libartpalette-system library. diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp index ebfe936dbb..7faa20d2fa 100644 --- a/libartservice/service/Android.bp +++ b/libartservice/service/Android.bp @@ -60,13 +60,61 @@ cc_library { ], } +java_defaults { + name: "service-art-defaults", + defaults: [ + "framework-system-server-module-defaults", + ], + sdk_version: "system_server_current", + min_sdk_version: "31", + srcs: [ + "java/**/*.java", + ], + libs: [ + "androidx.annotation_annotation", + "auto_value_annotations", + "sdk_module-lib_current_framework-permission-s", + // TODO(b/256866172): Transitive dependency, for r8 only. + "framework-statsd.stubs.module_lib", + // TODO(b/256866172): Transitive dependency, for r8 only. This module + // always refers to the jar in prebuilts/sdk. We can't use + // "framework-connectivity.stubs.module_lib" here because it's not + // available on master-art. + "sdk_module-lib_current_framework-connectivity", + ], + static_libs: [ + "art-statslog-art-java", + "artd-aidl-java", + "modules-utils-package-state", + "modules-utils-shell-command-handler", + "service-art-proto-java", + ], + plugins: [ + "auto_value_plugin", + ], +} + +// Used by tests to allow tests to mock the right classes. +java_library { + name: "service-art-pre-jarjar", + defaults: ["service-art-defaults"], + installable: false, + visibility: [ + "//visibility:override", + "//visibility:private", + ], +} + // Provides the API and implementation of the ART Service class that will be // loaded by the System Server. java_sdk_library { // This target is named 'service-art' to conform to the naming conventions // for JAR files in the System Server. name: "service-art", - defaults: ["framework-system-server-module-defaults"], + defaults: [ + "service-art-defaults", + "framework-system-server-module-optimize-defaults", + ], permitted_packages: ["com.android.server.art"], dex_preopt: { profile: "art-profile", @@ -79,14 +127,49 @@ java_sdk_library { "com.android.art", "com.android.art.debug", ], + jarjar_rules: "jarjar-rules.txt", + optimize: { + proguard_flags_files: ["proguard.flags"], + }, +} + +java_library { + name: "service-art-proto-java", + proto: { + type: "lite", + }, + srcs: [ + "proto/**/*.proto", + ], sdk_version: "system_server_current", min_sdk_version: "31", + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], +} + +java_library { + name: "art-statslog-art-java", srcs: [ - "java/**/*.java", + ":art-statslog-art-java-gen", ], - static_libs: [ + libs: [ + "framework-statsd.stubs.module_lib", + ], + sdk_version: "system_server_current", + min_sdk_version: "31", + apex_available: [ + "com.android.art", + "com.android.art.debug", ], - jarjar_rules: "jarjar-rules.txt", +} + +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 { @@ -129,12 +212,31 @@ android_test { "androidx.test.ext.junit", "androidx.test.ext.truth", "androidx.test.runner", - "mockito-target-minus-junit4", - "service-art.impl", + "artd-aidl-java", + "framework-annotations-lib", + // We need ExtendedMockito to mock static methods. + "mockito-target-extended-minus-junit4", + "modules-utils-package-state", + "service-art-pre-jarjar", + // Statically link against system server to allow us to mock system + // server APIs. This won't work on master-art, but it's fine because we + // don't run this test on master-art. + "services.core", ], - sdk_version: "system_server_current", + jni_libs: [ + // The two libraries below are required by ExtendedMockito. + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + compile_multilib: "both", + + // TODO: This module should move to sdk_version: "system_server_current" when possible, + // as this will restrict the APIs available to just that expected system API. For now, + // a compileOnly / runtimeOnly split for dependencies doesn't exist in the build system + // and so it's not possible to enforce. min_sdk_version: "31", test_suites: ["general-tests"], + test_config: "ArtServiceTests.xml", } diff --git a/libartservice/service/AndroidManifest.xml b/libartservice/service/AndroidManifest.xml index 921bde92c1..1c13fc6e67 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 0000000000..7a47ca3230 --- /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 c7844e0780..a9cd65a44d 100644 --- a/libartservice/service/api/system-server-current.txt +++ b/libartservice/service/api/system-server-current.txt @@ -2,7 +2,183 @@ package com.android.server.art { public final class ArtManagerLocal { - ctor public ArtManagerLocal(); + ctor @Deprecated public ArtManagerLocal(); + ctor public ArtManagerLocal(@NonNull android.content.Context); + method public void addDexoptDoneCallback(boolean, @NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback); + method public void cancelBackgroundDexoptJob(); + method @NonNull public void clearAppProfiles(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String); + method public void clearBatchDexoptStartCallback(); + method public void clearScheduleBackgroundDexoptJobCallback(); + method @NonNull public com.android.server.art.model.DeleteResult deleteDexoptArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String); + method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams); + method @NonNull public com.android.server.art.model.DexoptResult dexoptPackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.DexoptParams, @NonNull android.os.CancellationSignal); + method public void dump(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot); + method public void dumpPackage(@NonNull java.io.PrintWriter, @NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String); + method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String); + method @NonNull public com.android.server.art.model.DexoptStatus getDexoptStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, int); + method public int handleShellCommand(@NonNull android.os.Binder, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]); + method public void onBoot(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<com.android.server.art.model.OperationProgress>); + method public void printShellCommandHelp(@NonNull java.io.PrintWriter); + method public void removeDexoptDoneCallback(@NonNull com.android.server.art.ArtManagerLocal.DexoptDoneCallback); + method public int scheduleBackgroundDexoptJob(); + method public void setBatchDexoptStartCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback); + method public void setScheduleBackgroundDexoptJobCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback); + method @NonNull public android.os.ParcelFileDescriptor snapshotAppProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @Nullable String) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException; + method @NonNull public android.os.ParcelFileDescriptor snapshotBootImageProfile(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot) throws com.android.server.art.ArtManagerLocal.SnapshotProfileException; + method public void startBackgroundDexoptJob(); + method public void unscheduleBackgroundDexoptJob(); + } + + public static interface ArtManagerLocal.BatchDexoptStartCallback { + method public void onBatchDexoptStart(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull com.android.server.art.model.BatchDexoptParams.Builder, @NonNull android.os.CancellationSignal); + } + + public static interface ArtManagerLocal.DexoptDoneCallback { + method public void onDexoptDone(@NonNull com.android.server.art.model.DexoptResult); + } + + public static interface ArtManagerLocal.ScheduleBackgroundDexoptJobCallback { + method public void onOverrideJobInfo(@NonNull android.app.job.JobInfo.Builder); + } + + public static class ArtManagerLocal.SnapshotProfileException extends java.lang.Exception { + } + + public class ArtModuleServiceInitializer { + method public static void setArtModuleServiceManager(@NonNull android.os.ArtModuleServiceManager); + } + + public class DexUseManagerLocal { + method @NonNull public static com.android.server.art.DexUseManagerLocal createInstance(@NonNull android.content.Context); + method @NonNull public java.util.List<com.android.server.art.model.DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo(@NonNull String); + method public void notifyDexContainersLoaded(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>); + method public void systemReady(); + } + + public class ReasonMapping { + field public static final String REASON_BG_DEXOPT = "bg-dexopt"; + field public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update"; + field public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota"; + field public static final String REASON_CMDLINE = "cmdline"; + field public static final String REASON_FIRST_BOOT = "first-boot"; + field public static final String REASON_INACTIVE = "inactive"; + field public static final String REASON_INSTALL = "install"; + field public static final String REASON_INSTALL_BULK = "install-bulk"; + field public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded"; + field public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary"; + field public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = "install-bulk-secondary-downgraded"; + field public static final String REASON_INSTALL_FAST = "install-fast"; + } + +} + +package com.android.server.art.model { + + public class ArtFlags { + method public static int defaultGetStatusFlags(); + field public static final int FLAG_FORCE = 16; // 0x10 + field public static final int FLAG_FOR_PRIMARY_DEX = 1; // 0x1 + field public static final int FLAG_FOR_SECONDARY_DEX = 2; // 0x2 + field public static final int FLAG_FOR_SINGLE_SPLIT = 32; // 0x20 + field public static final int FLAG_SHOULD_DOWNGRADE = 8; // 0x8 + field public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 4; // 0x4 + field public static final int FLAG_SKIP_IF_STORAGE_LOW = 64; // 0x40 + field public static final int PRIORITY_BACKGROUND = 40; // 0x28 + field public static final int PRIORITY_BOOT = 100; // 0x64 + field public static final int PRIORITY_INTERACTIVE = 60; // 0x3c + field public static final int PRIORITY_INTERACTIVE_FAST = 80; // 0x50 + field public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2; // 0x2 + field public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1; // 0x1 + field public static final int SCHEDULE_SUCCESS = 0; // 0x0 + } + + public abstract class BatchDexoptParams { + method @NonNull public abstract com.android.server.art.model.DexoptParams getDexoptParams(); + method @NonNull public abstract java.util.List<java.lang.String> getPackages(); + } + + public static final class BatchDexoptParams.Builder { + method @NonNull public com.android.server.art.model.BatchDexoptParams build(); + method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setDexoptParams(@NonNull com.android.server.art.model.DexoptParams); + method @NonNull public com.android.server.art.model.BatchDexoptParams.Builder setPackages(@NonNull java.util.List<java.lang.String>); + } + + public abstract class DeleteResult { + method public abstract long getFreedBytes(); + } + + public abstract class DexContainerFileUseInfo { + method @NonNull public abstract String getDexContainerFile(); + method @NonNull public abstract java.util.Set<java.lang.String> getLoadingPackages(); + method @NonNull public abstract android.os.UserHandle getUserHandle(); + } + + public class DexoptParams { + method @NonNull public String getCompilerFilter(); + method public int getFlags(); + method public int getPriorityClass(); + method @NonNull public String getReason(); + method @Nullable public String getSplitName(); + field public static final String COMPILER_FILTER_NOOP = "skip"; + } + + public static final class DexoptParams.Builder { + ctor public DexoptParams.Builder(@NonNull String); + ctor public DexoptParams.Builder(@NonNull String, int); + method @NonNull public com.android.server.art.model.DexoptParams build(); + method @NonNull public com.android.server.art.model.DexoptParams.Builder setCompilerFilter(@NonNull String); + method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int); + method @NonNull public com.android.server.art.model.DexoptParams.Builder setFlags(int, int); + method @NonNull public com.android.server.art.model.DexoptParams.Builder setPriorityClass(int); + method @NonNull public com.android.server.art.model.DexoptParams.Builder setSplitName(@Nullable String); + } + + public abstract class DexoptResult { + method public int getFinalStatus(); + method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.PackageDexoptResult> getPackageDexoptResults(); + method @NonNull public abstract String getReason(); + method @NonNull public abstract String getRequestedCompilerFilter(); + field public static final int DEXOPT_CANCELLED = 40; // 0x28 + field public static final int DEXOPT_FAILED = 30; // 0x1e + field public static final int DEXOPT_PERFORMED = 20; // 0x14 + field public static final int DEXOPT_SKIPPED = 10; // 0xa + } + + public abstract static class DexoptResult.DexContainerFileDexoptResult { + method @NonNull public abstract String getAbi(); + method @NonNull public abstract String getActualCompilerFilter(); + method public abstract long getDex2oatCpuTimeMillis(); + method public abstract long getDex2oatWallTimeMillis(); + method @NonNull public abstract String getDexContainerFile(); + method public abstract long getSizeBeforeBytes(); + method public abstract long getSizeBytes(); + method public abstract int getStatus(); + method public abstract boolean isPrimaryAbi(); + } + + public abstract static class DexoptResult.PackageDexoptResult { + method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult> getDexContainerFileDexoptResults(); + method @NonNull public abstract String getPackageName(); + method public int getStatus(); + method public boolean hasUpdatedArtifacts(); + } + + public abstract class DexoptStatus { + method @NonNull public abstract java.util.List<com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses(); + } + + public abstract static class DexoptStatus.DexContainerFileDexoptStatus { + method @NonNull public abstract String getAbi(); + method @NonNull public abstract String getCompilationReason(); + method @NonNull public abstract String getCompilerFilter(); + method @NonNull public abstract String getDexContainerFile(); + method @NonNull public abstract String getLocationDebugString(); + method public abstract boolean isPrimaryAbi(); + method public abstract boolean isPrimaryDex(); + } + + public abstract class OperationProgress { + method public int getPercentage(); } } diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt index c7d39e68c9..54ff0a13d2 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 0000000000..4445ecd682 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/AidlUtils.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext; +import static com.android.server.art.ProfilePath.PrebuiltProfilePath; +import static com.android.server.art.ProfilePath.PrimaryCurProfilePath; +import static com.android.server.art.ProfilePath.PrimaryRefProfilePath; +import static com.android.server.art.ProfilePath.SecondaryCurProfilePath; +import static com.android.server.art.ProfilePath.SecondaryRefProfilePath; +import static com.android.server.art.ProfilePath.TmpProfilePath; +import static com.android.server.art.ProfilePath.WritableProfilePath; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** @hide */ +public final class AidlUtils { + private AidlUtils() {} + + @NonNull + public static ArtifactsPath buildArtifactsPath( + @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) { + var artifactsPath = new ArtifactsPath(); + artifactsPath.dexPath = dexPath; + artifactsPath.isa = isa; + artifactsPath.isInDalvikCache = isInDalvikCache; + return artifactsPath; + } + + @NonNull + public static FsPermission buildFsPermission( + int uid, int gid, boolean isOtherReadable, boolean isOtherExecutable) { + var fsPermission = new FsPermission(); + fsPermission.uid = uid; + fsPermission.gid = gid; + fsPermission.isOtherReadable = isOtherReadable; + fsPermission.isOtherExecutable = isOtherExecutable; + return fsPermission; + } + + @NonNull + public static FsPermission buildFsPermission(int uid, int gid, boolean isOtherReadable) { + return buildFsPermission(uid, gid, isOtherReadable, false /* isOtherExecutable */); + } + + @NonNull + public static DexMetadataPath buildDexMetadataPath(@NonNull String dexPath) { + var dexMetadataPath = new DexMetadataPath(); + dexMetadataPath.dexPath = dexPath; + return dexMetadataPath; + } + + @NonNull + public static PermissionSettings buildPermissionSettings(@NonNull FsPermission dirFsPermission, + @NonNull FsPermission fileFsPermission, @Nullable SeContext seContext) { + var permissionSettings = new PermissionSettings(); + permissionSettings.dirFsPermission = dirFsPermission; + permissionSettings.fileFsPermission = fileFsPermission; + permissionSettings.seContext = seContext; + return permissionSettings; + } + + @NonNull + public static OutputArtifacts buildOutputArtifacts(@NonNull String dexPath, @NonNull String isa, + boolean isInDalvikCache, @NonNull PermissionSettings permissionSettings) { + var outputArtifacts = new OutputArtifacts(); + outputArtifacts.artifactsPath = buildArtifactsPath(dexPath, isa, isInDalvikCache); + outputArtifacts.permissionSettings = permissionSettings; + return outputArtifacts; + } + + @NonNull + public static PrimaryRefProfilePath buildPrimaryRefProfilePath( + @NonNull String packageName, @NonNull String profileName) { + var primaryRefProfilePath = new PrimaryRefProfilePath(); + primaryRefProfilePath.packageName = packageName; + primaryRefProfilePath.profileName = profileName; + return primaryRefProfilePath; + } + + @NonNull + public static SecondaryRefProfilePath buildSecondaryRefProfilePath(@NonNull String dexPath) { + var secondaryRefProfilePath = new SecondaryRefProfilePath(); + secondaryRefProfilePath.dexPath = dexPath; + return secondaryRefProfilePath; + } + + @NonNull + public static ProfilePath buildProfilePathForPrimaryRef( + @NonNull String packageName, @NonNull String profileName) { + return ProfilePath.primaryRefProfilePath( + buildPrimaryRefProfilePath(packageName, profileName)); + } + + @NonNull + public static ProfilePath buildProfilePathForPrebuilt(@NonNull String dexPath) { + var prebuiltProfilePath = new PrebuiltProfilePath(); + prebuiltProfilePath.dexPath = dexPath; + return ProfilePath.prebuiltProfilePath(prebuiltProfilePath); + } + + @NonNull + public static ProfilePath buildProfilePathForDm(@NonNull String dexPath) { + return ProfilePath.dexMetadataPath(buildDexMetadataPath(dexPath)); + } + + @NonNull + public static ProfilePath buildProfilePathForPrimaryCur( + int userId, @NonNull String packageName, @NonNull String profileName) { + var primaryCurProfilePath = new PrimaryCurProfilePath(); + primaryCurProfilePath.userId = userId; + primaryCurProfilePath.packageName = packageName; + primaryCurProfilePath.profileName = profileName; + return ProfilePath.primaryCurProfilePath(primaryCurProfilePath); + } + + @NonNull + public static ProfilePath buildProfilePathForSecondaryRef(@NonNull String dexPath) { + return ProfilePath.secondaryRefProfilePath(buildSecondaryRefProfilePath(dexPath)); + } + + @NonNull + public static ProfilePath buildProfilePathForSecondaryCur(@NonNull String dexPath) { + var secondaryCurProfilePath = new SecondaryCurProfilePath(); + secondaryCurProfilePath.dexPath = dexPath; + return ProfilePath.secondaryCurProfilePath(secondaryCurProfilePath); + } + + @NonNull + private static OutputProfile buildOutputProfile( + @NonNull WritableProfilePath finalPath, int uid, int gid, boolean isPublic) { + var outputProfile = new OutputProfile(); + outputProfile.profilePath = new TmpProfilePath(); + outputProfile.profilePath.finalPath = finalPath; + outputProfile.profilePath.id = ""; // Will be filled by artd. + outputProfile.profilePath.tmpPath = ""; // Will be filled by artd. + outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic); + return outputProfile; + } + + @NonNull + public static OutputProfile buildOutputProfileForPrimary(@NonNull String packageName, + @NonNull String profileName, int uid, int gid, boolean isPublic) { + return buildOutputProfile(WritableProfilePath.forPrimary( + buildPrimaryRefProfilePath(packageName, profileName)), + uid, gid, isPublic); + } + + @NonNull + public static OutputProfile buildOutputProfileForSecondary( + @NonNull String dexPath, int uid, int gid, boolean isPublic) { + return buildOutputProfile( + WritableProfilePath.forSecondary(buildSecondaryRefProfilePath(dexPath)), uid, gid, + isPublic); + } + + @NonNull + public static SeContext buildSeContext(@NonNull String seInfo, int uid) { + var seContext = new SeContext(); + seContext.seInfo = seInfo; + seContext.uid = uid; + return seContext; + } + + @NonNull + public static String toString(@NonNull PrimaryRefProfilePath profile) { + return String.format("PrimaryRefProfilePath[packageName = %s, profileName = %s]", + profile.packageName, profile.profileName); + } + + @NonNull + public static String toString(@NonNull SecondaryRefProfilePath profile) { + return String.format("SecondaryRefProfilePath[dexPath = %s]", profile.dexPath); + } + + @NonNull + public static String toString(@NonNull PrebuiltProfilePath profile) { + return String.format("PrebuiltProfilePath[dexPath = %s]", profile.dexPath); + } + + @NonNull + public static String toString(@NonNull DexMetadataPath profile) { + return String.format("DexMetadataPath[dexPath = %s]", profile.dexPath); + } + + @NonNull + public static String toString(@NonNull WritableProfilePath profile) { + switch (profile.getTag()) { + case WritableProfilePath.forPrimary: + return toString(profile.getForPrimary()); + case WritableProfilePath.forSecondary: + return toString(profile.getForSecondary()); + default: + throw new IllegalStateException( + "Unknown WritableProfilePath tag " + profile.getTag()); + } + } + + @NonNull + public static String toString(@NonNull ProfilePath profile) { + switch (profile.getTag()) { + case ProfilePath.primaryRefProfilePath: + return toString(profile.getPrimaryRefProfilePath()); + case ProfilePath.secondaryRefProfilePath: + return toString(profile.getSecondaryRefProfilePath()); + case ProfilePath.prebuiltProfilePath: + return toString(profile.getPrebuiltProfilePath()); + case ProfilePath.dexMetadataPath: + return toString(profile.getDexMetadataPath()); + default: + throw new UnsupportedOperationException( + "Only a subset of profile paths are supported to be converted to string, " + + "got " + profile.getTag()); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index 64aec7bf6b..378bfa46be 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -16,16 +16,1338 @@ package com.android.server.art; +import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; +import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; +import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo; +import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; +import static com.android.server.art.ReasonMapping.BatchDexoptReason; +import static com.android.server.art.ReasonMapping.BootReason; +import static com.android.server.art.Utils.Abi; +import static com.android.server.art.model.ArtFlags.GetStatusFlags; +import static com.android.server.art.model.ArtFlags.ScheduleStatus; +import static com.android.server.art.model.Config.Callback; +import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.job.JobInfo; +import android.apphibernation.AppHibernationManager; +import android.content.Context; +import android.os.Binder; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.BatchDexoptParams; +import com.android.server.art.model.Config; +import com.android.server.art.model.DeleteResult; +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.model.DexoptStatus; +import com.android.server.art.model.OperationProgress; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.DexFile; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * This class provides a system API for functionality provided by the ART module. * + * Note: Although this class is the entry point of ART services, this class is not a {@link + * SystemService}, and it does not publish a binder. Instead, it is a module loaded by the + * system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry} + * specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal} + * for consistency. + * * @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public final class ArtManagerLocal { - private static final String TAG = "ArtService"; + /** @hide */ + public static final String TAG = "ArtService"; + + private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = { + "BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"}; + + /** @hide */ + @VisibleForTesting public static final long DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES = 500_000_000; + + @NonNull private final Injector mInjector; + + @Deprecated + public ArtManagerLocal() { + mInjector = new Injector(this, null /* context */); + } + + /** + * Creates an instance. + * + * Only {@code SystemServer} should create an instance and register it in {@link + * LocalManagerRegistry}. Other API users should obtain the instance from {@link + * LocalManagerRegistry}. + * + * @param context the system server context + * @throws NullPointerException if required dependencies are missing + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public ArtManagerLocal(@NonNull Context context) { + mInjector = new Injector(this, context); + } + + /** @hide */ + @VisibleForTesting + public ArtManagerLocal(@NonNull Injector injector) { + mInjector = injector; + } + + /** + * Handles ART Service commands, which is a subset of `cmd package` commands. + * + * Note: This method is not an override of {@link Binder#handleShellCommand} because ART + * services does not publish a binder. Instead, it handles the commands forwarded by the + * `package` service. The semantics of the parameters are the same as {@link + * Binder#handleShellCommand}. + * + * @return zero on success, non-zero on internal error (e.g., I/O error) + * @throws SecurityException if the caller is not root + * @throws IllegalArgumentException if the arguments are illegal + * @see ArtShellCommand#printHelp(PrintWriter) + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return new ArtShellCommand(this, mInjector.getPackageManagerLocal()) + .exec(target, in.getFileDescriptor(), out.getFileDescriptor(), + err.getFileDescriptor(), args); + } + + /** Prints ART Service shell command help. */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void printShellCommandHelp(@NonNull PrintWriter pw) { + ArtShellCommand.printHelp(pw); + } + + /** + * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the + * ones for secondary dex files. This includes VDEX, ODEX, and ART files. + * + * @throws IllegalArgumentException if the package is not found or the flags are illegal + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DeleteResult deleteDexoptArtifacts( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + + try { + long freedBytes = 0; + + boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd()); + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + for (Abi abi : Utils.getAllAbis(pkgState)) { + freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath( + dexInfo.dexPath(), abi.isa(), isInDalvikCache)); + } + } + + for (SecondaryDexInfo dexInfo : + mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { + for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { + freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath( + dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */)); + } + } + + return DeleteResult.create(freedBytes); + } catch (RemoteException e) { + Utils.logArtdException(e); + return DeleteResult.create(0 /* freedBytes */); + } + } + + /** + * Returns the dexopt status of a package. + * + * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}). + * + * @throws IllegalArgumentException if the package is not found or the flags are illegal + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptStatus getDexoptStatus( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { + return getDexoptStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags()); + } + + /** + * Same as above, but allows to specify flags. + * + * @see #getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String) + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptStatus getDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String packageName, @GetStatusFlags int flags) { + if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0 + && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) { + throw new IllegalArgumentException("Nothing to check"); + } + + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + + List<Pair<DetailedDexInfo, Abi>> dexAndAbis = new ArrayList<>(); + + if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) { + for (DetailedPrimaryDexInfo dexInfo : + PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + for (Abi abi : Utils.getAllAbis(pkgState)) { + dexAndAbis.add(Pair.create(dexInfo, abi)); + } + } + } + + if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) { + for (SecondaryDexInfo dexInfo : + mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { + for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { + dexAndAbis.add(Pair.create(dexInfo, abi)); + } + } + } + + try { + List<DexContainerFileDexoptStatus> statuses = new ArrayList<>(); + + for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) { + DetailedDexInfo dexInfo = pair.first; + Abi abi = pair.second; + try { + GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus( + dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext()); + statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(), + dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), + abi.name(), result.compilerFilter, result.compilationReason, + result.locationDebugString)); + } catch (ServiceSpecificException e) { + statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(), + dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), + abi.name(), "error", "error", e.getMessage())); + } + } + + return DexoptStatus.create(statuses); + } catch (RemoteException e) { + Utils.logArtdException(e); + List<DexContainerFileDexoptStatus> statuses = new ArrayList<>(); + for (Pair<DetailedDexInfo, Abi> pair : dexAndAbis) { + DetailedDexInfo dexInfo = pair.first; + Abi abi = pair.second; + statuses.add(DexContainerFileDexoptStatus.create(dexInfo.dexPath(), + dexInfo instanceof DetailedPrimaryDexInfo, abi.isPrimaryAbi(), abi.name(), + "error", "error", e.getMessage())); + } + return DexoptStatus.create(statuses); + } + } + + /** + * Clear the profiles that are collected locally for the given package, including the profiles + * for primary and secondary dex files. More specifically, it clears reference profiles and + * current profiles. External profiles (e.g., cloud profiles) will be kept. + * + * @throws IllegalArgumentException if the package is not found or the flags are illegal + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public void clearAppProfiles( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + + try { + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + mInjector.getArtd().deleteProfile( + PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo)); + for (ProfilePath profile : PrimaryDexUtils.getCurProfiles( + mInjector.getUserManager(), pkgState, dexInfo)) { + mInjector.getArtd().deleteProfile(profile); + } + } + + // This only deletes the profiles of known secondary dex files. If there are unknown + // secondary dex files, their profiles will be deleted by `cleanup`. + for (SecondaryDexInfo dexInfo : + mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { + mInjector.getArtd().deleteProfile( + AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath())); + mInjector.getArtd().deleteProfile( + AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); + } + } catch (RemoteException e) { + Utils.logArtdException(e); + } + } + + /** + * Dexopts a package. The time this operation takes ranges from a few milliseconds to several + * minutes, depending on the params and the code size of the package. + * + * When this operation ends (either completed or cancelled), callbacks added by {@link + * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called. + * + * @throws IllegalArgumentException if the package is not found or the params are illegal + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String packageName, @NonNull DexoptParams params) { + var cancellationSignal = new CancellationSignal(); + return dexoptPackage(snapshot, packageName, params, cancellationSignal); + } + + /** + * Same as above, but supports cancellation. + * + * @see #dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, DexoptParams) + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptResult dexoptPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String packageName, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + return mInjector.getDexoptHelper().dexopt( + snapshot, List.of(packageName), params, cancellationSignal, Runnable::run); + } + + /** + * Resets the dexopt state of the package as if the package is newly installed. + * + * More specifically, it clears reference profiles, current profiles, and any code compiled from + * those local profiles. If there is an external profile (e.g., a cloud profile), the code + * compiled from that profile will be kept. + * + * For secondary dex files, it also clears all dexopt artifacts. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptResult resetDexoptStatus(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String packageName, @NonNull CancellationSignal cancellationSignal) { + // We must delete the artifacts for primary dex files beforehand rather than relying on + // `dexoptPackage` to replace them because: + // - If dexopt is not needed after the deletion, then we shouldn't run dexopt at all. For + // example, when we have a DM file that contains a VDEX file but doesn't contain a cloud + // profile, this happens. Note that this is more about correctness rather than + // performance. + // - We don't want the existing artifacts to affect dexopt. For example, the existing VDEX + // file should not be an input VDEX. + // + // We delete the artifacts for secondary dex files and `dexoptPackage` won't re-generate + // them because `dexoptPackage` for `REASON_INSTALL` is for primary dex only. This is + // intentional because secondary dex files are supposed to be unknown at install time. + deleteDexoptArtifacts(snapshot, packageName); + clearAppProfiles(snapshot, packageName); + + // Re-generate artifacts for primary dex files if needed. + return dexoptPackage(snapshot, packageName, + new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(), cancellationSignal); + } + + /** + * Runs batch dexopt for the given reason. + * + * This is called by ART Service automatically during boot / background dexopt. + * + * The list of packages and options are determined by {@code reason}, and can be overridden by + * {@link #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}. + * + * The dexopt is done in a thread pool. The number of packages being dexopted + * simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency} + * (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code + * dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads} + * (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of + * concurrent threads is the product of the two system properties. Note that the physical core + * usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of + * threads. + * + * When this operation ends (either completed or cancelled), callbacks added by {@link + * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called. + * + * If the storage is nearly low, and {@code reason} is {@link ReasonMapping#REASON_BG_DEXOPT}, + * it may also downgrade some inactive packages to a less optimized compiler filter, specified + * by the system property {@code pm.dexopt.inactive} (typically "verify"), to free up some + * space. This feature is only enabled when the system property {@code + * pm.dexopt.downgrade_after_inactive_days} is set. The space threshold to trigger this feature + * is the Storage Manager's low space threshold plus {@link + * #DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES}. The concurrency can be configured by system property + * {@code pm.dexopt.bg-dexopt.concurrency}. The packages in the list provided by + * {@link BatchDexoptStartCallback} for {@link ReasonMapping#REASON_BG_DEXOPT} are never + * downgraded. + * + * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on + * @param reason determines the default list of packages and options + * @param cancellationSignal provides the ability to cancel this operation + * @param processCallbackExecutor the executor to call {@code progressCallback} + * @param progressCallback called repeatedly whenever there is an update on the progress + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error), or the callback set by {@link + * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} provides invalid + * params. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptResult dexoptPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull @BatchDexoptReason String reason, + @NonNull CancellationSignal cancellationSignal, + @Nullable @CallbackExecutor Executor progressCallbackExecutor, + @Nullable Consumer<OperationProgress> progressCallback) { + List<String> defaultPackages = + Collections.unmodifiableList(getDefaultPackages(snapshot, reason)); + DexoptParams defaultDexoptParams = new DexoptParams.Builder(reason).build(); + var builder = new BatchDexoptParams.Builder(defaultPackages, defaultDexoptParams); + Callback<BatchDexoptStartCallback, Void> callback = + mInjector.getConfig().getBatchDexoptStartCallback(); + if (callback != null) { + Utils.executeAndWait(callback.executor(), () -> { + callback.get().onBatchDexoptStart( + snapshot, reason, defaultPackages, builder, cancellationSignal); + }); + } + BatchDexoptParams params = builder.build(); + Utils.check(params.getDexoptParams().getReason().equals(reason)); + + ExecutorService dexoptExecutor = + Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason)); + try { + if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) { + maybeDowngradePackages(snapshot, + new HashSet<>(params.getPackages()) /* excludedPackages */, + cancellationSignal, dexoptExecutor); + } + Log.i(TAG, "Dexopting packages"); + return mInjector.getDexoptHelper().dexopt(snapshot, params.getPackages(), + params.getDexoptParams(), cancellationSignal, dexoptExecutor, + progressCallbackExecutor, progressCallback); + } finally { + dexoptExecutor.shutdown(); + } + } + + /** + * Overrides the default params for {@link #dexoptPackages}. This method is thread-safe. + * + * This method gives users the opportunity to change the behavior of {@link #dexoptPackages}, + * which is called by ART Service automatically during boot / background dexopt. + * + * If this method is not called, the default list of packages and options determined by {@code + * reason} will be used. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void setBatchDexoptStartCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull BatchDexoptStartCallback callback) { + mInjector.getConfig().setBatchDexoptStartCallback(executor, callback); + } + + /** + * Clears the callback set by {@link + * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)}. This method is + * thread-safe. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void clearBatchDexoptStartCallback() { + mInjector.getConfig().clearBatchDexoptStartCallback(); + } + + /** + * Schedules a background dexopt job. Does nothing if the job is already scheduled. + * + * Use this method if you want the system to automatically determine the best time to run + * dexopt. + * + * The job will be run by the job scheduler. The job scheduling configuration can be overridden + * by {@link + * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. By + * default, it runs periodically (at most once a day) when all the following constraints are + * meet. + * + * <ul> + * <li>The device is idling. (see {@link JobInfo.Builder#setRequiresDeviceIdle(boolean)}) + * <li>The device is charging. (see {@link JobInfo.Builder#setRequiresCharging(boolean)}) + * <li>The battery level is not low. + * (see {@link JobInfo.Builder#setRequiresBatteryNotLow(boolean)}) + * </ul> + * + * When the job is running, it may be cancelled by the job scheduler immediately whenever one of + * the constraints above is no longer met or cancelled by the {@link + * #cancelBackgroundDexoptJob()} API. The job scheduler retries it in the next <i>maintenance + * window</i>. For information about <i>maintenance window</i>, see + * https://developer.android.com/training/monitoring-device-state/doze-standby. + * + * See {@link #dexoptPackages} for how to customize the behavior of the job. + * + * When the job ends (either completed or cancelled), the result is sent to the callbacks added + * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the + * reason {@link ReasonMapping#REASON_BG_DEXOPT}. + * + * @throws RuntimeException if called during boot before the job scheduler service has started. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public @ScheduleStatus int scheduleBackgroundDexoptJob() { + return mInjector.getBackgroundDexoptJob().schedule(); + } + + /** + * Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}. + * Does nothing if the job is not scheduled. + * + * Use this method if you no longer want the system to automatically run dexopt. + * + * If the job is already started by the job scheduler and is running, it will be cancelled + * immediately, and the result sent to the callbacks added by {@link + * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link + * DexoptResult#DEXOPT_CANCELLED}. Note that a job started by {@link + * #startBackgroundDexoptJob()} will not be cancelled by this method. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void unscheduleBackgroundDexoptJob() { + mInjector.getBackgroundDexoptJob().unschedule(); + } + + /** + * Overrides the configuration of the background dexopt job. This method is thread-safe. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull ScheduleBackgroundDexoptJobCallback callback) { + mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback); + } + + /** + * Clears the callback set by {@link + * #setScheduleBackgroundDexoptJobCallback(Executor, ScheduleBackgroundDexoptJobCallback)}. This + * method is thread-safe. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void clearScheduleBackgroundDexoptJobCallback() { + mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback(); + } + + /** + * Manually starts a background dexopt job. Does nothing if a job is already started by this + * method or by the job scheduler. This method is not blocking. + * + * Unlike the job started by job scheduler, the job started by this method does not respect + * constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be + * cancelled when they aren't met. + * + * See {@link #dexoptPackages} for how to customize the behavior of the job. + * + * When the job ends (either completed or cancelled), the result is sent to the callbacks added + * by {@link #addDexoptDoneCallback(Executor, DexoptDoneCallback)} with the + * reason {@link ReasonMapping#REASON_BG_DEXOPT}. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startBackgroundDexoptJob() { + mInjector.getBackgroundDexoptJob().start(); + } + + /** + * Same as above, but also returns a {@link CompletableFuture}. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public CompletableFuture<BackgroundDexoptJob.Result> startBackgroundDexoptJobAndReturnFuture() { + return mInjector.getBackgroundDexoptJob().start(); + } + + /** + * Returns the running background dexopt job, or null of no background dexopt job is running. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Nullable + public CompletableFuture<BackgroundDexoptJob.Result> getRunningBackgroundDexoptJob() { + return mInjector.getBackgroundDexoptJob().get(); + } + + /** + * Cancels the running background dexopt job started by the job scheduler or by {@link + * #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not + * blocking. + * + * The result sent to the callbacks added by {@link + * #addDexoptDoneCallback(Executor, DexoptDoneCallback)} will contain {@link + * DexoptResult#DEXOPT_CANCELLED}. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void cancelBackgroundDexoptJob() { + mInjector.getBackgroundDexoptJob().cancel(); + } + + /** + * Adds a global listener that listens to any result of dexopting package(s), no matter run + * manually or automatically. Calling this method multiple times with different callbacks is + * allowed. Callbacks are executed in the same order as the one in which they were added. This + * method is thread-safe. + * + * @param onlyIncludeUpdates if true, the results passed to the callback will only contain + * packages that have any update, and the callback won't be called with results that + * don't have any update. + * @throws IllegalStateException if the same callback instance is already added + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void addDexoptDoneCallback(boolean onlyIncludeUpdates, + @NonNull @CallbackExecutor Executor executor, @NonNull DexoptDoneCallback callback) { + mInjector.getConfig().addDexoptDoneCallback(onlyIncludeUpdates, executor, callback); + } + + /** + * Removes the listener added by {@link + * #addDexoptDoneCallback(Executor, DexoptDoneCallback)}. Does nothing if the + * callback was not added. This method is thread-safe. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) { + mInjector.getConfig().removeDexoptDoneCallback(callback); + } + + /** + * Snapshots the profile of the given app split. The profile snapshot is the aggregation of all + * existing profiles of the app split (all current user profiles and the reference profile). + * + * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on + * @param packageName the name of the app that owns the profile + * @param splitName see {@link AndroidPackageSplit#getName()} + * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The + * caller is responsible for closing it. Note that the content may be empty. + * @throws IllegalArgumentException if the package or the split is not found + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + * @throws SnapshotProfileException if the operation encounters an error that the caller should + * handle (e.g., an I/O error, a sub-process crash). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public ParcelFileDescriptor snapshotAppProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName) throws SnapshotProfileException { + var options = new MergeProfileOptions(); + options.forceMerge = true; + return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options); + } + + /** + * Same as above, but outputs in text format. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public ParcelFileDescriptor dumpAppProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName, boolean dumpClassesAndMethods) + throws SnapshotProfileException { + var options = new MergeProfileOptions(); + options.dumpOnly = !dumpClassesAndMethods; + options.dumpClassesAndMethods = dumpClassesAndMethods; + return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + private ParcelFileDescriptor snapshotOrDumpAppProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName, @NonNull MergeProfileOptions options) + throws SnapshotProfileException { + try { + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName); + + List<ProfilePath> profiles = new ArrayList<>(); + + Pair<ProfilePath, Boolean> pair = Utils.getOrInitReferenceProfile(mInjector.getArtd(), + dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo), + PrimaryDexUtils.getExternalProfiles(dexInfo), + PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID, + Process.SYSTEM_UID, false /* isPublic */)); + ProfilePath refProfile = pair != null ? pair.first : null; + + if (refProfile != null) { + profiles.add(refProfile); + } + + profiles.addAll( + PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo)); + + OutputProfile output = PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, + Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */); + + try { + return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options); + } finally { + if (refProfile != null && refProfile.getTag() == ProfilePath.tmpProfilePath) { + mInjector.getArtd().deleteProfile(refProfile); + } + } + } catch (RemoteException e) { + throw new SnapshotProfileException(e); + } + } + + /** + * Snapshots the boot image profile + * (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot + * is the aggregation of all existing profiles on the device (all current user profiles and + * reference profiles) of all apps and the system server filtered by applicable classpaths. + * + * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on + * @return the file descriptor of the snapshot. It doesn't have any path associated with it. The + * caller is responsible for closing it. Note that the content may be empty. + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + * @throws SnapshotProfileException if the operation encounters an error that the caller should + * handle (e.g., an I/O error, a sub-process crash). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public ParcelFileDescriptor snapshotBootImageProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot) + throws SnapshotProfileException { + if (!Constants.isBootImageProfilingEnabled()) { + throw new SnapshotProfileException("Boot image profiling not enabled"); + } + + List<ProfilePath> profiles = new ArrayList<>(); + + // System server profiles. + profiles.add(AidlUtils.buildProfilePathForPrimaryRef( + Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY)); + for (UserHandle handle : + mInjector.getUserManager().getUserHandles(true /* excludeDying */)) { + profiles.add(AidlUtils.buildProfilePathForPrimaryCur(handle.getIdentifier(), + Utils.PLATFORM_PACKAGE_NAME, PrimaryDexUtils.PROFILE_PRIMARY)); + } + + // App profiles. + snapshot.getPackageStates().forEach((packageName, appPkgState) -> { + // Hibernating apps can still provide useful profile contents, so skip the hibernation + // check. + if (Utils.canDexoptPackage(appPkgState, null /* appHibernationManager */)) { + AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState); + for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) { + if (!appDexInfo.hasCode()) { + continue; + } + profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo)); + profiles.addAll(PrimaryDexUtils.getCurProfiles( + mInjector.getUserManager(), appPkgState, appDexInfo)); + } + } + }); + + OutputProfile output = AidlUtils.buildOutputProfileForPrimary(Utils.PLATFORM_PACKAGE_NAME, + PrimaryDexUtils.PROFILE_PRIMARY, Process.SYSTEM_UID, Process.SYSTEM_UID, + false /* isPublic */); + + List<String> dexPaths = Arrays.stream(CLASSPATHS_FOR_BOOT_IMAGE_PROFILE) + .map(envVar -> Constants.getenv(envVar)) + .filter(classpath -> !TextUtils.isEmpty(classpath)) + .flatMap(classpath -> Arrays.stream(classpath.split(":"))) + .collect(Collectors.toList()); + + var options = new MergeProfileOptions(); + options.forceMerge = true; + options.forBootImage = true; + return mergeProfilesAndGetFd(profiles, output, dexPaths, options); + } + + /** + * Notifies ART Service that this is a boot that falls into one of the categories listed in + * {@link BootReason}. The current behavior is that ART Service goes through all recently used + * packages and dexopts those that are not dexopted. This might change in the future. + * + * This method is blocking. It takes about 30 seconds to a few minutes. During execution, {@code + * progressCallback} is repeatedly called whenever there is an update on the progress. + * + * See {@link #dexoptPackages} for how to customize the behavior. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void onBoot(@NonNull @BootReason String bootReason, + @Nullable @CallbackExecutor Executor progressCallbackExecutor, + @Nullable Consumer<OperationProgress> progressCallback) { + try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) { + dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor, + progressCallback); + } + } + + /** + * Dumps the dexopt state of all packages in text format for debugging purposes. + * + * There are no stability guarantees for the output format. + * + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void dump( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + new DumpHelper(this).dump(pw, snapshot); + } + + /** + * Dumps the dexopt state of the given package in text format for debugging purposes. + * + * There are no stability guarantees for the output format. + * + * @throws IllegalArgumentException if the package is not found + * @throws IllegalStateException if the operation encounters an error that should never happen + * (e.g., an internal logic error). + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void dumpPackage(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { + new DumpHelper(this).dumpPackage( + pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName)); + } + + /** + * Cleans up obsolete profiles and artifacts. + * + * This is done in a mark-and-sweep approach. + * + * @return The amount of the disk space freed by the cleanup, in bytes. + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public long cleanup(@NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + mInjector.getDexUseManager().cleanup(); + + try { + // For every primary dex container file or secondary dex container file of every app, if + // it has code, we keep the following types of files: + // - The reference profile and the current profiles, regardless of the hibernation state + // of the app. + // - The dexopt artifacts, if they are up-to-date and the app is not hibernating. + // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated + // but the VDEX part is still usable and the app is not hibernating. + List<ProfilePath> profilesToKeep = new ArrayList<>(); + List<ArtifactsPath> artifactsToKeep = new ArrayList<>(); + List<VdexPath> vdexFilesToKeep = new ArrayList<>(); + + for (PackageState pkgState : snapshot.getPackageStates().values()) { + if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) { + continue; + } + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd()); + boolean keepArtifacts = !Utils.shouldSkipDexoptDueToHibernation( + pkgState, mInjector.getAppHibernationManager()); + for (DetailedPrimaryDexInfo dexInfo : + PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + profilesToKeep.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo)); + profilesToKeep.addAll(PrimaryDexUtils.getCurProfiles( + mInjector.getUserManager(), pkgState, dexInfo)); + if (keepArtifacts) { + for (Abi abi : Utils.getAllAbis(pkgState)) { + maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo, + abi, isInDalvikCache); + } + } + } + for (DetailedSecondaryDexInfo dexInfo : + mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo( + pkgState.getPackageName())) { + profilesToKeep.add( + AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath())); + profilesToKeep.add( + AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); + if (keepArtifacts) { + for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { + maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, pkgState, dexInfo, + abi, false /* isInDalvikCache */); + } + } + } + } + return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep); + } catch (RemoteException e) { + Utils.logArtdException(e); + return 0; + } + } + + /** + * Checks if the artifacts are up-to-date, and maybe adds them to {@code artifactsToKeep} or + * {@code vdexFilesToKeep} based on the result. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep, + @NonNull List<VdexPath> vdexFilesToKeep, @NonNull PackageState pkgState, + @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi, boolean isInDalvikCache) + throws RemoteException { + try { + GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus( + dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext()); + if (DexFile.isValidCompilerFilter(result.compilerFilter)) { + // TODO(b/263579377): This is a bit inaccurate. We may be keeping the artifacts in + // dalvik-cache while OatFileAssistant actually picks the ones not in dalvik-cache. + // However, this isn't a big problem because it is an edge case and it only causes + // us to delete less rather than deleting more. + ArtifactsPath artifacts = + AidlUtils.buildArtifactsPath(dexInfo.dexPath(), abi.isa(), isInDalvikCache); + if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) { + // Only the VDEX file is usable. + vdexFilesToKeep.add(VdexPath.artifactsPath(artifacts)); + } else { + artifactsToKeep.add(artifacts); + } + } + } catch (ServiceSpecificException e) { + // Don't add the artifacts to the lists. They should be cleaned up. + Log.e(TAG, + String.format("Failed to get dexopt status [packageName = %s, dexPath = %s, " + + "isa = %s, classLoaderContext = %s]", + pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(), + dexInfo.classLoaderContext()), + e); + } + } + + /** + * Should be used by {@link BackgroundDexoptJobService} ONLY. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + BackgroundDexoptJob getBackgroundDexoptJob() { + return mInjector.getBackgroundDexoptJob(); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private void maybeDowngradePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull Set<String> excludedPackages, @NonNull CancellationSignal cancellationSignal, + @NonNull Executor executor) { + if (shouldDowngrade()) { + List<String> packages = getDefaultPackages(snapshot, ReasonMapping.REASON_INACTIVE) + .stream() + .filter(pkg -> !excludedPackages.contains(pkg)) + .collect(Collectors.toList()); + if (!packages.isEmpty()) { + Log.i(TAG, "Storage is low. Downgrading inactive packages"); + DexoptParams params = + new DexoptParams.Builder(ReasonMapping.REASON_INACTIVE).build(); + mInjector.getDexoptHelper().dexopt(snapshot, packages, params, cancellationSignal, + executor, null /* processCallbackExecutor */, null /* progressCallback */); + } else { + Log.i(TAG, + "Storage is low, but downgrading is disabled or there's nothing to " + + "downgrade"); + } + } + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private boolean shouldDowngrade() { + try { + return mInjector.getStorageManager().getAllocatableBytes(StorageManager.UUID_DEFAULT) + < DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES; + } catch (IOException e) { + Log.e(TAG, "Failed to check storage. Assuming storage not low", e); + return false; + } + } + + /** Returns the list of packages to process for the given reason. */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull /* @BatchDexoptReason|REASON_INACTIVE */ String reason) { + var appHibernationManager = mInjector.getAppHibernationManager(); + + // Filter out hibernating packages even if the reason is REASON_INACTIVE. This is because + // artifacts for hibernating packages are already deleted. + Stream<PackageState> packages = snapshot.getPackageStates().values().stream().filter( + pkgState -> Utils.canDexoptPackage(pkgState, appHibernationManager)); + + switch (reason) { + case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE: + packages = packages.filter(pkgState + -> mInjector.isSystemUiPackage(pkgState.getPackageName()) + || mInjector.isLauncherPackage(pkgState.getPackageName())); + break; + case ReasonMapping.REASON_INACTIVE: + packages = filterAndSortByLastActiveTime( + packages, false /* keepRecent */, false /* descending */); + break; + default: + // Actually, the sorting is only needed for background dexopt, but we do it for all + // cases for simplicity. + packages = filterAndSortByLastActiveTime( + packages, true /* keepRecent */, true /* descending */); + } + + return packages.map(PackageState::getPackageName).collect(Collectors.toList()); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + private Stream<PackageState> filterAndSortByLastActiveTime( + @NonNull Stream<PackageState> packages, boolean keepRecent, boolean descending) { + // "pm.dexopt.downgrade_after_inactive_days" is repurposed to also determine whether to + // dexopt a package. + long inactiveMs = TimeUnit.DAYS.toMillis(SystemProperties.getInt( + "pm.dexopt.downgrade_after_inactive_days", Integer.MAX_VALUE /* def */)); + long currentTimeMs = mInjector.getCurrentTimeMillis(); + long thresholdTimeMs = currentTimeMs - inactiveMs; + return packages + .map(pkgState + -> Pair.create(pkgState, + Utils.getPackageLastActiveTime(pkgState, + mInjector.getDexUseManager(), mInjector.getUserManager()))) + .filter(keepRecent ? (pair -> pair.second > thresholdTimeMs) + : (pair -> pair.second <= thresholdTimeMs)) + .sorted(descending ? Comparator.comparingLong(pair -> - pair.second) + : Comparator.comparingLong(pair -> pair.second)) + .map(pair -> pair.first); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles, + @NonNull OutputProfile output, @NonNull List<String> dexPaths, + @NonNull MergeProfileOptions options) throws SnapshotProfileException { + try { + boolean hasContent = false; + try { + hasContent = mInjector.getArtd().mergeProfiles( + profiles, null /* referenceProfile */, output, dexPaths, options); + } catch (ServiceSpecificException e) { + throw new SnapshotProfileException(e); + } + + String path; + Path emptyFile = null; + if (hasContent) { + path = output.profilePath.tmpPath; + } else { + // We cannot use /dev/null because `ParcelFileDescriptor` have an API `getStatSize`, + // which expects the file to be a regular file or a link, and apps may call that + // API. + emptyFile = + Files.createTempFile(Paths.get(mInjector.getTempDir()), "empty", ".tmp"); + path = emptyFile.toString(); + } + ParcelFileDescriptor fd; + try { + fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY); + } catch (FileNotFoundException e) { + throw new IllegalStateException( + String.format("Failed to open profile snapshot '%s'", path), e); + } + + // The deletion is done on the open file so that only the FD keeps a reference to the + // file. + if (hasContent) { + mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath)); + } else { + Files.delete(emptyFile); + } + + return fd; + } catch (IOException | RemoteException e) { + throw new SnapshotProfileException(e); + } + } + + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public interface BatchDexoptStartCallback { + /** + * Mutates {@code builder} to override the default params for {@link #dexoptPackages}. It + * must ignore unknown reasons because more reasons may be added in the future. + * + * This is called before the start of any automatic package dexopt (i.e., not + * including package dexopt initiated by the {@link #dexoptPackage} API call). + * + * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the + * list of packages to dexopt. + * + * If {@code builder.setDexoptParams} is not called, the default params built from {@code + * new DexoptParams.Builder(reason)} will to used as the params for dexopting each + * package. + * + * Additionally, {@code cancellationSignal.cancel()} can be called to cancel this operation. + * If this operation is initiated by the job scheduler and the {@code reason} is {@link + * ReasonMapping#REASON_BG_DEXOPT}, the job will be retried in the next <i>maintenance + * window</i>. For information about <i>maintenance window</i>, see + * https://developer.android.com/training/monitoring-device-state/doze-standby. + * + * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException} + * when {@link #dexoptPackages} is called. + */ + void onBatchDexoptStart(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull @BatchDexoptReason String reason, @NonNull List<String> defaultPackages, + @NonNull BatchDexoptParams.Builder builder, + @NonNull CancellationSignal cancellationSignal); + } + + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public interface ScheduleBackgroundDexoptJobCallback { + /** + * Mutates {@code builder} to override the configuration of the background dexopt job. + * + * The default configuration described in {@link + * ArtManagerLocal#scheduleBackgroundDexoptJob()} is passed to the callback as the {@code + * builder} argument. + * + * Setting {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)} is not allowed. Doing + * so will result in {@link IllegalStateException} when {@link + * #scheduleBackgroundDexoptJob()} is called. ART Service has its own storage check, which + * skips package dexopt when the storage is low. The storage check is enabled by + * default for background dexopt jobs. {@link + * #setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback)} can be used to disable + * the storage check by clearing the {@link ArtFlags#FLAG_SKIP_IF_STORAGE_LOW} flag. + */ + void onOverrideJobInfo(@NonNull JobInfo.Builder builder); + } + + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public interface DexoptDoneCallback { + void onDexoptDone(@NonNull DexoptResult result); + } + + /** + * Represents an error that happens when snapshotting profiles. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static class SnapshotProfileException extends Exception { + /** @hide */ + public SnapshotProfileException(@NonNull Throwable cause) { + super(cause); + } + + /** @hide */ + public SnapshotProfileException(@NonNull String message) { + super(message); + } + } + + /** + * Injector pattern for testing purpose. + * + * @hide + */ + @VisibleForTesting + public static class Injector { + @Nullable private final ArtManagerLocal mArtManagerLocal; + @Nullable private final Context mContext; + @Nullable private final PackageManagerLocal mPackageManagerLocal; + @Nullable private final Config mConfig; + @Nullable private BackgroundDexoptJob mBgDexoptJob = null; + + // TODO(jiakaiz): Remove @SuppressLint and check `Build.VERSION.SDK_INT >= + // Build.VERSION_CODES.UPSIDE_DOWN_CAKE` once the SDK is finalized. + @SuppressLint("NewApi") + Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) { + mArtManagerLocal = artManagerLocal; + mContext = context; + if (context != null) { + // We only need them on Android U and above, where a context is passed. + mPackageManagerLocal = Objects.requireNonNull( + LocalManagerRegistry.getManager(PackageManagerLocal.class)); + mConfig = new Config(); + + // Call the getters for the dependencies that aren't optional, to ensure correct + // initialization order. + getDexoptHelper(); + getUserManager(); + getDexUseManager(); + getStorageManager(); + ArtModuleServiceInitializer.getArtModuleServiceManager(); + } else { + mPackageManagerLocal = null; + mConfig = null; + } + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public Context getContext() { + return Objects.requireNonNull(mContext); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public PackageManagerLocal getPackageManagerLocal() { + return Objects.requireNonNull(mPackageManagerLocal); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public IArtd getArtd() { + return Utils.getArtd(); + } + + /** Returns a new {@link DexoptHelper} instance. */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexoptHelper getDexoptHelper() { + return new DexoptHelper(getContext(), getConfig()); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public Config getConfig() { + return mConfig; + } + + /** Returns the registered {@link AppHibernationManager} instance. */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public AppHibernationManager getAppHibernationManager() { + return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class)); + } + + /** + * Returns the {@link BackgroundDexoptJob} instance. + * + * @throws RuntimeException if called during boot before the job scheduler service has + * started. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public synchronized BackgroundDexoptJob getBackgroundDexoptJob() { + if (mBgDexoptJob == null) { + mBgDexoptJob = new BackgroundDexoptJob(mContext, mArtManagerLocal, mConfig); + } + return mBgDexoptJob; + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public UserManager getUserManager() { + return Objects.requireNonNull(mContext.getSystemService(UserManager.class)); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public DexUseManagerLocal getDexUseManager() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(DexUseManagerLocal.class)); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public boolean isSystemUiPackage(@NonNull String packageName) { + return Utils.isSystemUiPackage(mContext, packageName); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public boolean isLauncherPackage(@NonNull String packageName) { + return Utils.isLauncherPackage(mContext, packageName); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public StorageManager getStorageManager() { + return Objects.requireNonNull(mContext.getSystemService(StorageManager.class)); + } - public ArtManagerLocal() {} + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public String getTempDir() { + // This is a path that system_server is known to have full access to. + return "/data/system"; + } + } } diff --git a/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java new file mode 100644 index 0000000000..c9295c1a10 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ArtModuleServiceInitializer.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.ArtModuleServiceManager; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; + +/** + * Class for performing registration for the ART mainline module. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class ArtModuleServiceInitializer { + private ArtModuleServiceInitializer() {} + + @NonNull private static Object sLock = new Object(); + @GuardedBy("sLock") @Nullable private static ArtModuleServiceManager sArtModuleServiceManager; + + /** + * Sets an instance of {@link ArtModuleServiceManager} that allows the ART mainline module to + * obtain ART binder services. This is called by the platform during the system server + * initialization. + */ + public static void setArtModuleServiceManager( + @NonNull ArtModuleServiceManager artModuleServiceManager) { + synchronized (sLock) { + if (sArtModuleServiceManager != null) { + throw new IllegalStateException("ArtModuleServiceManager is already set"); + } + sArtModuleServiceManager = artModuleServiceManager; + } + } + + /** @hide */ + @NonNull + public static ArtModuleServiceManager getArtModuleServiceManager() { + synchronized (sLock) { + return Objects.requireNonNull(sArtModuleServiceManager); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java new file mode 100644 index 0000000000..b83f4ab25d --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java @@ -0,0 +1,938 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static android.os.ParcelFileDescriptor.AutoCloseInputStream; + +import static com.android.server.art.ArtManagerLocal.SnapshotProfileException; +import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; +import static com.android.server.art.model.ArtFlags.DexoptFlags; +import static com.android.server.art.model.ArtFlags.PriorityClassApi; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.DexoptResultStatus; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; +import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Binder; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; +import com.android.modules.utils.BasicShellCommandHandler; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DeleteResult; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.model.DexoptStatus; +import com.android.server.art.model.OperationProgress; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import libcore.io.Streams; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +/** + * This class handles ART shell commands. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public final class ArtShellCommand extends BasicShellCommandHandler { + private static final String TAG = ArtManagerLocal.TAG; + + /** The default location for profile dumps. */ + private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman"; + + private final ArtManagerLocal mArtManagerLocal; + private final PackageManagerLocal mPackageManagerLocal; + + @GuardedBy("sCancellationSignalMap") + @NonNull + private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>(); + + public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal, + @NonNull PackageManagerLocal packageManagerLocal) { + mArtManagerLocal = artManagerLocal; + mPackageManagerLocal = packageManagerLocal; + } + + @Override + public int onCommand(String cmd) { + // Apps shouldn't call ART Service shell commands, not even for dexopting themselves. + enforceRootOrShell(); + PrintWriter pw = getOutPrintWriter(); + try (var snapshot = mPackageManagerLocal.withFilteredSnapshot()) { + switch (cmd) { + case "compile": + return handleCompile(pw, snapshot); + case "reconcile-secondary-dex-files": + pw.println("Warning: 'pm reconcile-secondary-dex-files' is deprecated. It is " + + "now doing nothing"); + return 0; + case "force-dex-opt": + return handleForceDexopt(pw, snapshot); + case "bg-dexopt-job": + return handleBgDexoptJob(pw, snapshot); + case "cancel-bg-dexopt-job": + pw.println("Warning: 'pm cancel-bg-dexopt-job' is deprecated. It is now an " + + "alias of 'pm bg-dexopt-job --cancel'"); + return handleCancelBgDexoptJob(pw); + case "delete-dexopt": + return handleDeleteDexopt(pw, snapshot); + case "dump-profiles": + return handleDumpProfile(pw, snapshot); + case "snapshot-profile": + return handleSnapshotProfile(pw, snapshot); + case "art": + return handleArtCommand(pw, snapshot); + default: + // Can't happen. Only supported commands are forwarded to ART Service. + throw new IllegalArgumentException( + String.format("Unexpected command '%s' forwarded to ART Service", cmd)); + } + } catch (IllegalArgumentException | SnapshotProfileException e) { + pw.println("Error: " + e.getMessage()); + return 1; + } + } + + private int handleArtCommand( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + String subcmd = getNextArgRequired(); + switch (subcmd) { + case "dexopt-packages": { + return handleBatchDexopt(pw, snapshot); + } + case "cancel": { + String jobId = getNextArgRequired(); + CancellationSignal signal; + synchronized (sCancellationSignalMap) { + signal = sCancellationSignalMap.getOrDefault(jobId, null); + } + if (signal == null) { + pw.println("Job not found"); + return 1; + } + signal.cancel(); + pw.println("Job cancelled"); + return 0; + } + case "dump": { + String packageName = getNextArg(); + if (packageName != null) { + mArtManagerLocal.dumpPackage(pw, snapshot, packageName); + } else { + mArtManagerLocal.dump(pw, snapshot); + } + return 0; + } + case "cleanup": { + return handleCleanup(pw, snapshot); + } + case "clear-app-profiles": { + mArtManagerLocal.clearAppProfiles(snapshot, getNextArgRequired()); + pw.println("Profiles cleared"); + return 0; + } + default: + pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd); + pw.println("See 'pm help' for help"); + return 1; + } + } + + private int handleCompile( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + @DexoptFlags int scopeFlags = 0; + String reason = null; + String compilerFilter = null; + @PriorityClassApi int priorityClass = ArtFlags.PRIORITY_NONE; + String splitArg = null; + boolean force = false; + boolean reset = false; + boolean forAllPackages = false; + boolean legacyClearProfile = false; + boolean verbose = false; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-a": + forAllPackages = true; + break; + case "-r": + reason = getNextArgRequired(); + break; + case "-m": + compilerFilter = getNextArgRequired(); + break; + case "-p": + priorityClass = parsePriorityClass(getNextArgRequired()); + break; + case "-f": + force = true; + break; + case "--primary-dex": + scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX; + break; + case "--secondary-dex": + scopeFlags |= ArtFlags.FLAG_FOR_SECONDARY_DEX; + break; + case "--include-dependencies": + scopeFlags |= ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES; + break; + case "--full": + scopeFlags |= ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES; + break; + case "--split": + splitArg = getNextArgRequired(); + break; + case "--reset": + reset = true; + break; + case "-c": + pw.println("Warning: Flag '-c' is deprecated and usually produces undesired " + + "results. Please use one of the following commands instead."); + pw.println("- To clear the local profiles only, use " + + "'pm art clear-app-profiles PACKAGE_NAME'. (The existing dexopt " + + "artifacts will be kept, even if they are derived from the " + + "profiles.)"); + pw.println("- To clear the local profiles and also clear the dexopt artifacts " + + "that are derived from them, use 'pm compile --reset PACKAGE_NAME'. " + + "(The package will be reset to the initial state as if it's newly " + + "installed, which means the package will be re-dexopted if " + + "necessary, and cloud profiles will be used if exist.)"); + pw.println("- To re-dexopt the package with no profile, use " + + "'pm compile -m verify -f PACKAGE_NAME'. (The local profiles " + + "will be kept but not used during the dexopt. The dexopt artifacts " + + "are guaranteed to have no compiled code.)"); + legacyClearProfile = true; + break; + case "--check-prof": + getNextArgRequired(); + pw.println("Warning: Ignoring obsolete flag '--check-prof'. It is " + + "unconditionally enabled now"); + break; + case "-v": + verbose = true; + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + List<String> packageNames = forAllPackages + ? List.copyOf(snapshot.getPackageStates().keySet()) + : List.of(getNextArgRequired()); + + var paramsBuilder = new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE); + if (reason != null) { + if (reason.equals(ReasonMapping.REASON_INACTIVE)) { + pw.println("Warning: '-r inactive' produces undesired results."); + } + if (compilerFilter == null) { + paramsBuilder.setCompilerFilter(ReasonMapping.getCompilerFilterForReason(reason)); + } + if (priorityClass == ArtFlags.PRIORITY_NONE) { + paramsBuilder.setPriorityClass(ReasonMapping.getPriorityClassForReason(reason)); + } + } + if (compilerFilter != null) { + paramsBuilder.setCompilerFilter(compilerFilter); + } + if (priorityClass != ArtFlags.PRIORITY_NONE) { + paramsBuilder.setPriorityClass(priorityClass); + } + if (force) { + paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE); + } + if (splitArg != null) { + if (scopeFlags != 0) { + pw.println("Error: '--primary-dex', '--secondary-dex', " + + "'--include-dependencies', or '--full' must not be set when '--split' " + + "is set."); + return 1; + } + if (forAllPackages) { + pw.println("Error: '-a' cannot be specified together with '--split'"); + return 1; + } + scopeFlags = ArtFlags.FLAG_FOR_PRIMARY_DEX; + paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT, ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName(getSplitName(pw, snapshot, packageNames.get(0), splitArg)); + } + if (scopeFlags != 0) { + paramsBuilder.setFlags(scopeFlags, + ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES); + } else { + paramsBuilder.setFlags( + ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES, + ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES); + } + if (forAllPackages) { + // We'll iterate over all packages anyway. + paramsBuilder.setFlags(0, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES); + } + + if (reset) { + return resetPackages(pw, snapshot, packageNames, verbose); + } else { + if (legacyClearProfile) { + // For compat only. Combining this with dexopt usually produces in undesired + // results. + for (String packageName : packageNames) { + mArtManagerLocal.clearAppProfiles(snapshot, packageName); + } + } + return dexoptPackages(pw, snapshot, packageNames, paramsBuilder.build(), verbose); + } + } + + private int handleForceDexopt( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + pw.println("Warning: 'pm force-dex-opt' is deprecated. Please use 'pm compile " + + "-f PACKAGE_NAME' instead"); + return dexoptPackages(pw, snapshot, List.of(getNextArgRequired()), + new DexoptParams.Builder(ReasonMapping.REASON_CMDLINE) + .setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE) + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES, + ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(), + false /* verbose */); + } + + private int handleBgDexoptJob( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + String opt = getNextOption(); + if (opt == null) { + List<String> packageNames = new ArrayList<>(); + String arg; + while ((arg = getNextArg()) != null) { + packageNames.add(arg); + } + if (!packageNames.isEmpty()) { + pw.println("Warning: Running 'pm bg-dexopt-job' with package names is deprecated. " + + "Please use 'pm compile -r bg-dexopt PACKAGE_NAME' instead"); + return dexoptPackages(pw, snapshot, packageNames, + new DexoptParams.Builder(ReasonMapping.REASON_BG_DEXOPT).build(), + false /* verbose */); + } + + CompletableFuture<BackgroundDexoptJob.Result> runningJob = + mArtManagerLocal.getRunningBackgroundDexoptJob(); + if (runningJob != null) { + pw.println("Another job already running. Waiting for it to finish... To cancel it, " + + "run 'pm bg-dexopt-job --cancel'. in a separate shell."); + pw.flush(); + Utils.getFuture(runningJob); + } + CompletableFuture<BackgroundDexoptJob.Result> future = + mArtManagerLocal.startBackgroundDexoptJobAndReturnFuture(); + pw.println("Job running... To cancel it, run 'pm bg-dexopt-job --cancel'. in a " + + "separate shell."); + pw.flush(); + BackgroundDexoptJob.Result result = Utils.getFuture(future); + if (result instanceof BackgroundDexoptJob.CompletedResult) { + var completedResult = (BackgroundDexoptJob.CompletedResult) result; + if (completedResult.dexoptResult().getFinalStatus() + == DexoptResult.DEXOPT_CANCELLED) { + pw.println("Job cancelled. See logs for details"); + } else { + pw.println("Job finished. See logs for details"); + } + } else if (result instanceof BackgroundDexoptJob.FatalErrorResult) { + // Never expected. + pw.println("Job encountered a fatal error"); + } + return 0; + } + switch (opt) { + case "--cancel": { + return handleCancelBgDexoptJob(pw); + } + case "--enable": { + // This operation requires the uid to be "system" (1000). + long identityToken = Binder.clearCallingIdentity(); + try { + mArtManagerLocal.scheduleBackgroundDexoptJob(); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + pw.println("Background dexopt job enabled"); + return 0; + } + case "--disable": { + // This operation requires the uid to be "system" (1000). + long identityToken = Binder.clearCallingIdentity(); + try { + mArtManagerLocal.unscheduleBackgroundDexoptJob(); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + pw.println("Background dexopt job disabled"); + return 0; + } + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + private int handleCancelBgDexoptJob(@NonNull PrintWriter pw) { + mArtManagerLocal.cancelBackgroundDexoptJob(); + pw.println("Background dexopt job cancelled"); + return 0; + } + + private int handleCleanup( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + long freedBytes = mArtManagerLocal.cleanup(snapshot); + pw.printf("Freed %d bytes\n", freedBytes); + return 0; + } + + private int handleDeleteDexopt( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + DeleteResult result = + mArtManagerLocal.deleteDexoptArtifacts(snapshot, getNextArgRequired()); + pw.printf("Freed %d bytes\n", result.getFreedBytes()); + return 0; + } + + private int handleSnapshotProfile( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) + throws SnapshotProfileException { + String splitName = null; + String codePath = null; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--split": + splitName = getNextArgRequired(); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + String packageName = getNextArgRequired(); + + if ("--code-path".equals(getNextOption())) { + pw.println("Warning: Specifying a split using '--code-path' is deprecated. Please use " + + "'--split SPLIT_NAME' instead"); + pw.println("Tip: '--split SPLIT_NAME' must be passed before the package name"); + codePath = getNextArgRequired(); + } + + if (splitName != null && codePath != null) { + pw.println("Error: '--split' and '--code-path' cannot be both specified"); + return 1; + } + + if (packageName.equals(Utils.PLATFORM_PACKAGE_NAME)) { + if (splitName != null) { + pw.println("Error: '--split' must not be specified for boot image profile"); + return 1; + } + if (codePath != null) { + pw.println("Error: '--code-path' must not be specified for boot image profile"); + return 1; + } + return handleSnapshotBootProfile(pw, snapshot); + } + + if (splitName != null && splitName.isEmpty()) { + splitName = null; + } + if (codePath != null) { + splitName = getSplitNameByFullPath(snapshot, packageName, codePath); + } + + return handleSnapshotAppProfile(pw, snapshot, packageName, splitName); + } + + private int handleSnapshotBootProfile( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) + throws SnapshotProfileException { + String outputRelativePath = "android.prof"; + ParcelFileDescriptor fd = mArtManagerLocal.snapshotBootImageProfile(snapshot); + writeProfileFdContentsToFile(pw, fd, outputRelativePath); + return 0; + } + + private int handleSnapshotAppProfile(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName) throws SnapshotProfileException { + String outputRelativePath = String.format("%s%s.prof", packageName, + splitName != null ? String.format("-split_%s.apk", splitName) : ""); + ParcelFileDescriptor fd = + mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName); + writeProfileFdContentsToFile(pw, fd, outputRelativePath); + return 0; + } + + private int handleDumpProfile( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) + throws SnapshotProfileException { + boolean dumpClassesAndMethods = false; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--dump-classes-and-methods": { + dumpClassesAndMethods = true; + break; + } + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + String packageName = getNextArgRequired(); + + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + try (var tracing = new Utils.Tracing("dump profiles")) { + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName()); + // The path is intentionally inconsistent with the one for "snapshot-profile". This + // is to match the behavior of the legacy PM shell command. + String outputRelativePath = + String.format("%s-%s.prof.txt", packageName, profileName); + ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile( + snapshot, packageName, dexInfo.splitName(), dumpClassesAndMethods); + writeProfileFdContentsToFile(pw, fd, outputRelativePath); + } + } + return 0; + } + + private int handleBatchDexopt( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + String reason = null; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-r": + reason = getNextArgRequired(); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + if (reason == null) { + pw.println("Error: '-r REASON' is required"); + return 1; + } + if (!ReasonMapping.BATCH_DEXOPT_REASONS.contains(reason)) { + pw.printf("Error: Invalid batch dexopt reason '%s'. Valid values are: %s\n", reason, + ReasonMapping.BATCH_DEXOPT_REASONS); + return 1; + } + DexoptResult result; + ExecutorService progressCallbackExecutor = Executors.newSingleThreadExecutor(); + try (var signal = new WithCancellationSignal(pw, true /* verbose */)) { + result = mArtManagerLocal.dexoptPackages( + snapshot, reason, signal.get(), progressCallbackExecutor, progress -> { + pw.println(String.format("Dexopting apps: %d%%", progress.getPercentage())); + pw.flush(); + }); + Utils.executeAndWait(progressCallbackExecutor, () -> { + printDexoptResult(pw, result, true /* verbose */, true /* multiPackage */); + }); + } finally { + progressCallbackExecutor.shutdown(); + } + return 0; + } + + @Override + public void onHelp() { + // No one should call this. The help text should be printed by the `onHelp` handler of `cmd + // package`. + throw new UnsupportedOperationException("Unexpected call to 'onHelp'"); + } + + public static void printHelp(@NonNull PrintWriter pw) { + pw.println("compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f]"); + pw.println(" [--primary-dex] [--secondary-dex] [--include-dependencies] [--full]"); + pw.println(" [--split SPLIT_NAME] [--reset] [-a | PACKAGE_NAME]"); + pw.println(" Dexopt a package or all packages."); + pw.println(" Options:"); + pw.println(" -a Dexopt all packages"); + pw.println(" -r Set the compiler filter and the priority based on the given"); + pw.println(" compilation reason."); + pw.println(" Available options: 'first-boot', 'boot-after-ota',"); + pw.println(" 'boot-after-mainline-update', 'install', 'bg-dexopt', 'cmdline'."); + pw.println(" -m Set the target compiler filter. The filter actually used may be"); + pw.println(" different, e.g. 'speed-profile' without profiles present may result in"); + pw.println(" 'verify' being used instead. If not specified, this defaults to the"); + pw.println(" value given by -r, or the system property 'pm.dexopt.cmdline'."); + pw.println(" Available options (in descending order): 'speed', 'speed-profile',"); + pw.println(" 'verify'."); + pw.println(" -p Set the priority of the operation, which determines the resource usage"); + pw.println(" and the process priority. If not specified, this defaults to"); + pw.println(" the value given by -r, or 'PRIORITY_INTERACTIVE'."); + pw.println(" Available options (in descending order): 'PRIORITY_BOOT',"); + pw.println(" 'PRIORITY_INTERACTIVE_FAST', 'PRIORITY_INTERACTIVE',"); + pw.println(" 'PRIORITY_BACKGROUND'."); + pw.println(" -f Force dexopt, also when the compiler filter being applied is not"); + pw.println(" better than that of the current dexopt artifacts for a package."); + pw.println(" --reset Reset the dexopt state of the package as if the package is newly"); + pw.println(" installed."); + pw.println(" More specifically, it clears reference profiles, current profiles, and"); + pw.println(" any code compiled from those local profiles. If there is an external"); + pw.println(" profile (e.g., a cloud profile), the code compiled from that profile"); + pw.println(" will be kept."); + pw.println(" For secondary dex files, it also clears all dexopt artifacts."); + pw.println(" When this flag is set, all the other flags are ignored."); + pw.println(" -v Verbose mode. This mode prints detailed results."); + pw.println(" Scope options:"); + pw.println(" --primary-dex Dexopt primary dex files only (all APKs that are installed"); + pw.println(" as part of the package, including the base APK and all other split"); + pw.println(" APKs)."); + pw.println(" --secondary-dex Dexopt secondary dex files only (APKs/JARs that the app"); + pw.println(" puts in its own data directory at runtime and loads with custom"); + pw.println(" classloaders)."); + pw.println(" --include-dependencies Include dependency packages (dependencies that are"); + pw.println(" declared by the app with <uses-library> tags and transitive"); + pw.println(" dependencies). This option can only be used together with"); + pw.println(" '--primary-dex' or '--secondary-dex'."); + pw.println(" --full Dexopt all above. (Recommended)"); + pw.println(" --split SPLIT_NAME Only dexopt the given split. If SPLIT_NAME is an empty"); + pw.println(" string, only dexopt the base APK."); + pw.println(" Tip: To pass an empty string, use a pair of quotes (\"\")."); + pw.println(" When this option is set, '--primary-dex', '--secondary-dex',"); + pw.println(" '--include-dependencies', '--full', and '-a' must not be set."); + pw.println(" Note: If none of the scope options above are set, the scope defaults to"); + pw.println(" '--primary-dex --include-dependencies'."); + pw.println(); + pw.println("delete-dexopt PACKAGE_NAME"); + pw.println(" Delete the dexopt artifacts of both primary dex files and secondary dex"); + pw.println(" files of a package."); + pw.println(); + pw.println("bg-dexopt-job [--cancel | --disable | --enable]"); + pw.println(" Control the background dexopt job."); + pw.println(" Without flags, it starts a background dexopt job immediately and waits for"); + pw.println(" it to finish. If a job is already started either automatically by the"); + pw.println(" system or through this command, it will wait for the running job to"); + pw.println(" finish and then start a new one."); + pw.println(" Different from 'pm compile -r bg-dexopt -a', the behavior of this command"); + pw.println(" is the same as a real background dexopt job. Specifically,"); + pw.println(" - It only dexopts a subset of apps determined by either the system's"); + pw.println(" default logic based on app usage data or the custom logic specified by"); + pw.println(" the 'ArtManagerLocal.setBatchDexoptStartCallback' Java API."); + pw.println(" - It runs dexopt in parallel, where the concurrency setting is specified"); + pw.println(" by the system property 'pm.dexopt.bg-dexopt.concurrency'."); + pw.println(" - If the storage is low, it also downgrades unused apps."); + pw.println(" - It also cleans up obsolete files."); + pw.println(" Options:"); + pw.println(" --cancel Cancel any currently running background dexopt job immediately."); + pw.println(" This cancels jobs started either automatically by the system or through"); + pw.println(" this command. This command is not blocking."); + pw.println(" --disable: Disable the background dexopt job from being started by the"); + pw.println(" job scheduler. If a job is already started by the job scheduler and is"); + pw.println(" running, it will be cancelled immediately. Does not affect jobs started"); + pw.println(" through this command or by the system in other ways."); + pw.println(" This state will be lost when the system_server process exits."); + pw.println(" --enable: Enable the background dexopt job to be started by the job"); + pw.println(" scheduler again, if previously disabled by --disable."); + pw.println(" When a list of package names is passed, this command does NOT start a real"); + pw.println(" background dexopt job. Instead, it dexopts the given packages sequentially."); + pw.println(" This usage is deprecated. Please use 'pm compile -r bg-dexopt PACKAGE_NAME'"); + pw.println(" instead."); + pw.println(); + pw.println("snapshot-profile [android | [--split SPLIT_NAME] PACKAGE_NAME]"); + pw.println(" Snapshot the boot image profile or the app profile and save it to"); + pw.println(" '" + PROFILE_DEBUG_LOCATION + "'."); + pw.println(" If 'android' is passed, the command snapshots the boot image profile, and"); + pw.println(" the output filename is 'android.prof'."); + pw.println(" If a package name is passed, the command snapshots the app profile."); + pw.println(" Options:"); + pw.println(" --split SPLIT_NAME If specified, the command snapshots the profile of the"); + pw.println(" given split, and the output filename is"); + pw.println(" 'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'."); + pw.println(" If not specified, the command snapshots the profile of the base APK,"); + pw.println(" and the output filename is 'PACKAGE_NAME.prof'"); + pw.println(); + pw.println("dump-profiles [--dump-classes-and-methods] PACKAGE_NAME"); + pw.println(" Dump the profiles of the given app in text format and save the outputs to"); + pw.println(" '" + PROFILE_DEBUG_LOCATION + "'."); + pw.println(" The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'"); + pw.println(" The profile of a split APK is dumped to"); + pw.println(" 'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'"); + pw.println(); + pw.println("art SUB_COMMAND [ARGS]..."); + pw.println(" Run ART Service commands"); + pw.println(); + pw.println(" Supported sub-commands:"); + pw.println(); + pw.println(" cancel JOB_ID"); + pw.println(" Cancel a job started by a shell command. This doesn't apply to background"); + pw.println(" jobs."); + pw.println(); + pw.println(" clear-app-profiles PACKAGE_NAME"); + pw.println(" Clear the profiles that are collected locally for the given package,"); + pw.println(" including the profiles for primary and secondary dex files. More"); + pw.println(" specifically, this command clears reference profiles and current"); + pw.println(" profiles. External profiles (e.g., cloud profiles) will be kept."); + pw.println(); + pw.println(" cleanup"); + pw.println(" Cleanup obsolete files, such as dexopt artifacts that are outdated or"); + pw.println(" correspond to dex container files that no longer exist."); + pw.println(); + pw.println(" dump [PACKAGE_NAME]"); + pw.println(" Dumps the dexopt state in text format to stdout."); + pw.println(" If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it"); + pw.println(" is for the given package."); + pw.println(); + pw.println(" dexopt-packages -r REASON"); + pw.println(" Run batch dexopt for the given reason."); + pw.println(" Valid values for REASON: 'first-boot', 'boot-after-ota',"); + pw.println(" 'boot-after-mainline-update', 'bg-dexopt'"); + pw.println(" This command is different from 'pm compile -r REASON -a'. For example, it"); + pw.println(" only dexopts a subset of apps, and it runs dexopt in parallel. See the"); + pw.println(" API documentation for 'ArtManagerLocal.dexoptPackages' for details."); + } + + private void enforceRootOrShell() { + final int uid = Binder.getCallingUid(); + if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) { + throw new SecurityException("ART service shell commands need root or shell access"); + } + } + + @PriorityClassApi + int parsePriorityClass(@NonNull String priorityClass) { + switch (priorityClass) { + case "PRIORITY_BOOT": + return ArtFlags.PRIORITY_BOOT; + case "PRIORITY_INTERACTIVE_FAST": + return ArtFlags.PRIORITY_INTERACTIVE_FAST; + case "PRIORITY_INTERACTIVE": + return ArtFlags.PRIORITY_INTERACTIVE; + case "PRIORITY_BACKGROUND": + return ArtFlags.PRIORITY_BACKGROUND; + default: + throw new IllegalArgumentException("Unknown priority " + priorityClass); + } + } + + @Nullable + private String getSplitName(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @NonNull String splitArg) { + if (splitArg.isEmpty()) { + return null; // Base APK. + } + + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg); + + for (PrimaryDexInfo dexInfo : dexInfoList) { + if (splitArg.equals(dexInfo.splitName())) { + return splitArg; + } + } + + for (PrimaryDexInfo dexInfo : dexInfoList) { + if (splitArg.equals(new File(dexInfo.dexPath()).getName())) { + pw.println("Warning: Specifying a split using a filename is deprecated. Please " + + "use a split name (or an empty string for the base APK) instead"); + return dexInfo.splitName(); + } + } + + throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg)); + } + + @Nullable + private String getSplitNameByFullPath(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String packageName, @NonNull String fullPath) { + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg); + + for (PrimaryDexInfo dexInfo : dexInfoList) { + if (fullPath.equals(dexInfo.dexPath())) { + return dexInfo.splitName(); + } + } + + throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath)); + } + + private int resetPackages(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull List<String> packageNames, boolean verbose) { + try (var signal = new WithCancellationSignal(pw, verbose)) { + for (String packageName : packageNames) { + DexoptResult result = + mArtManagerLocal.resetDexoptStatus(snapshot, packageName, signal.get()); + printDexoptResult(pw, result, verbose, packageNames.size() > 1); + } + } + return 0; + } + + private int dexoptPackages(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull List<String> packageNames, @NonNull DexoptParams params, boolean verbose) { + try (var signal = new WithCancellationSignal(pw, verbose)) { + for (String packageName : packageNames) { + DexoptResult result = + mArtManagerLocal.dexoptPackage(snapshot, packageName, params, signal.get()); + printDexoptResult(pw, result, verbose, packageNames.size() > 1); + } + } + return 0; + } + + @NonNull + private String dexoptResultStatusToSimpleString(@DexoptResultStatus int status) { + return (status == DexoptResult.DEXOPT_SKIPPED || status == DexoptResult.DEXOPT_PERFORMED) + ? "Success" + : "Failure"; + } + + private void printDexoptResult(@NonNull PrintWriter pw, @NonNull DexoptResult result, + boolean verbose, boolean multiPackage) { + for (PackageDexoptResult packageResult : result.getPackageDexoptResults()) { + if (verbose) { + pw.printf("[%s]\n", packageResult.getPackageName()); + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + pw.println(fileResult); + } + } else if (multiPackage) { + pw.printf("[%s] %s\n", packageResult.getPackageName(), + dexoptResultStatusToSimpleString(packageResult.getStatus())); + } + } + + if (verbose) { + pw.println("Final Status: " + + DexoptResult.dexoptResultStatusToString(result.getFinalStatus())); + } else if (!multiPackage) { + // Multi-package result is printed by the loop above. + pw.println(dexoptResultStatusToSimpleString(result.getFinalStatus())); + } + + pw.flush(); + } + + private void writeProfileFdContentsToFile(@NonNull PrintWriter pw, + @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) { + try { + StructStat st = Os.stat(PROFILE_DEBUG_LOCATION); + if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID + || (st.st_mode & 0007) != 0) { + throw new RuntimeException( + String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o", + PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode)); + } + } catch (ErrnoException e) { + throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e); + } + Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath); + try (InputStream inputStream = new AutoCloseInputStream(fd); + FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) { + // The system server doesn't have the permission to chown the file to "shell", so we + // make it readable by everyone and put it in a directory that is only accessible by + // "shell", which is created by system/core/rootdir/init.rc. The permissions are + // verified by the code above. + Os.fchmod(outputStream.getFD(), 0644); + Streams.copy(inputStream, outputStream); + pw.printf("Profile saved to '%s'\n", outputPath); + } catch (IOException | ErrnoException e) { + Utils.deleteIfExistsSafe(outputPath); + throw new RuntimeException(e); + } + } + + private static class WithCancellationSignal implements AutoCloseable { + @NonNull private final CancellationSignal mSignal = new CancellationSignal(); + @NonNull private final String mJobId; + + public WithCancellationSignal(@NonNull PrintWriter pw, boolean verbose) { + mJobId = UUID.randomUUID().toString(); + if (verbose) { + pw.printf( + "Job running. To cancel it, run 'pm art cancel %s' in a separate shell.\n", + mJobId); + pw.flush(); + } + + synchronized (sCancellationSignalMap) { + sCancellationSignalMap.put(mJobId, mSignal); + } + } + + @NonNull + public CancellationSignal get() { + return mSignal; + } + + public void close() { + synchronized (sCancellationSignalMap) { + sCancellationSignalMap.remove(mJobId); + } + } + } +} diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java new file mode 100644 index 0000000000..b2973260c4 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback; +import static com.android.server.art.model.ArtFlags.ScheduleStatus; +import static com.android.server.art.model.Config.Callback; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.Config; +import com.android.server.art.model.DexoptResult; +import com.android.server.pm.PackageManagerLocal; + +import com.google.auto.value.AutoValue; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class BackgroundDexoptJob { + private static final String TAG = ArtManagerLocal.TAG; + + /** + * "android" is the package name for a <service> declared in + * frameworks/base/core/res/AndroidManifest.xml + */ + private static final String JOB_PKG_NAME = Utils.PLATFORM_PACKAGE_NAME; + /** An arbitrary number. Must be unique among all jobs owned by the system uid. */ + private static final int JOB_ID = 27873780; + + @VisibleForTesting public static final long JOB_INTERVAL_MS = TimeUnit.DAYS.toMillis(1); + + @NonNull private final Injector mInjector; + + @GuardedBy("this") @Nullable private CompletableFuture<Result> mRunningJob = null; + @GuardedBy("this") @Nullable private CancellationSignal mCancellationSignal = null; + @GuardedBy("this") @NonNull private Optional<Integer> mLastStopReason = Optional.empty(); + + public BackgroundDexoptJob(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal, + @NonNull Config config) { + this(new Injector(context, artManagerLocal, config)); + } + + @VisibleForTesting + public BackgroundDexoptJob(@NonNull Injector injector) { + mInjector = injector; + } + + /** Handles {@link BackgroundDexoptJobService#onStartJob(JobParameters)}. */ + public boolean onStartJob( + @NonNull BackgroundDexoptJobService jobService, @NonNull JobParameters params) { + start().thenAcceptAsync(result -> { + writeStats(result); + // This is a periodic job, where the interval is specified in the `JobInfo`. "true" + // means to execute again during a future idle maintenance window in the same + // interval, while "false" means not to execute again during a future idle maintenance + // window in the same interval but to execute again in the next interval. + // This call will be ignored if `onStopJob` is called. + boolean wantsReschedule = result instanceof CompletedResult + && ((CompletedResult) result).dexoptResult().getFinalStatus() + == DexoptResult.DEXOPT_CANCELLED; + jobService.jobFinished(params, wantsReschedule); + }); + // "true" means the job will continue running until `jobFinished` is called. + return true; + } + + /** Handles {@link BackgroundDexoptJobService#onStopJob(JobParameters)}. */ + public boolean onStopJob(@NonNull JobParameters params) { + synchronized (this) { + mLastStopReason = Optional.of(params.getStopReason()); + } + cancel(); + // "true" means to execute again during a future idle maintenance window in the same + // interval. + return true; + } + + /** Handles {@link ArtManagerLocal#scheduleBackgroundDexoptJob()}. */ + public @ScheduleStatus int schedule() { + if (this != BackgroundDexoptJobService.getJob()) { + throw new IllegalStateException("This job cannot be scheduled"); + } + + if (SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false /* def */)) { + Log.i(TAG, "Job is disabled by system property 'pm.dexopt.disable_bg_dexopt'"); + return ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP; + } + + JobInfo.Builder builder = + new JobInfo + .Builder(JOB_ID, + new ComponentName( + JOB_PKG_NAME, BackgroundDexoptJobService.class.getName())) + .setPeriodic(JOB_INTERVAL_MS) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setRequiresBatteryNotLow(true); + + Callback<ScheduleBackgroundDexoptJobCallback, Void> callback = + mInjector.getConfig().getScheduleBackgroundDexoptJobCallback(); + if (callback != null) { + Utils.executeAndWait( + callback.executor(), () -> { callback.get().onOverrideJobInfo(builder); }); + } + + JobInfo info = builder.build(); + if (info.isRequireStorageNotLow()) { + // See the javadoc of + // `ArtManagerLocal.ScheduleBackgroundDexoptJobCallback.onOverrideJobInfo` for details. + throw new IllegalStateException("'setRequiresStorageNotLow' must not be set"); + } + + return mInjector.getJobScheduler().schedule(info) == JobScheduler.RESULT_SUCCESS + ? ArtFlags.SCHEDULE_SUCCESS + : ArtFlags.SCHEDULE_JOB_SCHEDULER_FAILURE; + } + + /** Handles {@link ArtManagerLocal#unscheduleBackgroundDexoptJob()}. */ + public void unschedule() { + if (this != BackgroundDexoptJobService.getJob()) { + throw new IllegalStateException("This job cannot be unscheduled"); + } + + mInjector.getJobScheduler().cancel(JOB_ID); + } + + @NonNull + public synchronized CompletableFuture<Result> start() { + if (mRunningJob != null) { + Log.i(TAG, "Job is already running"); + return mRunningJob; + } + + mCancellationSignal = new CancellationSignal(); + mLastStopReason = Optional.empty(); + mRunningJob = new CompletableFuture().supplyAsync(() -> { + try (var tracing = new Utils.TracingWithTimingLogging(TAG, "jobExecution")) { + return run(mCancellationSignal); + } catch (RuntimeException e) { + Log.e(TAG, "Fatal error", e); + return new FatalErrorResult(); + } finally { + synchronized (this) { + mRunningJob = null; + mCancellationSignal = null; + } + } + }); + return mRunningJob; + } + + public synchronized void cancel() { + if (mRunningJob == null) { + Log.i(TAG, "Job is not running"); + return; + } + + mCancellationSignal.cancel(); + Log.i(TAG, "Job cancelled"); + } + + @Nullable + public synchronized CompletableFuture<Result> get() { + return mRunningJob; + } + + @NonNull + private CompletedResult run(@NonNull CancellationSignal cancellationSignal) { + long startTimeMs = SystemClock.uptimeMillis(); + DexoptResult dexoptResult; + try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) { + dexoptResult = mInjector.getArtManagerLocal().dexoptPackages(snapshot, + ReasonMapping.REASON_BG_DEXOPT, cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + + // For simplicity, we don't support cancelling the following operation in the middle. + // This is fine because it typically takes only a few seconds. + if (!cancellationSignal.isCanceled()) { + // We do the cleanup after dexopt so that it doesn't affect the `getSizeBeforeBytes` + // field in the result that we send to callbacks. Admittedly, this will cause us to + // lose some chance to dexopt when the storage is very low, but it's fine because we + // can still dexopt in the next run. + long freedBytes = mInjector.getArtManagerLocal().cleanup(snapshot); + Log.i(TAG, String.format("Freed %d bytes", freedBytes)); + } + } + return CompletedResult.create(dexoptResult, SystemClock.uptimeMillis() - startTimeMs); + } + + private void writeStats(@NonNull Result result) { + Optional<Integer> stopReason; + synchronized (this) { + stopReason = mLastStopReason; + } + if (result instanceof CompletedResult) { + var completedResult = (CompletedResult) result; + ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED, + getStatusForStats(completedResult, stopReason), + stopReason.orElse(JobParameters.STOP_REASON_UNDEFINED), + completedResult.durationMs(), 0 /* deprecated */); + } else if (result instanceof FatalErrorResult) { + ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED, + ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR, + JobParameters.STOP_REASON_UNDEFINED, 0 /* durationMs */, 0 /* deprecated */); + } + } + + private int getStatusForStats(@NonNull CompletedResult result, Optional<Integer> stopReason) { + if (result.dexoptResult().getFinalStatus() == DexoptResult.DEXOPT_CANCELLED) { + if (stopReason.isPresent()) { + return ArtStatsLog + .BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION; + } else { + return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_API; + } + } + + boolean isSkippedDueToStorageLow = + result.dexoptResult() + .getPackageDexoptResults() + .stream() + .flatMap(packageResult + -> packageResult.getDexContainerFileDexoptResults().stream()) + .anyMatch(fileResult -> fileResult.isSkippedDueToStorageLow()); + if (isSkippedDueToStorageLow) { + return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_NO_SPACE_LEFT; + } + + return ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED; + } + + static abstract class Result {} + static class FatalErrorResult extends Result {} + + @AutoValue + static abstract class CompletedResult extends Result { + abstract @NonNull DexoptResult dexoptResult(); + abstract long durationMs(); + + @NonNull + static CompletedResult create(@NonNull DexoptResult dexoptResult, long durationMs) { + return new AutoValue_BackgroundDexoptJob_CompletedResult(dexoptResult, durationMs); + } + } + + /** + * Injector pattern for testing purpose. + * + * @hide + */ + @VisibleForTesting + public static class Injector { + @NonNull private final Context mContext; + @NonNull private final ArtManagerLocal mArtManagerLocal; + @NonNull private final Config mConfig; + + Injector(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal, + @NonNull Config config) { + mContext = context; + mArtManagerLocal = artManagerLocal; + mConfig = config; + + // Call the getters for various dependencies, to ensure correct initialization order. + getPackageManagerLocal(); + getJobScheduler(); + } + + @NonNull + public ArtManagerLocal getArtManagerLocal() { + return mArtManagerLocal; + } + + @NonNull + public PackageManagerLocal getPackageManagerLocal() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(PackageManagerLocal.class)); + } + + @NonNull + public Config getConfig() { + return mConfig; + } + + @NonNull + public JobScheduler getJobScheduler() { + return Objects.requireNonNull(mContext.getSystemService(JobScheduler.class)); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java new file mode 100644 index 0000000000..41425ee5cc --- /dev/null +++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJobService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.android.server.LocalManagerRegistry; + +/** + * Entry point for the callback from the job scheduler. This class is instantiated by the system + * automatically. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class BackgroundDexoptJobService extends JobService { + @Override + public boolean onStartJob(@NonNull JobParameters params) { + return getJob().onStartJob(this, params); + } + + @Override + public boolean onStopJob(@NonNull JobParameters params) { + return getJob().onStopJob(params); + } + + @NonNull + static BackgroundDexoptJob getJob() { + return LocalManagerRegistry.getManager(ArtManagerLocal.class).getBackgroundDexoptJob(); + } +} diff --git a/libartservice/service/java/com/android/server/art/Constants.java b/libartservice/service/java/com/android/server/art/Constants.java new file mode 100644 index 0000000000..f4c6e07e32 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/Constants.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Build; +import android.os.SystemProperties; +import android.system.Os; + +/** + * A mockable wrapper class for device-specific constants. + * + * @hide + */ +public class Constants { + private Constants() {} + + /** Returns the ABI that the device prefers. */ + @NonNull + public static String getPreferredAbi() { + return Build.SUPPORTED_ABIS[0]; + } + + /** Returns the 64 bit ABI that is native to the device. */ + @Nullable + public static String getNative64BitAbi() { + // The value comes from "ro.product.cpu.abilist64" and we assume that the first element is + // the native one. + return Build.SUPPORTED_64_BIT_ABIS.length > 0 ? Build.SUPPORTED_64_BIT_ABIS[0] : null; + } + + /** Returns the 32 bit ABI that is native to the device. */ + @Nullable + public static String getNative32BitAbi() { + // The value comes from "ro.product.cpu.abilist32" and we assume that the first element is + // the native one. + return Build.SUPPORTED_32_BIT_ABIS.length > 0 ? Build.SUPPORTED_32_BIT_ABIS[0] : null; + } + + @Nullable + public static String getenv(@NonNull String name) { + return Os.getenv(name); + } + + public static boolean isBootImageProfilingEnabled() { + boolean profileBootClassPath = SystemProperties.getBoolean( + "persist.device_config.runtime_native_boot.profilebootclasspath", + SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false /* def */)); + return Build.isDebuggable() && profileBootClassPath; + } +} diff --git a/libartservice/service/java/com/android/server/art/Debouncer.java b/libartservice/service/java/com/android/server/art/Debouncer.java new file mode 100644 index 0000000000..61aea816d7 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/Debouncer.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * A class that executes commands with a minimum interval. + * + * @hide + */ +public class Debouncer { + @NonNull private Supplier<ScheduledExecutorService> mScheduledExecutorFactory; + private final long mIntervalMs; + @Nullable private ScheduledFuture<?> mCurrentTask = null; + + public Debouncer( + long intervalMs, @NonNull Supplier<ScheduledExecutorService> scheduledExecutorFactory) { + mScheduledExecutorFactory = scheduledExecutorFactory; + mIntervalMs = intervalMs; + } + + /** + * Runs the given command after the interval has passed. If another command comes in during + * this interval, the previous one will never run. + */ + synchronized public void maybeRunAsync(@NonNull Runnable command) { + if (mCurrentTask != null) { + mCurrentTask.cancel(false /* mayInterruptIfRunning */); + } + ScheduledExecutorService executor = mScheduledExecutorFactory.get(); + mCurrentTask = executor.schedule(command, mIntervalMs, TimeUnit.MILLISECONDS); + executor.shutdown(); + } +} diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java new file mode 100644 index 0000000000..153e83b54b --- /dev/null +++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java @@ -0,0 +1,1162 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Binder; +import android.os.Build; +import android.os.Environment; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.Immutable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.art.model.DexContainerFileUseInfo; +import com.android.server.art.proto.DexUseProto; +import com.android.server.art.proto.Int32Value; +import com.android.server.art.proto.PackageDexUseProto; +import com.android.server.art.proto.PrimaryDexUseProto; +import com.android.server.art.proto.PrimaryDexUseRecordProto; +import com.android.server.art.proto.SecondaryDexUseProto; +import com.android.server.art.proto.SecondaryDexUseRecordProto; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; + +import com.google.auto.value.AutoValue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A singleton class that maintains the information about dex uses. This class is thread-safe. + * + * This class collects data sent directly by apps, and hence the data should be trusted as little as + * possible. + * + * To avoid overwriting data, {@link #load()} must be called exactly once, during initialization. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class DexUseManagerLocal { + private static final String TAG = ArtManagerLocal.TAG; + private static final String FILENAME = "/data/system/package-dex-usage.pb"; + + /** + * The minimum interval between disk writes. + * + * In practice, the interval will be much longer because we use a debouncer to postpone the disk + * write to the end of a series of changes. Note that in theory we could postpone the disk write + * indefinitely, and therefore we could lose data if the device isn't shut down in the normal + * way, but that's fine because the data isn't crucial and is recoverable. + * + * @hide + */ + @VisibleForTesting public static final long INTERVAL_MS = 15_000; + + private static final Object sLock = new Object(); + @GuardedBy("sLock") @Nullable private static DexUseManagerLocal sInstance = null; + + @NonNull private final Injector mInjector; + @NonNull private final Debouncer mDebouncer; + + private final Object mLock = new Object(); + @GuardedBy("mLock") @NonNull private DexUse mDexUse; // Initialized by `load`. + @GuardedBy("mLock") private int mRevision = 0; + @GuardedBy("mLock") private int mLastCommittedRevision = 0; + @GuardedBy("mLock") + @NonNull + private SecondaryDexLocationManager mSecondaryDexLocationManager = + new SecondaryDexLocationManager(); + + /** + * Creates the singleton instance. + * + * Only {@code SystemServer} should create the instance and register it in {@link + * LocalManagerRegistry}. Other API users should obtain the instance from {@link + * LocalManagerRegistry}. + * + * In practice, it must be created and registered in {@link LocalManagerRegistry} before {@code + * PackageManagerService} starts because {@code PackageManagerService} needs it as soon as it + * starts. It's safe to create an instance early because it doesn't depend on anything else. + * + * @param context the system server context + * @throws IllegalStateException if the instance is already created + * @throws NullPointerException if required dependencies are missing + */ + @NonNull + public static DexUseManagerLocal createInstance(@NonNull Context context) { + synchronized (sLock) { + if (sInstance != null) { + throw new IllegalStateException("DexUseManagerLocal is already created"); + } + sInstance = new DexUseManagerLocal(context); + return sInstance; + } + } + + private DexUseManagerLocal(@NonNull Context context) { + this(new Injector(context)); + } + + /** @hide */ + @VisibleForTesting + public DexUseManagerLocal(@NonNull Injector injector) { + mInjector = injector; + mDebouncer = new Debouncer(INTERVAL_MS, mInjector::createScheduledExecutor); + load(); + } + + /** Notifies dex use manager that {@link Context#registerReceiver} is ready for use. */ + public void systemReady() { + // Save the data when the device is being shut down. The receiver is blocking, with a + // 10s timeout. + mInjector.getContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + context.unregisterReceiver(this); + save(); + } + }, new IntentFilter(Intent.ACTION_SHUTDOWN)); + } + + /** + * Returns the information about the use of all secondary dex files owned by the given package, + * or an empty list if the package does not own any secondary dex file or it does not exist. + */ + @NonNull + public List<DexContainerFileUseInfo> getSecondaryDexContainerFileUseInfo( + @NonNull String packageName) { + return getSecondaryDexInfo(packageName) + .stream() + .map(info + -> DexContainerFileUseInfo.create(info.dexPath(), info.userHandle(), + info.loaders() + .stream() + .map(loader -> loader.loadingPackageName()) + .collect(Collectors.toSet()))) + .collect(Collectors.toList()); + } + + /** + * Returns all entities that load the given primary dex file owned by the given package. + * + * @hide + */ + @NonNull + public Set<DexLoader> getPrimaryDexLoaders( + @NonNull String packageName, @NonNull String dexPath) { + synchronized (mLock) { + PackageDexUse packageDexUse = + mDexUse.mPackageDexUseByOwningPackageName.get(packageName); + if (packageDexUse == null) { + return Set.of(); + } + PrimaryDexUse primaryDexUse = packageDexUse.mPrimaryDexUseByDexFile.get(dexPath); + if (primaryDexUse == null) { + return Set.of(); + } + return Set.copyOf(primaryDexUse.mRecordByLoader.keySet()); + } + } + + /** + * Returns whether a primary dex file owned by the given package is used by other apps. + * + * @hide + */ + public boolean isPrimaryDexUsedByOtherApps( + @NonNull String packageName, @NonNull String dexPath) { + return isUsedByOtherApps(getPrimaryDexLoaders(packageName, dexPath), packageName); + } + + /** + * Returns the basic information about all secondary dex files owned by the given package. This + * method doesn't take dex file visibility into account, so it can only be used for debugging + * purpose, such as dumpsys. + * + * @see #getFilteredDetailedSecondaryDexInfo(String) + * @hide + */ + public @NonNull List<? extends SecondaryDexInfo> getSecondaryDexInfo( + @NonNull String packageName) { + return getSecondaryDexInfoImpl(packageName, false /* checkDexFile */); + } + + /** + * Same as above, but requires disk IO, and returns the detailed information, including dex file + * visibility, filtered by dex file existence and visibility. + * + * @hide + */ + public @NonNull List<DetailedSecondaryDexInfo> getFilteredDetailedSecondaryDexInfo( + @NonNull String packageName) { + return getSecondaryDexInfoImpl(packageName, true /* checkDexFile */); + } + + /** + * Returns the last time the package was used, or 0 if the package has never been used. + * + * @hide + */ + public long getPackageLastUsedAtMs(@NonNull String packageName) { + synchronized (mLock) { + PackageDexUse packageDexUse = + mDexUse.mPackageDexUseByOwningPackageName.get(packageName); + if (packageDexUse == null) { + return 0; + } + long primaryLastUsedAtMs = + packageDexUse.mPrimaryDexUseByDexFile.values() + .stream() + .flatMap(primaryDexUse + -> primaryDexUse.mRecordByLoader.values().stream()) + .map(record -> record.mLastUsedAtMs) + .max(Long::compare) + .orElse(0l); + long secondaryLastUsedAtMs = + packageDexUse.mSecondaryDexUseByDexFile.values() + .stream() + .flatMap(secondaryDexUse + -> secondaryDexUse.mRecordByLoader.values().stream()) + .map(record -> record.mLastUsedAtMs) + .max(Long::compare) + .orElse(0l); + return Math.max(primaryLastUsedAtMs, secondaryLastUsedAtMs); + } + } + + /** + * @param checkDexFile if true, check the existence and visibility of the dex files, and filter + * the results accordingly. Note that the value of the {@link + * DetailedSecondaryDexInfo#isDexFilePublic()} field is undefined if this argument is + * false. + */ + private @NonNull List<DetailedSecondaryDexInfo> getSecondaryDexInfoImpl( + @NonNull String packageName, boolean checkDexFile) { + synchronized (mLock) { + PackageDexUse packageDexUse = + mDexUse.mPackageDexUseByOwningPackageName.get(packageName); + if (packageDexUse == null) { + return List.of(); + } + var results = new ArrayList<DetailedSecondaryDexInfo>(); + for (var entry : packageDexUse.mSecondaryDexUseByDexFile.entrySet()) { + String dexPath = entry.getKey(); + SecondaryDexUse secondaryDexUse = entry.getValue(); + + @FileVisibility + int visibility = checkDexFile ? getDexFileVisibility(dexPath) + : FileVisibility.OTHER_READABLE; + if (visibility == FileVisibility.NOT_FOUND) { + continue; + } + + Map<DexLoader, SecondaryDexUseRecord> filteredRecordByLoader; + if (visibility == FileVisibility.OTHER_READABLE) { + filteredRecordByLoader = secondaryDexUse.mRecordByLoader; + } else { + // Only keep the entry that belongs to the same app. + DexLoader sameApp = DexLoader.create(packageName, false /* isolatedProcess */); + SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.get(sameApp); + filteredRecordByLoader = record != null ? Map.of(sameApp, record) : Map.of(); + } + if (filteredRecordByLoader.isEmpty()) { + continue; + } + List<String> distinctClcList = + filteredRecordByLoader.values() + .stream() + .map(record -> Utils.assertNonEmpty(record.mClassLoaderContext)) + .filter(clc + -> !clc.equals( + SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT)) + .distinct() + .collect(Collectors.toList()); + String clc; + if (distinctClcList.size() == 0) { + clc = SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT; + } else if (distinctClcList.size() == 1) { + clc = distinctClcList.get(0); + } else { + // If there are more than one class loader contexts, we can't dexopt the dex + // file. + clc = SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS; + } + // Although we filter out unsupported CLCs above, `distinctAbiNames` and `loaders` + // still need to take apps with unsupported CLCs into account because the vdex file + // is still usable to them. + Set<String> distinctAbiNames = + filteredRecordByLoader.values() + .stream() + .map(record -> Utils.assertNonEmpty(record.mAbiName)) + .collect(Collectors.toSet()); + Set<DexLoader> loaders = Set.copyOf(filteredRecordByLoader.keySet()); + results.add(DetailedSecondaryDexInfo.create(dexPath, + Objects.requireNonNull(secondaryDexUse.mUserHandle), clc, distinctAbiNames, + loaders, isUsedByOtherApps(loaders, packageName), + visibility == FileVisibility.OTHER_READABLE)); + } + return Collections.unmodifiableList(results); + } + } + + /** + * Notifies ART Service that a list of dex container files have been loaded. + * + * ART Service uses this information to: + * <ul> + * <li>Determine whether an app is used by another app + * <li>Record which secondary dex container files to dexopt and how to dexopt them + * </ul> + * + * @param loadingPackageName the name of the package who performs the load. ART Service assumes + * that this argument has been validated that it exists in the snapshot and matches the + * calling UID + * @param classLoaderContextByDexContainerFile a map from dex container files' absolute paths to + * the string representations of the class loader contexts used to load them + * @throws IllegalArgumentException if {@code classLoaderContextByDexContainerFile} contains + * invalid entries + */ + public void notifyDexContainersLoaded(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String loadingPackageName, + @NonNull Map<String, String> classLoaderContextByDexContainerFile) { + // "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle + // this case because it doesn't compile system server and system server isn't allowed to + // load artifacts produced by ART Services. + if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) { + return; + } + + validateInputs(snapshot, loadingPackageName, classLoaderContextByDexContainerFile); + + // TODO(jiakaiz): Investigate whether it should also be considered as isolated process if + // `Process.isSdkSandboxUid` returns true. + boolean isolatedProcess = Process.isIsolatedUid(Binder.getCallingUid()); + long lastUsedAtMs = mInjector.getCurrentTimeMillis(); + + for (var entry : classLoaderContextByDexContainerFile.entrySet()) { + String dexPath = Utils.assertNonEmpty(entry.getKey()); + String classLoaderContext = Utils.assertNonEmpty(entry.getValue()); + String owningPackageName = findOwningPackage(snapshot, loadingPackageName, + (pkgState) -> isOwningPackageForPrimaryDex(pkgState, dexPath)); + if (owningPackageName != null) { + addPrimaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess, + lastUsedAtMs); + continue; + } + Path path = Paths.get(dexPath); + synchronized (mLock) { + owningPackageName = findOwningPackage(snapshot, loadingPackageName, + (pkgState) -> isOwningPackageForSecondaryDexLocked(pkgState, path)); + } + if (owningPackageName != null) { + PackageState loadingPkgState = + Utils.getPackageStateOrThrow(snapshot, loadingPackageName); + // An app is always launched with its primary ABI. + Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState); + addSecondaryDexUse(owningPackageName, dexPath, loadingPackageName, isolatedProcess, + classLoaderContext, abi.name(), lastUsedAtMs); + continue; + } + // It is expected that a dex file isn't owned by any package. For example, the dex + // file could be a shared library jar. + } + } + + @Nullable + private static String findOwningPackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String loadingPackageName, + @NonNull Function<PackageState, Boolean> predicate) { + // Most likely, the package is loading its own dex file, so we check this first as an + // optimization. + PackageState loadingPkgState = Utils.getPackageStateOrThrow(snapshot, loadingPackageName); + if (predicate.apply(loadingPkgState)) { + return loadingPkgState.getPackageName(); + } + + for (PackageState pkgState : snapshot.getPackageStates().values()) { + if (predicate.apply(pkgState)) { + return pkgState.getPackageName(); + } + } + + return null; + } + + private static boolean isOwningPackageForPrimaryDex( + @NonNull PackageState pkgState, @NonNull String dexPath) { + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + List<AndroidPackageSplit> splits = pkg.getSplits(); + for (int i = 0; i < splits.size(); i++) { + if (splits.get(i).getPath().equals(dexPath)) { + return true; + } + } + return false; + } + + @GuardedBy("mLock") + private boolean isOwningPackageForSecondaryDexLocked( + @NonNull PackageState pkgState, @NonNull Path dexPath) { + UserHandle userHandle = Binder.getCallingUserHandle(); + List<Path> locations = mSecondaryDexLocationManager.getLocations(pkgState, userHandle); + for (int i = 0; i < locations.size(); i++) { + if (dexPath.startsWith(locations.get(i))) { + return true; + } + } + return false; + } + + private void addPrimaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath, + @NonNull String loadingPackageName, boolean isolatedProcess, long lastUsedAtMs) { + synchronized (mLock) { + PrimaryDexUseRecord record = + mDexUse.mPackageDexUseByOwningPackageName + .computeIfAbsent(owningPackageName, k -> new PackageDexUse()) + .mPrimaryDexUseByDexFile + .computeIfAbsent(dexPath, k -> new PrimaryDexUse()) + .mRecordByLoader.computeIfAbsent( + DexLoader.create(loadingPackageName, isolatedProcess), + k -> new PrimaryDexUseRecord()); + record.mLastUsedAtMs = lastUsedAtMs; + mRevision++; + } + maybeSaveAsync(); + } + + private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath, + @NonNull String loadingPackageName, boolean isolatedProcess, + @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) { + synchronized (mLock) { + SecondaryDexUse secondaryDexUse = + mDexUse.mPackageDexUseByOwningPackageName + .computeIfAbsent(owningPackageName, k -> new PackageDexUse()) + .mSecondaryDexUseByDexFile.computeIfAbsent( + dexPath, k -> new SecondaryDexUse()); + secondaryDexUse.mUserHandle = Binder.getCallingUserHandle(); + SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.computeIfAbsent( + DexLoader.create(loadingPackageName, isolatedProcess), + k -> new SecondaryDexUseRecord()); + record.mClassLoaderContext = classLoaderContext; + record.mAbiName = abiName; + record.mLastUsedAtMs = lastUsedAtMs; + mRevision++; + } + maybeSaveAsync(); + } + + /** @hide */ + public @NonNull String dump() { + var builder = DexUseProto.newBuilder(); + synchronized (mLock) { + mDexUse.toProto(builder); + } + return builder.build().toString(); + } + + private void save() { + var builder = DexUseProto.newBuilder(); + int thisRevision; + synchronized (mLock) { + if (mRevision <= mLastCommittedRevision) { + return; + } + mDexUse.toProto(builder); + thisRevision = mRevision; + } + var file = new File(mInjector.getFilename()); + File tempFile = null; + try { + tempFile = File.createTempFile(file.getName(), null /* suffix */, file.getParentFile()); + try (OutputStream out = new FileOutputStream(tempFile.getPath())) { + builder.build().writeTo(out); + } + synchronized (mLock) { + // Check revision again in case `mLastCommittedRevision` has changed since the check + // above, to avoid ABA race. + if (thisRevision > mLastCommittedRevision) { + Files.move(tempFile.toPath(), file.toPath(), + StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + mLastCommittedRevision = thisRevision; + } + } + } catch (IOException e) { + Log.e(TAG, "Failed to save dex use data", e); + } finally { + Utils.deleteIfExistsSafe(tempFile); + } + } + + private void maybeSaveAsync() { + mDebouncer.maybeRunAsync(this::save); + } + + /** This should only be called during initialization. */ + private void load() { + DexUseProto proto = null; + try (InputStream in = new FileInputStream(mInjector.getFilename())) { + proto = DexUseProto.parseFrom(in); + } catch (IOException e) { + // Nothing else we can do but to start from scratch. + Log.e(TAG, "Failed to load dex use data", e); + } + synchronized (mLock) { + if (mDexUse != null) { + throw new IllegalStateException("Load has already been attempted"); + } + mDexUse = new DexUse(); + if (proto != null) { + mDexUse.fromProto(proto); + } + } + } + + private static boolean isUsedByOtherApps( + @NonNull Set<DexLoader> loaders, @NonNull String owningPackageName) { + return loaders.stream().anyMatch(loader -> isLoaderOtherApp(loader, owningPackageName)); + } + + /** + * Returns true if {@code loader} is considered as "other app" (i.e., its process UID is + * different from the UID of the package represented by {@code owningPackageName}). + * + * @hide + */ + public static boolean isLoaderOtherApp( + @NonNull DexLoader loader, @NonNull String owningPackageName) { + // If the dex file is loaded by an isolated process of the same app, it can also be + // considered as "used by other apps" because isolated processes are sandboxed and can only + // read world readable files, so they need the dexopt artifacts to be world readable. An + // example of such a package is webview. + return !loader.loadingPackageName().equals(owningPackageName) || loader.isolatedProcess(); + } + + private static void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull String loadingPackageName, + @NonNull Map<String, String> classLoaderContextByDexContainerFile) { + if (classLoaderContextByDexContainerFile.isEmpty()) { + throw new IllegalArgumentException("Nothing to record"); + } + + for (var entry : classLoaderContextByDexContainerFile.entrySet()) { + Utils.assertNonEmpty(entry.getKey()); + if (!Paths.get(entry.getKey()).isAbsolute()) { + throw new IllegalArgumentException(String.format( + "Dex container file path must be absolute, got '%s'", entry.getKey())); + } + Utils.assertNonEmpty(entry.getValue()); + } + + // TODO(b/253570365): Make the validation more strict. + } + + private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) { + try { + return mInjector.getArtd().getDexFileVisibility(dexPath); + } catch (ServiceSpecificException | RemoteException e) { + Log.e(TAG, "Failed to get visibility of " + dexPath, e); + return FileVisibility.NOT_FOUND; + } + } + + /** @hide */ + @Nullable + public String getSecondaryClassLoaderContext( + @NonNull String owningPackageName, @NonNull String dexFile, @NonNull DexLoader loader) { + synchronized (mLock) { + return Optional + .ofNullable(mDexUse.mPackageDexUseByOwningPackageName.get(owningPackageName)) + .map(packageDexUse -> packageDexUse.mSecondaryDexUseByDexFile.get(dexFile)) + .map(secondaryDexUse -> secondaryDexUse.mRecordByLoader.get(loader)) + .map(record -> record.mClassLoaderContext) + .orElse(null); + } + } + + /** + * Cleans up obsolete information about dex files and packages that no longer exist. + * + * @hide + */ + public void cleanup() { + Set<String> packageNames = mInjector.getAllPackageNames(); + Map<String, Integer> dexFileVisibilityByName = new HashMap<>(); + + // Scan the data in two passes to avoid holding the lock during I/O. + synchronized (mLock) { + for (PackageDexUse packageDexUse : mDexUse.mPackageDexUseByOwningPackageName.values()) { + for (String dexFile : packageDexUse.mPrimaryDexUseByDexFile.keySet()) { + dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND); + } + for (String dexFile : packageDexUse.mSecondaryDexUseByDexFile.keySet()) { + dexFileVisibilityByName.put(dexFile, FileVisibility.NOT_FOUND); + } + } + } + + for (var entry : dexFileVisibilityByName.entrySet()) { + entry.setValue(getDexFileVisibility(entry.getKey())); + } + + synchronized (mLock) { + for (var it = mDexUse.mPackageDexUseByOwningPackageName.entrySet().iterator(); + it.hasNext();) { + Map.Entry<String, PackageDexUse> entry = it.next(); + String owningPackageName = entry.getKey(); + PackageDexUse packageDexUse = entry.getValue(); + + if (!packageNames.contains(owningPackageName)) { + // Remove information about the non-existing owning package. + it.remove(); + mRevision++; + continue; + } + + cleanupPrimaryDexUsesLocked(packageDexUse.mPrimaryDexUseByDexFile, packageNames, + dexFileVisibilityByName, owningPackageName); + + cleanupSecondaryDexUsesLocked(packageDexUse.mSecondaryDexUseByDexFile, packageNames, + dexFileVisibilityByName, owningPackageName); + + if (packageDexUse.mPrimaryDexUseByDexFile.isEmpty() + && packageDexUse.mSecondaryDexUseByDexFile.isEmpty()) { + it.remove(); + mRevision++; + } + } + } + + maybeSaveAsync(); + } + + @GuardedBy("mLock") + private void cleanupPrimaryDexUsesLocked(@NonNull Map<String, PrimaryDexUse> primaryDexUses, + @NonNull Set<String> packageNames, + @NonNull Map<String, Integer> dexFileVisibilityByName, + @NonNull String owningPackageName) { + for (var it = primaryDexUses.entrySet().iterator(); it.hasNext();) { + Map.Entry<String, PrimaryDexUse> entry = it.next(); + String dexFile = entry.getKey(); + PrimaryDexUse primaryDexUse = entry.getValue(); + + if (!dexFileVisibilityByName.containsKey(dexFile)) { + // This can only happen when the file is added after the first pass. We can just + // keep it as-is and check it in the next `cleanup` run. + continue; + } + + @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile); + + if (visibility == FileVisibility.NOT_FOUND) { + // Remove information about the non-existing dex files. + it.remove(); + mRevision++; + continue; + } + + cleanupRecordsLocked( + primaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName); + + if (primaryDexUse.mRecordByLoader.isEmpty()) { + it.remove(); + mRevision++; + } + } + } + + @GuardedBy("mLock") + private void cleanupSecondaryDexUsesLocked( + @NonNull Map<String, SecondaryDexUse> secondaryDexUses, + @NonNull Set<String> packageNames, + @NonNull Map<String, Integer> dexFileVisibilityByName, + @NonNull String owningPackageName) { + for (var it = secondaryDexUses.entrySet().iterator(); it.hasNext();) { + Map.Entry<String, SecondaryDexUse> entry = it.next(); + String dexFile = entry.getKey(); + SecondaryDexUse secondaryDexUse = entry.getValue(); + + if (!dexFileVisibilityByName.containsKey(dexFile)) { + // This can only happen when the file is added after the first pass. We can just + // keep it as-is and check it in the next `cleanup` run. + continue; + } + + @FileVisibility int visibility = dexFileVisibilityByName.get(dexFile); + + // Remove information about non-existing dex files. + if (visibility == FileVisibility.NOT_FOUND) { + it.remove(); + mRevision++; + continue; + } + + cleanupRecordsLocked( + secondaryDexUse.mRecordByLoader, packageNames, visibility, owningPackageName); + + if (secondaryDexUse.mRecordByLoader.isEmpty()) { + it.remove(); + mRevision++; + } + } + } + + @GuardedBy("mLock") + private void cleanupRecordsLocked(@NonNull Map<DexLoader, ?> records, + @NonNull Set<String> packageNames, @FileVisibility int visibility, + @NonNull String owningPackageName) { + for (var it = records.entrySet().iterator(); it.hasNext();) { + Map.Entry<DexLoader, ?> entry = it.next(); + DexLoader loader = entry.getKey(); + + if (!packageNames.contains(loader.loadingPackageName())) { + // Remove information about the non-existing loading package. + it.remove(); + mRevision++; + continue; + } + + if (visibility == FileVisibility.NOT_OTHER_READABLE + && isLoaderOtherApp(loader, owningPackageName)) { + // The visibility must have changed since the last load. The loader cannot load this + // dex file anymore. + it.remove(); + mRevision++; + continue; + } + } + } + + /** + * Basic information about a secondary dex file (an APK or JAR file that an app adds to its + * own data directory and loads dynamically). + * + * @hide + */ + @Immutable + public abstract static class SecondaryDexInfo implements DetailedDexInfo { + // Special encoding used to denote a foreign ClassLoader was found when trying to encode + // class loader contexts for each classpath element in a ClassLoader. + // Must be in sync with `kUnsupportedClassLoaderContextEncoding` in + // `art/runtime/class_loader_context.h`. + public static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = + "=UnsupportedClassLoaderContext="; + + // Special encoding used to denote that a dex file is loaded by different packages with + // different ClassLoader's. Only for display purpose (e.g., in dumpsys). This value is not + // written to the file, and so far only used here. + @VisibleForTesting + public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts="; + + /** The absolute path to the dex file within the user's app data directory. */ + public abstract @NonNull String dexPath(); + + /** + * The {@link UserHandle} that represents the human user who owns and loads the dex file. A + * secondary dex file belongs to a specific human user, and only that user can load it. + */ + public abstract @NonNull UserHandle userHandle(); + + /** + * A string describing the structure of the class loader that the dex file is loaded with, + * or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}. + */ + public abstract @NonNull String displayClassLoaderContext(); + + /** + * A string describing the structure of the class loader that the dex file is loaded with, + * or null if the class loader context is invalid. + */ + public @Nullable String classLoaderContext() { + return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT) + && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS) + ? displayClassLoaderContext() + : null; + } + + /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */ + public abstract @NonNull Set<String> abiNames(); + + /** The set of entities that load the dex file. Guaranteed to be non-empty. */ + public abstract @NonNull Set<DexLoader> loaders(); + + /** Returns whether the dex file is used by apps other than the app that owns it. */ + public abstract boolean isUsedByOtherApps(); + } + + /** + * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its + * own data directory and loads dynamically). It contains the visibility of the dex file in + * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO. + * + * @hide + */ + @Immutable + @AutoValue + public abstract static class DetailedSecondaryDexInfo + extends SecondaryDexInfo implements DetailedDexInfo { + static DetailedSecondaryDexInfo create(@NonNull String dexPath, + @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext, + @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders, + boolean isUsedByOtherApps, boolean isDexFilePublic) { + return new AutoValue_DexUseManagerLocal_DetailedSecondaryDexInfo(dexPath, userHandle, + displayClassLoaderContext, Collections.unmodifiableSet(abiNames), + Collections.unmodifiableSet(loaders), isUsedByOtherApps, isDexFilePublic); + } + + /** + * Returns true if the filesystem permission of the dex file has the "read" bit for "others" + * (S_IROTH). + */ + public abstract boolean isDexFilePublic(); + } + + private static class DexUse { + @NonNull Map<String, PackageDexUse> mPackageDexUseByOwningPackageName = new HashMap<>(); + + void toProto(@NonNull DexUseProto.Builder builder) { + for (var entry : mPackageDexUseByOwningPackageName.entrySet()) { + var packageBuilder = + PackageDexUseProto.newBuilder().setOwningPackageName(entry.getKey()); + entry.getValue().toProto(packageBuilder); + builder.addPackageDexUse(packageBuilder); + } + } + + void fromProto(@NonNull DexUseProto proto) { + for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) { + var packageDexUse = new PackageDexUse(); + packageDexUse.fromProto(packageProto); + mPackageDexUseByOwningPackageName.put( + Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse); + } + } + } + + private static class PackageDexUse { + /** + * The keys are absolute paths to primary dex files of the owning package (the base APK and + * split APKs). + */ + @NonNull Map<String, PrimaryDexUse> mPrimaryDexUseByDexFile = new HashMap<>(); + + /** + * The keys are absolute paths to secondary dex files of the owning package (the APKs and + * JARs in CE and DE directories). + */ + @NonNull Map<String, SecondaryDexUse> mSecondaryDexUseByDexFile = new HashMap<>(); + + void toProto(@NonNull PackageDexUseProto.Builder builder) { + for (var entry : mPrimaryDexUseByDexFile.entrySet()) { + var primaryBuilder = PrimaryDexUseProto.newBuilder().setDexFile(entry.getKey()); + entry.getValue().toProto(primaryBuilder); + builder.addPrimaryDexUse(primaryBuilder); + } + for (var entry : mSecondaryDexUseByDexFile.entrySet()) { + var secondaryBuilder = SecondaryDexUseProto.newBuilder().setDexFile(entry.getKey()); + entry.getValue().toProto(secondaryBuilder); + builder.addSecondaryDexUse(secondaryBuilder); + } + } + + void fromProto(@NonNull PackageDexUseProto proto) { + for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) { + var primaryDexUse = new PrimaryDexUse(); + primaryDexUse.fromProto(primaryProto); + mPrimaryDexUseByDexFile.put( + Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse); + } + for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) { + var secondaryDexUse = new SecondaryDexUse(); + secondaryDexUse.fromProto(secondaryProto); + mSecondaryDexUseByDexFile.put( + Utils.assertNonEmpty(secondaryProto.getDexFile()), secondaryDexUse); + } + } + } + + private static class PrimaryDexUse { + @NonNull Map<DexLoader, PrimaryDexUseRecord> mRecordByLoader = new HashMap<>(); + + void toProto(@NonNull PrimaryDexUseProto.Builder builder) { + for (var entry : mRecordByLoader.entrySet()) { + var recordBuilder = + PrimaryDexUseRecordProto.newBuilder() + .setLoadingPackageName(entry.getKey().loadingPackageName()) + .setIsolatedProcess(entry.getKey().isolatedProcess()); + entry.getValue().toProto(recordBuilder); + builder.addRecord(recordBuilder); + } + } + + void fromProto(@NonNull PrimaryDexUseProto proto) { + for (PrimaryDexUseRecordProto recordProto : proto.getRecordList()) { + var record = new PrimaryDexUseRecord(); + record.fromProto(recordProto); + mRecordByLoader.put( + DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()), + recordProto.getIsolatedProcess()), + record); + } + } + } + + private static class SecondaryDexUse { + @Nullable UserHandle mUserHandle = null; + @NonNull Map<DexLoader, SecondaryDexUseRecord> mRecordByLoader = new HashMap<>(); + + void toProto(@NonNull SecondaryDexUseProto.Builder builder) { + builder.setUserId(Int32Value.newBuilder().setValue(mUserHandle.getIdentifier())); + for (var entry : mRecordByLoader.entrySet()) { + var recordBuilder = + SecondaryDexUseRecordProto.newBuilder() + .setLoadingPackageName(entry.getKey().loadingPackageName()) + .setIsolatedProcess(entry.getKey().isolatedProcess()); + entry.getValue().toProto(recordBuilder); + builder.addRecord(recordBuilder); + } + } + + void fromProto(@NonNull SecondaryDexUseProto proto) { + Utils.check(proto.hasUserId()); + mUserHandle = UserHandle.of(proto.getUserId().getValue()); + for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) { + var record = new SecondaryDexUseRecord(); + record.fromProto(recordProto); + mRecordByLoader.put( + DexLoader.create(Utils.assertNonEmpty(recordProto.getLoadingPackageName()), + recordProto.getIsolatedProcess()), + record); + } + } + } + + /** + * Represents an entity that loads a dex file. + * + * @hide + */ + @Immutable + @AutoValue + public abstract static class DexLoader implements Comparable<DexLoader> { + static DexLoader create(@NonNull String loadingPackageName, boolean isolatedProcess) { + return new AutoValue_DexUseManagerLocal_DexLoader(loadingPackageName, isolatedProcess); + } + + abstract @NonNull String loadingPackageName(); + + /** @see Process#isIsolatedUid(int) */ + abstract boolean isolatedProcess(); + + @Override + @NonNull + public String toString() { + return loadingPackageName() + (isolatedProcess() ? " (isolated)" : ""); + } + + @Override + public int compareTo(DexLoader o) { + return Comparator.comparing(DexLoader::loadingPackageName) + .thenComparing(DexLoader::isolatedProcess) + .compare(this, o); + } + } + + private static class PrimaryDexUseRecord { + @Nullable long mLastUsedAtMs = 0; + + void toProto(@NonNull PrimaryDexUseRecordProto.Builder builder) { + builder.setLastUsedAtMs(mLastUsedAtMs); + } + + void fromProto(@NonNull PrimaryDexUseRecordProto proto) { + mLastUsedAtMs = proto.getLastUsedAtMs(); + Utils.check(mLastUsedAtMs > 0); + } + } + + private static class SecondaryDexUseRecord { + // An app constructs their own class loader to load a secondary dex file, so only itself + // knows the class loader context. Therefore, we need to record the class loader context + // reported by the app. + @Nullable String mClassLoaderContext = null; + @Nullable String mAbiName = null; + @Nullable long mLastUsedAtMs = 0; + + void toProto(@NonNull SecondaryDexUseRecordProto.Builder builder) { + builder.setClassLoaderContext(mClassLoaderContext) + .setAbiName(mAbiName) + .setLastUsedAtMs(mLastUsedAtMs); + } + + void fromProto(@NonNull SecondaryDexUseRecordProto proto) { + mClassLoaderContext = Utils.assertNonEmpty(proto.getClassLoaderContext()); + mAbiName = Utils.assertNonEmpty(proto.getAbiName()); + mLastUsedAtMs = proto.getLastUsedAtMs(); + Utils.check(mLastUsedAtMs > 0); + } + } + + // TODO(b/278697552): Consider removing the cache or moving it to `Environment`. + static class SecondaryDexLocationManager { + private @NonNull Map<CacheKey, CacheValue> mCache = new HashMap<>(); + + public @NonNull List<Path> getLocations( + @NonNull PackageState pkgState, @NonNull UserHandle userHandle) { + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + UUID storageUuid = pkg.getStorageUuid(); + String packageName = pkgState.getPackageName(); + + CacheKey cacheKey = CacheKey.create(packageName, userHandle); + CacheValue cacheValue = mCache.get(cacheKey); + if (cacheValue != null && cacheValue.storageUuid().equals(storageUuid)) { + return cacheValue.locations(); + } + + File ceDir = Environment.getDataCePackageDirectoryForUser( + storageUuid, userHandle, packageName); + File deDir = Environment.getDataDePackageDirectoryForUser( + storageUuid, userHandle, packageName); + List<Path> locations = List.of(ceDir.toPath(), deDir.toPath()); + mCache.put(cacheKey, CacheValue.create(locations, storageUuid)); + return locations; + } + + @Immutable + @AutoValue + abstract static class CacheKey { + static CacheKey create(@NonNull String packageName, @NonNull UserHandle userHandle) { + return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheKey( + packageName, userHandle); + } + + abstract @NonNull String packageName(); + + abstract @NonNull UserHandle userHandle(); + } + + @Immutable + @AutoValue + abstract static class CacheValue { + static CacheValue create(@NonNull List<Path> locations, @NonNull UUID storageUuid) { + return new AutoValue_DexUseManagerLocal_SecondaryDexLocationManager_CacheValue( + locations, storageUuid); + } + + abstract @NonNull List<Path> locations(); + + abstract @NonNull UUID storageUuid(); + } + } + + /** + * Injector pattern for testing purpose. + * + * @hide + */ + @VisibleForTesting + public static class Injector { + @NonNull private final Context mContext; + + Injector(@NonNull Context context) { + mContext = context; + + // Call the getters for various dependencies, to ensure correct initialization order. + ArtModuleServiceInitializer.getArtModuleServiceManager(); + getPackageManagerLocal(); + } + + @NonNull + public IArtd getArtd() { + return Utils.getArtd(); + } + + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + + @NonNull + public String getFilename() { + return FILENAME; + } + + @NonNull + public ScheduledExecutorService createScheduledExecutor() { + return Executors.newSingleThreadScheduledExecutor(); + } + + @NonNull + public Context getContext() { + return mContext; + } + + @NonNull + public Set<String> getAllPackageNames() { + try (PackageManagerLocal.UnfilteredSnapshot snapshot = + getPackageManagerLocal().withUnfilteredSnapshot()) { + return new HashSet<>(snapshot.getPackageStates().keySet()); + } + } + + @NonNull + private PackageManagerLocal getPackageManagerLocal() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(PackageManagerLocal.class)); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/DexoptHelper.java b/libartservice/service/java/com/android/server/art/DexoptHelper.java new file mode 100644 index 0000000000..c04a9811c8 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback; +import static com.android.server.art.model.Config.Callback; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.apphibernation.AppHibernationManager; +import android.content.Context; +import android.os.Binder; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.WorkSource; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.Config; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.model.OperationProgress; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.SharedLibrary; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A helper class to handle dexopt. + * + * It talks to other components (e.g., PowerManager) and dispatches tasks to dexopters. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class DexoptHelper { + private static final String TAG = ArtManagerLocal.TAG; + + /** + * Timeout of the wake lock. This is required by AndroidLint, but we set it to a very large + * value so that it should normally never triggered. + */ + private static final long WAKE_LOCK_TIMEOUT_MS = TimeUnit.DAYS.toMillis(1); + + @NonNull private final Injector mInjector; + + public DexoptHelper(@NonNull Context context, @NonNull Config config) { + this(new Injector(context, config)); + } + + @VisibleForTesting + public DexoptHelper(@NonNull Injector injector) { + mInjector = injector; + } + + /** + * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link + * ArtManagerLocal#dexoptPackages}. + */ + @NonNull + public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull List<String> packageNames, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor) { + return dexopt(snapshot, packageNames, params, cancellationSignal, dexoptExecutor, + null /* progressCallbackExecutor */, null /* progressCallback */); + } + + /** + * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link + * ArtManagerLocal#dexoptPackages}. + */ + @NonNull + public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull List<String> packageNames, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor, + @Nullable Executor progressCallbackExecutor, + @Nullable Consumer<OperationProgress> progressCallback) { + return dexoptPackages( + getPackageStates(snapshot, packageNames, + (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0), + params, cancellationSignal, dexoptExecutor, progressCallbackExecutor, + progressCallback); + } + + /** + * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link + * ArtManagerLocal#dexoptPackages}. + */ + @NonNull + private DexoptResult dexoptPackages(@NonNull List<PackageState> pkgStates, + @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal, + @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor, + @Nullable Consumer<OperationProgress> progressCallback) { + int callingUid = Binder.getCallingUid(); + long identityToken = Binder.clearCallingIdentity(); + PowerManager.WakeLock wakeLock = null; + + try { + // Acquire a wake lock. + PowerManager powerManager = mInjector.getPowerManager(); + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + wakeLock.setWorkSource(new WorkSource(callingUid)); + wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS); + + List<CompletableFuture<PackageDexoptResult>> futures = new ArrayList<>(); + + // Child threads will set their own listeners on the cancellation signal, so we must + // create a separate cancellation signal for each of them so that the listeners don't + // overwrite each other. + List<CancellationSignal> childCancellationSignals = + pkgStates.stream() + .map(pkgState -> new CancellationSignal()) + .collect(Collectors.toList()); + cancellationSignal.setOnCancelListener(() -> { + for (CancellationSignal childCancellationSignal : childCancellationSignals) { + childCancellationSignal.cancel(); + } + }); + + for (int i = 0; i < pkgStates.size(); i++) { + PackageState pkgState = pkgStates.get(i); + CancellationSignal childCancellationSignal = childCancellationSignals.get(i); + futures.add(CompletableFuture.supplyAsync(() -> { + return dexoptPackage(pkgState, params, childCancellationSignal); + }, dexoptExecutor)); + } + + if (progressCallback != null) { + CompletableFuture.runAsync(() -> { + progressCallback.accept( + OperationProgress.create(0 /* current */, futures.size())); + }, progressCallbackExecutor); + AtomicInteger current = new AtomicInteger(0); + for (CompletableFuture<PackageDexoptResult> future : futures) { + future.thenRunAsync(() -> { + progressCallback.accept(OperationProgress.create( + current.incrementAndGet(), futures.size())); + }, progressCallbackExecutor); + } + } + + List<PackageDexoptResult> results = + futures.stream().map(Utils::getFuture).collect(Collectors.toList()); + + var result = + DexoptResult.create(params.getCompilerFilter(), params.getReason(), results); + + for (Callback<DexoptDoneCallback, Boolean> doneCallback : + mInjector.getConfig().getDexoptDoneCallbacks()) { + boolean onlyIncludeUpdates = doneCallback.extra(); + if (onlyIncludeUpdates) { + List<PackageDexoptResult> filteredResults = + results.stream() + .filter(PackageDexoptResult::hasUpdatedArtifacts) + .collect(Collectors.toList()); + if (!filteredResults.isEmpty()) { + var resultForCallback = DexoptResult.create( + params.getCompilerFilter(), params.getReason(), filteredResults); + CompletableFuture.runAsync(() -> { + doneCallback.get().onDexoptDone(resultForCallback); + }, doneCallback.executor()); + } + } else { + CompletableFuture.runAsync(() -> { + doneCallback.get().onDexoptDone(result); + }, doneCallback.executor()); + } + } + + return result; + } finally { + if (wakeLock != null) { + wakeLock.release(); + } + Binder.restoreCallingIdentity(identityToken); + // Make sure nothing leaks even if the caller holds `cancellationSignal` forever. + cancellationSignal.setOnCancelListener(null); + } + } + + /** + * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link + * ArtManagerLocal#dexoptPackages}. + */ + @NonNull + private PackageDexoptResult dexoptPackage(@NonNull PackageState pkgState, + @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) { + List<DexContainerFileDexoptResult> results = new ArrayList<>(); + Function<Integer, PackageDexoptResult> createResult = (packageLevelStatus) + -> PackageDexoptResult.create( + pkgState.getPackageName(), results, packageLevelStatus); + + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + + if (!canDexoptPackage(pkgState)) { + return createResult.apply(null /* packageLevelStatus */); + } + + if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) { + // Throws if the split is not found. + PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName()); + } + + try (var tracing = new Utils.Tracing("dexopt")) { + if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) { + if (cancellationSignal.isCanceled()) { + return createResult.apply(DexoptResult.DEXOPT_CANCELLED); + } + + results.addAll( + mInjector.getPrimaryDexopter(pkgState, pkg, params, cancellationSignal) + .dexopt()); + } + + if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) { + if (cancellationSignal.isCanceled()) { + return createResult.apply(DexoptResult.DEXOPT_CANCELLED); + } + + results.addAll( + mInjector.getSecondaryDexopter(pkgState, pkg, params, cancellationSignal) + .dexopt()); + } + } catch (RemoteException e) { + Utils.logArtdException(e); + return createResult.apply(DexoptResult.DEXOPT_FAILED); + } + + return createResult.apply(null /* packageLevelStatus */); + } + + private boolean canDexoptPackage(@NonNull PackageState pkgState) { + // getAppHibernationManager may return null here during boot time compilation, which will + // make this function return true incorrectly for packages that shouldn't be dexopted due to + // hibernation. Further discussion in comments in ArtManagerLocal.getDefaultPackages. + return Utils.canDexoptPackage(pkgState, mInjector.getAppHibernationManager()); + } + + @NonNull + private List<PackageState> getPackageStates( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull List<String> packageNames, boolean includeDependencies) { + var pkgStates = new LinkedHashMap<String, PackageState>(); + Set<String> visitedLibraries = new HashSet<>(); + Queue<SharedLibrary> queue = new LinkedList<>(); + + Consumer<SharedLibrary> maybeEnqueue = library -> { + // The package name is not null if the library is an APK. + // TODO(jiakaiz): Support JAR libraries. + if (library.getPackageName() != null && !library.isNative() + && !visitedLibraries.contains(library.getName())) { + visitedLibraries.add(library.getName()); + queue.add(library); + } + }; + + for (String packageName : packageNames) { + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + Utils.getPackageOrThrow(pkgState); + pkgStates.put(packageName, pkgState); + if (includeDependencies && canDexoptPackage(pkgState)) { + for (SharedLibrary library : pkgState.getSharedLibraryDependencies()) { + maybeEnqueue.accept(library); + } + } + } + + SharedLibrary library; + while ((library = queue.poll()) != null) { + String packageName = library.getPackageName(); + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + if (canDexoptPackage(pkgState)) { + pkgStates.put(packageName, pkgState); + + // Note that `library.getDependencies()` is different from + // `pkgState.getUsesLibraries()`. Different libraries can belong to the same + // package. `pkgState.getUsesLibraries()` returns a union of dependencies of + // libraries that belong to the same package, which is not what we want here. + // Therefore, this loop cannot be unified with the one above. + for (SharedLibrary dep : library.getDependencies()) { + maybeEnqueue.accept(dep); + } + } + } + + // `LinkedHashMap` guarantees deterministic order. + return new ArrayList<>(pkgStates.values()); + } + + /** + * Injector pattern for testing purpose. + * + * @hide + */ + @VisibleForTesting + public static class Injector { + @NonNull private final Context mContext; + @NonNull private final Config mConfig; + + Injector(@NonNull Context context, @NonNull Config config) { + mContext = context; + mConfig = config; + + // Call the getters for the dependencies that aren't optional, to ensure correct + // initialization order. + getAppHibernationManager(); + getPowerManager(); + } + + @NonNull + PrimaryDexopter getPrimaryDexopter(@NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + return new PrimaryDexopter(mContext, pkgState, pkg, params, cancellationSignal); + } + + @NonNull + SecondaryDexopter getSecondaryDexopter(@NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + return new SecondaryDexopter(mContext, pkgState, pkg, params, cancellationSignal); + } + + @NonNull + public AppHibernationManager getAppHibernationManager() { + return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class)); + } + + @NonNull + public PowerManager getPowerManager() { + return Objects.requireNonNull(mContext.getSystemService(PowerManager.class)); + } + + @NonNull + public Config getConfig() { + return mConfig; + } + } +} diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java new file mode 100644 index 0000000000..446d9483b6 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/Dexopter.java @@ -0,0 +1,713 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation; +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.ProfilePath.TmpProfilePath; +import static com.android.server.art.Utils.Abi; +import static com.android.server.art.model.ArtFlags.DexoptFlags; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; + +import android.R; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.role.RoleManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemProperties; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.DexFile; + +import com.google.auto.value.AutoValue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { + private static final String TAG = ArtManagerLocal.TAG; + private static final List<String> ART_PACKAGE_NAMES = + List.of("com.google.android.art", "com.android.art", "com.google.android.go.art"); + + @NonNull protected final Injector mInjector; + @NonNull protected final PackageState mPkgState; + /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */ + @NonNull protected final AndroidPackage mPkg; + @NonNull protected final DexoptParams mParams; + @NonNull protected final CancellationSignal mCancellationSignal; + + protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + mInjector = injector; + mPkgState = pkgState; + mPkg = pkg; + mParams = params; + mCancellationSignal = cancellationSignal; + if (pkgState.getAppId() < 0) { + throw new IllegalStateException( + "Package '" + pkgState.getPackageName() + "' has invalid app ID"); + } + } + + /** + * DO NOT use this method directly. Use {@link + * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, + * DexoptParams)}. + */ + @NonNull + public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException { + List<DexContainerFileDexoptResult> results = new ArrayList<>(); + + boolean isInDalvikCache = isInDalvikCache(); + + for (DexInfoType dexInfo : getDexInfoList()) { + ProfilePath profile = null; + boolean succeeded = true; + try { + if (!isDexoptable(dexInfo)) { + continue; + } + + String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo); + if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) { + continue; + } + + boolean needsToBeShared = needsToBeShared(dexInfo); + boolean isOtherReadable = true; + // If true, implies that the profile has changed since the last compilation. + boolean profileMerged = false; + if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) { + if (needsToBeShared) { + profile = initReferenceProfile(dexInfo); + } else { + Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo); + if (pair != null) { + profile = pair.first; + isOtherReadable = pair.second; + } + ProfilePath mergedProfile = mergeProfiles(dexInfo, profile); + if (mergedProfile != null) { + if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) { + mInjector.getArtd().deleteProfile(profile); + } + profile = mergedProfile; + isOtherReadable = false; + profileMerged = true; + } + } + if (profile == null) { + // A profile guided dexopt with no profile is essentially 'verify', + // and dex2oat already makes this transformation. However, we need to + // explicitly make this transformation here to guide the later decisions + // such as whether the artifacts can be public and whether dexopt is needed. + compilerFilter = needsToBeShared + ? ReasonMapping.getCompilerFilterForShared() + : "verify"; + } + } + boolean isProfileGuidedCompilerFilter = + DexFile.isProfileGuidedCompilerFilter(compilerFilter); + Utils.check(isProfileGuidedCompilerFilter == (profile != null)); + + boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable) + && isDexFilePublic(dexInfo); + Utils.check(Utils.implies(needsToBeShared, canBePublic)); + PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic); + + DexoptOptions dexoptOptions = + getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter); + + for (Abi abi : getAllAbis(dexInfo)) { + @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED; + long wallTimeMs = 0; + long cpuTimeMs = 0; + long sizeBytes = 0; + long sizeBeforeBytes = 0; + boolean isSkippedDueToStorageLow = false; + try { + var target = DexoptTarget.<DexInfoType>builder() + .setDexInfo(dexInfo) + .setIsa(abi.isa()) + .setIsInDalvikCache(isInDalvikCache) + .setCompilerFilter(compilerFilter) + .build(); + var options = GetDexoptNeededOptions.builder() + .setProfileMerged(profileMerged) + .setFlags(mParams.getFlags()) + .setNeedsToBePublic(needsToBeShared) + .build(); + + GetDexoptNeededResult getDexoptNeededResult = + getDexoptNeeded(target, options); + + if (!getDexoptNeededResult.isDexoptNeeded) { + continue; + } + + try { + // `StorageManager.getAllocatableBytes` returns (free space + space used + // by clearable cache - low storage threshold). Since we only compare + // the result with 0, the clearable cache doesn't make a difference. + // When the free space is below the threshold, there should be no + // clearable cache left because system cleans up cache every minute. + if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0 + && mInjector.getStorageManager().getAllocatableBytes( + mPkg.getStorageUuid()) + <= 0) { + isSkippedDueToStorageLow = true; + continue; + } + } catch (IOException e) { + Log.e(TAG, "Failed to check storage. Assuming storage not low", e); + } + + IArtdCancellationSignal artdCancellationSignal = + mInjector.getArtd().createCancellationSignal(); + mCancellationSignal.setOnCancelListener(() -> { + try { + artdCancellationSignal.cancel(); + } catch (RemoteException e) { + Log.e(TAG, "An error occurred when sending a cancellation signal", + e); + } + }); + + ArtdDexoptResult dexoptResult = dexoptFile(target, profile, + getDexoptNeededResult, permissionSettings, + mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal); + status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED + : DexoptResult.DEXOPT_PERFORMED; + wallTimeMs = dexoptResult.wallTimeMs; + cpuTimeMs = dexoptResult.cpuTimeMs; + sizeBytes = dexoptResult.sizeBytes; + sizeBeforeBytes = dexoptResult.sizeBeforeBytes; + + if (status == DexoptResult.DEXOPT_CANCELLED) { + return results; + } + } catch (ServiceSpecificException e) { + // Log the error and continue. + Log.e(TAG, + String.format("Failed to dexopt [packageName = %s, dexPath = %s, " + + "isa = %s, classLoaderContext = %s]", + mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(), + dexInfo.classLoaderContext()), + e); + status = DexoptResult.DEXOPT_FAILED; + } finally { + var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(), + abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs, + cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow); + Log.i(TAG, + String.format("Dexopt result: [packageName = %s] %s", + mPkgState.getPackageName(), result)); + results.add(result); + if (status != DexoptResult.DEXOPT_SKIPPED + && status != DexoptResult.DEXOPT_PERFORMED) { + succeeded = false; + } + // Make sure artd does not leak even if the caller holds + // `mCancellationSignal` forever. + mCancellationSignal.setOnCancelListener(null); + } + } + + if (profile != null && succeeded) { + if (profile.getTag() == ProfilePath.tmpProfilePath) { + // Commit the profile only if dexopt succeeds. + if (commitProfileChanges(profile.getTmpProfilePath())) { + profile = null; + } + } + if (profileMerged) { + // Note that this is just an optimization, to reduce the amount of data that + // the runtime writes on every profile save. The profile merge result on the + // next run won't change regardless of whether the cleanup is done or not + // because profman only looks at the diff. + // A caveat is that it may delete more than what has been merged, if the + // runtime writes additional entries between the merge and the cleanup, but + // this is fine because the runtime writes all JITed classes and methods on + // every save and the additional entries will likely be written back on the + // next save. + cleanupCurProfiles(dexInfo); + } + } + } finally { + if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) { + mInjector.getArtd().deleteProfile(profile); + } + } + } + + return results; + } + + @NonNull + private String adjustCompilerFilter( + @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) { + if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) { + String systemUiCompilerFilter = getSystemUiCompilerFilter(); + if (!systemUiCompilerFilter.isEmpty()) { + return systemUiCompilerFilter; + } + } + + if (mInjector.isLauncherPackage(mPkgState.getPackageName())) { + return "speed-profile"; + } + + // We force vmSafeMode on debuggable apps as well: + // - the runtime ignores their compiled code + // - they generally have lots of methods that could make the compiler used run out of + // memory (b/130828957) + // Note that forcing the compiler filter here applies to all compilations (even if they + // are done via adb shell commands). This is okay because the runtime will ignore the + // compiled code anyway. + if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) { + return DexFile.getSafeModeCompilerFilter(targetCompilerFilter); + } + + // We cannot do AOT compilation if we don't have a valid class loader context. + if (dexInfo.classLoaderContext() == null) { + return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify" + : targetCompilerFilter; + } + + // This application wants to use the embedded dex in the APK, rather than extracted or + // locally compiled variants, so we only verify it. + // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat + // won't extract the dex code because the APK is uncompressed, and the assumption is that + // such applications always use uncompressed APKs. + if (mPkg.isUseEmbeddedDex()) { + return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify" + : targetCompilerFilter; + } + + return targetCompilerFilter; + } + + @NonNull + private String getSystemUiCompilerFilter() { + String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter"); + if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) { + throw new IllegalStateException( + "Got invalid compiler filter '" + compilerFilter + "' for System UI"); + } + return compilerFilter; + } + + /** @see Utils#getOrInitReferenceProfile */ + @Nullable + private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo) + throws RemoteException { + return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(), + buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo), + buildOutputProfile(dexInfo, true /* isPublic */)); + } + + @Nullable + private ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo) throws RemoteException { + return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(), + getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */)); + } + + @NonNull + private DexoptOptions getDexoptOptions( + @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) { + DexoptOptions dexoptOptions = new DexoptOptions(); + dexoptOptions.compilationReason = mParams.getReason(); + dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion(); + dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable(); + // Generating a meaningful app image needs a profile to determine what to include in the + // image. Otherwise, the app image will be nearly empty. + dexoptOptions.generateAppImage = + isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled(); + dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled(); + dexoptOptions.comments = String.format( + "app-version-name:%s,app-version-code:%d,art-version:%d", mPkg.getVersionName(), + mPkg.getLongVersionCode(), mInjector.getArtVersion()); + return dexoptOptions; + } + + private boolean isAlwaysDebuggable() { + return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */); + } + + private boolean isAppImageEnabled() { + return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty(); + } + + private boolean isHiddenApiPolicyEnabled() { + return mPkgState.getHiddenApiEnforcementPolicy() + != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED; + } + + @NonNull + GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target, + @NonNull GetDexoptNeededOptions options) throws RemoteException { + int dexoptTrigger = getDexoptTrigger(target, options); + + // The result should come from artd even if all the bits of `dexoptTrigger` are set + // because the result also contains information about the usable VDEX file. + // Note that the class loader context can be null. In that case, we intentionally pass the + // null value down to lower levels to indicate that the class loader context check should be + // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`). + GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded( + target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(), + target.compilerFilter(), dexoptTrigger); + + return result; + } + + int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target, + @NonNull GetDexoptNeededOptions options) throws RemoteException { + if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) { + return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME + | DexoptTrigger.COMPILER_FILTER_IS_WORSE + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE + | DexoptTrigger.NEED_EXTRACTION; + } + + if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) { + return DexoptTrigger.COMPILER_FILTER_IS_WORSE; + } + + int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + if (options.profileMerged()) { + dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME; + } + + ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath( + target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache()); + + if (options.needsToBePublic() + && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath) + == FileVisibility.NOT_OTHER_READABLE) { + // Typically, this happens after an app starts being used by other apps. + // This case should be the same as force as we have no choice but to trigger a new + // dexopt. + dexoptTrigger |= + DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE; + } + + return dexoptTrigger; + } + + private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target, + @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult, + @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass, + @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal) + throws RemoteException { + OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(), + target.isa(), target.isInDalvikCache(), permissionSettings); + + VdexPath inputVdex = + getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa()); + + DexMetadataPath dmFile = getDmFile(target.dexInfo()); + if (dmFile != null + && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) { + // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g., + // "install-dm"). + // Note that this only applies to reasons for app install because the goal is to give + // Play a signal that a DM file is downloaded at install time. We actually pass the DM + // file regardless of the compilation reason, but we don't append a suffix when the + // compilation reason is not a reason for app install. + // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by + // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The + // compilation reason can still be "install-dm" even if dex2oat left all contents of the + // DM file unused or an empty DM file is passed to dex2oat. + dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm"; + } + + return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(), + target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex, + dmFile, priorityClass, dexoptOptions, artdCancellationSignal); + } + + @Nullable + private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult, + @NonNull String dexPath, @NonNull String isa) { + if (!getDexoptNeededResult.isVdexUsable) { + return null; + } + switch (getDexoptNeededResult.artifactsLocation) { + case ArtifactsLocation.DALVIK_CACHE: + return VdexPath.artifactsPath( + AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */)); + case ArtifactsLocation.NEXT_TO_DEX: + return VdexPath.artifactsPath( + AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */)); + case ArtifactsLocation.DM: + // The DM file is passed to dex2oat as a separate flag whenever it exists. + return null; + default: + // This should never happen as the value is got from artd. + throw new IllegalStateException( + "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation); + } + } + + @Nullable + private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException { + DexMetadataPath path = buildDmPath(dexInfo); + if (path == null) { + return null; + } + try { + if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) { + return path; + } + } catch (ServiceSpecificException e) { + Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e); + } + return null; + } + + private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException { + try { + mInjector.getArtd().commitTmpProfile(profile); + return true; + } catch (ServiceSpecificException e) { + Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath), + e); + return false; + } + } + + @Nullable + private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo, + @Nullable ProfilePath referenceProfile) throws RemoteException { + OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */); + + try { + if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output, + List.of(dexInfo.dexPath()), new MergeProfileOptions())) { + return ProfilePath.tmpProfilePath(output.profilePath); + } + } catch (ServiceSpecificException e) { + Log.e(TAG, + "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath), + e); + } + + return null; + } + + private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException { + for (ProfilePath profile : getCurProfiles(dexInfo)) { + mInjector.getArtd().deleteProfile(profile); + } + } + + // Methods to be implemented by child classes. + + /** Returns true if the artifacts should be written to the global dalvik-cache directory. */ + protected abstract boolean isInDalvikCache() throws RemoteException; + + /** Returns information about all dex files. */ + @NonNull protected abstract List<DexInfoType> getDexInfoList(); + + /** Returns true if the given dex file should be dexopted. */ + protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo); + + /** + * Returns true if the artifacts should be shared with other apps. Note that this must imply + * {@link #isDexFilePublic(DexInfoType)}. + */ + protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo); + + /** + * Returns true if the filesystem permission of the dex file has the "read" bit for "others" + * (S_IROTH). + */ + protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo); + + /** + * Returns a list of external profiles (e.g., a DM profile) that the reference profile can be + * initialized from, in the order of preference. + */ + @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo); + + /** Returns the permission settings to use for the artifacts of the given dex file. */ + @NonNull + protected abstract PermissionSettings getPermissionSettings( + @NonNull DexInfoType dexInfo, boolean canBePublic); + + /** Returns all ABIs that the given dex file should be compiled for. */ + @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo); + + /** Returns the path to the reference profile of the given dex file. */ + @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo); + + /** Returns true if app image (--app-image-fd) is allowed. */ + protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo); + + /** + * Returns the data structure that represents the temporary profile to use during processing. + */ + @NonNull + protected abstract OutputProfile buildOutputProfile( + @NonNull DexInfoType dexInfo, boolean isPublic); + + /** Returns the paths to the current profiles of the given dex file. */ + @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo); + + /** + * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file + * should be passed. + */ + @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo); + + @AutoValue + abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> { + abstract @NonNull DexInfoType dexInfo(); + abstract @NonNull String isa(); + abstract boolean isInDalvikCache(); + abstract @NonNull String compilerFilter(); + + static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() { + return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>(); + } + + @AutoValue.Builder + abstract static class Builder<DexInfoType extends DetailedDexInfo> { + abstract Builder setDexInfo(@NonNull DexInfoType value); + abstract Builder setIsa(@NonNull String value); + abstract Builder setIsInDalvikCache(boolean value); + abstract Builder setCompilerFilter(@NonNull String value); + abstract DexoptTarget<DexInfoType> build(); + } + } + + @AutoValue + abstract static class GetDexoptNeededOptions { + abstract @DexoptFlags int flags(); + abstract boolean profileMerged(); + abstract boolean needsToBePublic(); + + static Builder builder() { + return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setFlags(@DexoptFlags int value); + abstract Builder setProfileMerged(boolean value); + abstract Builder setNeedsToBePublic(boolean value); + abstract GetDexoptNeededOptions build(); + } + } + + /** + * Injector pattern for testing purpose. + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public static class Injector { + @NonNull private final Context mContext; + + public Injector(@NonNull Context context) { + mContext = context; + + // Call the getters for various dependencies, to ensure correct initialization order. + getUserManager(); + getDexUseManager(); + getStorageManager(); + ArtModuleServiceInitializer.getArtModuleServiceManager(); + } + + public boolean isSystemUiPackage(@NonNull String packageName) { + return Utils.isSystemUiPackage(mContext, packageName); + } + + public boolean isLauncherPackage(@NonNull String packageName) { + return Utils.isLauncherPackage(mContext, packageName); + } + + @NonNull + public UserManager getUserManager() { + return Objects.requireNonNull(mContext.getSystemService(UserManager.class)); + } + + @NonNull + public DexUseManagerLocal getDexUseManager() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(DexUseManagerLocal.class)); + } + + @NonNull + public IArtd getArtd() { + return Utils.getArtd(); + } + + @NonNull + public StorageManager getStorageManager() { + return Objects.requireNonNull(mContext.getSystemService(StorageManager.class)); + } + + @NonNull + private PackageManagerLocal getPackageManagerLocal() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(PackageManagerLocal.class)); + } + + public long getArtVersion() { + try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) { + Map<String, PackageState> packageStates = snapshot.getPackageStates(); + for (String artPackageName : ART_PACKAGE_NAMES) { + PackageState pkgState = packageStates.get(artPackageName); + if (pkgState != null) { + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + return pkg.getLongVersionCode(); + } + } + } + return -1; + } + } +} diff --git a/libartservice/service/java/com/android/server/art/DumpHelper.java b/libartservice/service/java/com/android/server/art/DumpHelper.java new file mode 100644 index 0000000000..2a640ec48d --- /dev/null +++ b/libartservice/service/java/com/android/server/art/DumpHelper.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.DexUseManagerLocal.DexLoader; +import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; +import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus; + +import android.annotation.NonNull; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.VMRuntime; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A helper class to handle dump. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class DumpHelper { + private static final String TAG = ArtManagerLocal.TAG; + + @NonNull private final Injector mInjector; + + public DumpHelper(@NonNull ArtManagerLocal artManagerLocal) { + this(new Injector(artManagerLocal)); + } + + @VisibleForTesting + public DumpHelper(@NonNull Injector injector) { + mInjector = injector; + } + + /** Handles {@link ArtManagerLocal#dump(PrintWriter, PackageManagerLocal.FilteredSnapshot)}. */ + public void dump( + @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) { + snapshot.getPackageStates() + .values() + .stream() + .sorted(Comparator.comparing(PackageState::getPackageName)) + .forEach(pkgState -> dumpPackage(pw, snapshot, pkgState)); + } + + /** + * Handles {@link + * ArtManagerLocal#dumpPackage(PrintWriter, PackageManagerLocal.FilteredSnapshot, String)}. + */ + public void dumpPackage(@NonNull PrintWriter pw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull PackageState pkgState) { + // An APEX has a uid of -1. + // TODO(b/256637152): Consider using `isApex` instead. + if (pkgState.getAppId() <= 0 || pkgState.getAndroidPackage() == null) { + return; + } + + var ipw = new IndentingPrintWriter(pw); + + String packageName = pkgState.getPackageName(); + ipw.printf("[%s]\n", packageName); + + List<DexContainerFileDexoptStatus> statuses = + mInjector.getArtManagerLocal() + .getDexoptStatus(snapshot, packageName) + .getDexContainerFileDexoptStatuses(); + Map<String, SecondaryDexInfo> secondaryDexInfoByDexPath = + mInjector.getDexUseManager() + .getSecondaryDexInfo(packageName) + .stream() + .collect(Collectors.toMap(SecondaryDexInfo::dexPath, Function.identity())); + + // Use LinkedHashMap to keep the order. They are ordered by their split indexes. + var primaryStatusesByDexPath = + new LinkedHashMap<String, List<DexContainerFileDexoptStatus>>(); + // Use TreeMap to force lexicographical order. + var secondaryStatusesByDexPath = new TreeMap<String, List<DexContainerFileDexoptStatus>>(); + for (DexContainerFileDexoptStatus fileStatus : statuses) { + if (fileStatus.isPrimaryDex()) { + primaryStatusesByDexPath + .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>()) + .add(fileStatus); + } else if (secondaryDexInfoByDexPath.containsKey(fileStatus.getDexContainerFile())) { + // The condition above is false only if a change occurs between + // `getDexoptStatus` and `getSecondaryDexInfo`, which is an edge case. + secondaryStatusesByDexPath + .computeIfAbsent(fileStatus.getDexContainerFile(), k -> new ArrayList<>()) + .add(fileStatus); + } + } + + ipw.increaseIndent(); + for (List<DexContainerFileDexoptStatus> fileStatuses : primaryStatusesByDexPath.values()) { + dumpPrimaryDex(ipw, snapshot, fileStatuses, packageName); + } + if (!secondaryStatusesByDexPath.isEmpty()) { + ipw.println("known secondary dex files:"); + ipw.increaseIndent(); + for (Map.Entry<String, List<DexContainerFileDexoptStatus>> entry : + secondaryStatusesByDexPath.entrySet()) { + dumpSecondaryDex(ipw, snapshot, entry.getValue(), packageName, + secondaryDexInfoByDexPath.get(entry.getKey())); + } + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + } + + private void dumpPrimaryDex(@NonNull IndentingPrintWriter ipw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName) { + String dexPath = fileStatuses.get(0).getDexContainerFile(); + ipw.printf("path: %s\n", dexPath); + ipw.increaseIndent(); + dumpFileStatuses(ipw, fileStatuses); + dumpUsedByOtherApps(ipw, snapshot, + mInjector.getDexUseManager().getPrimaryDexLoaders(packageName, dexPath), + packageName); + ipw.decreaseIndent(); + } + + private void dumpSecondaryDex(@NonNull IndentingPrintWriter ipw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName, + @NonNull SecondaryDexInfo info) { + String dexPath = fileStatuses.get(0).getDexContainerFile(); + @FileVisibility int visibility = getDexFileVisibility(dexPath); + ipw.println(dexPath + + (visibility == FileVisibility.NOT_FOUND + ? " (removed)" + : (visibility == FileVisibility.OTHER_READABLE ? " (public)" + : ""))); + ipw.increaseIndent(); + dumpFileStatuses(ipw, fileStatuses); + ipw.printf("class loader context: %s\n", info.displayClassLoaderContext()); + TreeMap<DexLoader, String> classLoaderContexts = + info.loaders().stream().collect(Collectors.toMap(loader + -> loader, + loader + -> mInjector.getDexUseManager().getSecondaryClassLoaderContext( + packageName, dexPath, loader), + (a, b) -> a, TreeMap::new)); + // We should print all class loader contexts even if `info.displayClassLoaderContext()` is + // not `VARYING_CLASS_LOADER_CONTEXTS`. This is because `info.displayClassLoaderContext()` + // may show the only supported class loader context while other apps have unsupported ones. + if (classLoaderContexts.values().stream().distinct().count() >= 2) { + ipw.increaseIndent(); + for (var entry : classLoaderContexts.entrySet()) { + // entry.getValue() may be null due to a race, but it's an edge case. + ipw.printf("%s: %s\n", entry.getKey(), entry.getValue()); + } + ipw.decreaseIndent(); + } + dumpUsedByOtherApps(ipw, snapshot, info.loaders(), packageName); + ipw.decreaseIndent(); + } + + private void dumpFileStatuses( + @NonNull IndentingPrintWriter ipw, List<DexContainerFileDexoptStatus> fileStatuses) { + for (DexContainerFileDexoptStatus fileStatus : fileStatuses) { + ipw.printf("%s: [status=%s] [reason=%s]%s\n", + VMRuntime.getInstructionSet(fileStatus.getAbi()), + fileStatus.getCompilerFilter(), fileStatus.getCompilationReason(), + fileStatus.isPrimaryAbi() ? " [primary-abi]" : ""); + ipw.increaseIndent(); + ipw.printf("[location is %s]\n", fileStatus.getLocationDebugString()); + ipw.decreaseIndent(); + } + } + + private void dumpUsedByOtherApps(@NonNull IndentingPrintWriter ipw, + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, + @NonNull Set<DexLoader> dexLoaders, @NonNull String packageName) { + List<DexLoader> otherApps = + dexLoaders.stream() + .filter(loader -> DexUseManagerLocal.isLoaderOtherApp(loader, packageName)) + .collect(Collectors.toList()); + if (!otherApps.isEmpty()) { + ipw.printf("used by other apps: [%s]\n", + otherApps.stream() + .sorted() + .map(loader -> getLoaderState(snapshot, loader)) + .collect(Collectors.joining(", "))); + } + } + + @NonNull + private String getLoaderState( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull DexLoader loader) { + var result = new StringBuilder(loader.toString()); + PackageState loadingPkgState = snapshot.getPackageState(loader.loadingPackageName()); + if (loadingPkgState == null) { + // This can happen because the information held by DexUseManagerLocal can be outdated. + // We don't want to clean up the entry at this point because we don't want the dump + // operation to have an side effect. + result.append(" (removed)"); + return result.toString(); + } + Utils.Abi abi = Utils.getPrimaryAbi(loadingPkgState); + result.append(String.format(" (isa=%s)", abi.isa())); + return result.toString(); + } + + private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) { + try { + return mInjector.getArtd().getDexFileVisibility(dexPath); + } catch (ServiceSpecificException | RemoteException e) { + Log.e(TAG, "Failed to get visibility of " + dexPath, e); + return FileVisibility.NOT_FOUND; + } + } + + /** Injector pattern for testing purpose. */ + @VisibleForTesting + public static class Injector { + @NonNull private final ArtManagerLocal mArtManagerLocal; + + Injector(@NonNull ArtManagerLocal artManagerLocal) { + mArtManagerLocal = artManagerLocal; + } + + @NonNull + public ArtManagerLocal getArtManagerLocal() { + return mArtManagerLocal; + } + + @NonNull + public DexUseManagerLocal getDexUseManager() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(DexUseManagerLocal.class)); + } + + @NonNull + public IArtd getArtd() { + return Utils.getArtd(); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java new file mode 100644 index 0000000000..b717786394 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/IndentingPrintWriter.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * A minimal copy of the hidden {@link android.util.IndentingPrintWriter}. + * + * TODO(b/264968147): Avoid the duplication. + * + * @hide + */ +public class IndentingPrintWriter extends PrintWriter { + private final String mSingleIndent; + + /** Mutable version of current indent */ + private StringBuilder mIndentBuilder = new StringBuilder(); + /** Cache of current {@link #mIndentBuilder} value */ + private char[] mCurrentIndent; + /** Length of current line being built, excluding any indent */ + private int mCurrentLength; + + /** + * Flag indicating if we're currently sitting on an empty line, and that + * next write should be prefixed with the current indent. + */ + private boolean mEmptyLine = true; + + private char[] mSingleChar = new char[1]; + + public IndentingPrintWriter(@NonNull Writer writer) { + super(writer); + mSingleIndent = " "; + } + + /** + * Increases the indent starting with the next printed line. + */ + @NonNull + public IndentingPrintWriter increaseIndent() { + mIndentBuilder.append(mSingleIndent); + mCurrentIndent = null; + return this; + } + + /** + * Decreases the indent starting with the next printed line. + */ + @NonNull + public IndentingPrintWriter decreaseIndent() { + mIndentBuilder.delete(0, mSingleIndent.length()); + mCurrentIndent = null; + return this; + } + + @Override + public void println() { + write('\n'); + } + + @Override + public void write(int c) { + mSingleChar[0] = (char) c; + write(mSingleChar, 0, 1); + } + + @Override + public void write(@NonNull String s, int off, int len) { + final char[] buf = new char[len]; + s.getChars(off, len - off, buf, 0); + write(buf, 0, len); + } + + @Override + public void write(@NonNull char[] buf, int offset, int count) { + final int indentLength = mIndentBuilder.length(); + final int bufferEnd = offset + count; + int lineStart = offset; + int lineEnd = offset; + + // March through incoming buffer looking for newlines + while (lineEnd < bufferEnd) { + char ch = buf[lineEnd++]; + mCurrentLength++; + if (ch == '\n') { + maybeWriteIndent(); + super.write(buf, lineStart, lineEnd - lineStart); + lineStart = lineEnd; + mEmptyLine = true; + mCurrentLength = 0; + } + } + + if (lineStart != lineEnd) { + maybeWriteIndent(); + super.write(buf, lineStart, lineEnd - lineStart); + } + } + + private void maybeWriteIndent() { + if (mEmptyLine) { + mEmptyLine = false; + if (mIndentBuilder.length() != 0) { + if (mCurrentIndent == null) { + mCurrentIndent = mIndentBuilder.toString().toCharArray(); + } + super.write(mCurrentIndent, 0, mCurrentIndent.length); + } + } + } +} diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java new file mode 100644 index 0000000000..64f9bc5418 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.Immutable; +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.SharedLibrary; + +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.DexClassLoader; +import dalvik.system.PathClassLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class PrimaryDexUtils { + public static final String PROFILE_PRIMARY = "primary"; + private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName(); + + /** + * Returns the basic information about all primary dex files belonging to the package. The + * return value is a list where the entry at index 0 is the information about the base APK, and + * the entry at index i is the information about the (i-1)-th split APK. + */ + @NonNull + public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackage pkg) { + return getDexInfoImpl(pkg) + .stream() + .map(builder -> builder.build()) + .collect(Collectors.toList()); + } + + /** + * Same as above, but requires {@link PackageState} in addition, and returns the detailed + * information, including the class loader context. + */ + @NonNull + public static List<DetailedPrimaryDexInfo> getDetailedDexInfo( + @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) { + return getDetailedDexInfoImpl(pkgState, pkg) + .stream() + .map(builder -> builder.buildDetailed()) + .collect(Collectors.toList()); + } + + /** Returns the basic information about a dex file specified by {@code splitName}. */ + @NonNull + public static PrimaryDexInfo getDexInfoBySplitName( + @NonNull AndroidPackage pkg, @Nullable String splitName) { + if (splitName == null) { + return getDexInfo(pkg).get(0); + } else { + return getDexInfo(pkg) + .stream() + .filter(info -> splitName.equals(info.splitName())) + .findFirst() + .orElseThrow(() -> { + return new IllegalArgumentException( + String.format("Split '%s' not found", splitName)); + }); + } + } + + @NonNull + private static List<PrimaryDexInfoBuilder> getDexInfoImpl(@NonNull AndroidPackage pkg) { + List<PrimaryDexInfoBuilder> dexInfos = new ArrayList<>(); + + for (var split : pkg.getSplits()) { + dexInfos.add(new PrimaryDexInfoBuilder(split)); + } + + return dexInfos; + } + + @NonNull + private static List<PrimaryDexInfoBuilder> getDetailedDexInfoImpl( + @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) { + List<PrimaryDexInfoBuilder> dexInfos = getDexInfoImpl(pkg); + + PrimaryDexInfoBuilder baseApk = dexInfos.get(0); + baseApk.mClassLoaderName = baseApk.mSplit.getClassLoaderName(); + File baseDexFile = new File(baseApk.mSplit.getPath()); + baseApk.mRelativeDexPath = baseDexFile.getName(); + + // Shared libraries are the dependencies of the base APK. + baseApk.mSharedLibrariesContext = + encodeSharedLibraries(pkgState.getSharedLibraryDependencies()); + + boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg); + + for (int i = 1; i < dexInfos.size(); i++) { + var dexInfoBuilder = dexInfos.get(i); + File splitDexFile = new File(dexInfoBuilder.mSplit.getPath()); + if (!splitDexFile.getParent().equals(baseDexFile.getParent())) { + throw new IllegalStateException( + "Split APK and base APK are in different directories: " + + splitDexFile.getParent() + " != " + baseDexFile.getParent()); + } + dexInfoBuilder.mRelativeDexPath = splitDexFile.getName(); + if (isIsolatedSplitLoading && dexInfoBuilder.mSplit.isHasCode()) { + dexInfoBuilder.mClassLoaderName = dexInfoBuilder.mSplit.getClassLoaderName(); + + List<AndroidPackageSplit> dependencies = dexInfoBuilder.mSplit.getDependencies(); + if (!Utils.isEmpty(dependencies)) { + // We only care about the first dependency because it is the parent split. The + // rest are configuration splits, which we don't care. + AndroidPackageSplit dependency = dependencies.get(0); + for (var dexInfo : dexInfos) { + if (Objects.equals(dexInfo.mSplit, dependency)) { + dexInfoBuilder.mSplitDependency = dexInfo; + break; + } + } + + if (dexInfoBuilder.mSplitDependency == null) { + throw new IllegalStateException( + "Split dependency not found for " + splitDexFile); + } + } + } + } + + if (isIsolatedSplitLoading) { + computeClassLoaderContextsIsolated(dexInfos); + } else { + computeClassLoaderContexts(dexInfos); + } + + return dexInfos; + } + + /** + * Computes class loader context for an app that didn't request isolated split loading. Stores + * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}. + * + * In this case, all the splits will be loaded in the base apk class loader (in the order of + * their definition). + * + * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK is + * `CLN[base.apk, split_0.apk, ..., split_n-1.apk]{shared-libraries}`; where `CLN` is the + * class loader name for the base APK. + */ + private static void computeClassLoaderContexts(@NonNull List<PrimaryDexInfoBuilder> dexInfos) { + String baseClassLoaderName = dexInfos.get(0).mClassLoaderName; + String sharedLibrariesContext = dexInfos.get(0).mSharedLibrariesContext; + List<String> classpath = new ArrayList<>(); + for (PrimaryDexInfoBuilder dexInfo : dexInfos) { + if (dexInfo.mSplit.isHasCode()) { + dexInfo.mClassLoaderContext = encodeClassLoader(baseClassLoaderName, classpath, + null /* parentContext */, sharedLibrariesContext); + } + // Note that the splits with no code are not removed from the classpath computation. + // I.e., split_n might get the split_n-1 in its classpath dependency even if split_n-1 + // has no code. + // The splits with no code do not matter for the runtime which ignores APKs without code + // when doing the classpath checks. As such we could actually filter them but we don't + // do it in order to keep consistency with how the apps are loaded. + classpath.add(dexInfo.mRelativeDexPath); + } + } + + /** + * Computes class loader context for an app that requested for isolated split loading. Stores + * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}. + * + * In this case, each split will be loaded with a separate class loader, whose context is a + * chain formed from inter-split dependencies. + * + * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK that + * depends on the base APK is `CLN_n[];CLN[base.apk]{shared-libraries}`; the CLC for the n-th + * split APK that depends on the m-th split APK is + * `CLN_n[];CLN_m[split_m.apk];...;CLN[base.apk]{shared-libraries}`; where `CLN` is the base + * class loader name for the base APK, `CLN_i` is the class loader name for the i-th split APK, + * and `...` represents the ancestors along the dependency chain. + * + * Specially, if a split does not have any dependency, the CLC for it is `CLN_n[]`. + */ + private static void computeClassLoaderContextsIsolated( + @NonNull List<PrimaryDexInfoBuilder> dexInfos) { + for (PrimaryDexInfoBuilder dexInfo : dexInfos) { + if (dexInfo.mSplit.isHasCode()) { + dexInfo.mClassLoaderContext = encodeClassLoader(dexInfo.mClassLoaderName, + null /* classpath */, getParentContextRecursive(dexInfo), + dexInfo.mSharedLibrariesContext); + } + } + } + + /** + * Computes the parent class loader context, recursively. Caches results in {@link + * PrimaryDexInfoBuilder#mContextForChildren}. + */ + @Nullable + private static String getParentContextRecursive(@NonNull PrimaryDexInfoBuilder dexInfo) { + if (dexInfo.mSplitDependency == null) { + return null; + } + PrimaryDexInfoBuilder parent = dexInfo.mSplitDependency; + if (parent.mContextForChildren == null) { + parent.mContextForChildren = + encodeClassLoader(parent.mClassLoaderName, List.of(parent.mRelativeDexPath), + getParentContextRecursive(parent), parent.mSharedLibrariesContext); + } + return parent.mContextForChildren; + } + + /** + * Returns class loader context in the format of + * `CLN[classpath...]{share-libraries};parent-context`, where `CLN` is the class loader name. + */ + @NonNull + private static String encodeClassLoader(@Nullable String classLoaderName, + @Nullable List<String> classpath, @Nullable String parentContext, + @Nullable String sharedLibrariesContext) { + StringBuilder classLoaderContext = new StringBuilder(); + + classLoaderContext.append(encodeClassLoaderName(classLoaderName)); + + classLoaderContext.append( + "[" + (classpath != null ? String.join(":", classpath) : "") + "]"); + + if (!TextUtils.isEmpty(sharedLibrariesContext)) { + classLoaderContext.append(sharedLibrariesContext); + } + + if (!TextUtils.isEmpty(parentContext)) { + classLoaderContext.append(";" + parentContext); + } + + return classLoaderContext.toString(); + } + + @NonNull + private static String encodeClassLoaderName(@Nullable String classLoaderName) { + // `PathClassLoader` and `DexClassLoader` are grouped together because they have the same + // behavior. For null values we default to "PCL". This covers the case where a package does + // not specify any value for its class loader. + if (classLoaderName == null || PathClassLoader.class.getName().equals(classLoaderName) + || DexClassLoader.class.getName().equals(classLoaderName)) { + return "PCL"; + } else if (DelegateLastClassLoader.class.getName().equals(classLoaderName)) { + return "DLC"; + } else { + throw new IllegalStateException("Unsupported classLoaderName: " + classLoaderName); + } + } + + /** + * Returns shared libraries context in the format of + * `{PCL[library_1_dex_1.jar:library_1_dex_2.jar:...]{library_1-dependencies}#PCL[ + * library_1_dex_2.jar:library_2_dex_2.jar:...]{library_2-dependencies}#...}`. + */ + @Nullable + private static String encodeSharedLibraries(@Nullable List<SharedLibrary> sharedLibraries) { + if (Utils.isEmpty(sharedLibraries)) { + return null; + } + return sharedLibraries.stream() + .filter(library -> !library.isNative()) + .map(library + -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(), + null /* parentContext */, + encodeSharedLibraries(library.getDependencies()))) + .collect(Collectors.joining("#", "{", "}")); + } + + public static boolean isIsolatedSplitLoading(@NonNull AndroidPackage pkg) { + return pkg.isIsolatedSplitLoading() && pkg.getSplits().size() > 1; + } + + @NonNull + public static ProfilePath buildRefProfilePath( + @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) { + String profileName = getProfileName(dexInfo.splitName()); + return AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName); + } + + @NonNull + public static OutputProfile buildOutputProfile(@NonNull PackageState pkgState, + @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic) { + String profileName = getProfileName(dexInfo.splitName()); + return AidlUtils.buildOutputProfileForPrimary( + pkgState.getPackageName(), profileName, uid, gid, isPublic); + } + + @NonNull + public static List<ProfilePath> getCurProfiles(@NonNull UserManager userManager, + @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) { + List<ProfilePath> profiles = new ArrayList<>(); + for (UserHandle handle : userManager.getUserHandles(true /* excludeDying */)) { + int userId = handle.getIdentifier(); + PackageUserState userState = pkgState.getStateForUser(handle); + if (userState.isInstalled()) { + profiles.add(AidlUtils.buildProfilePathForPrimaryCur( + userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName()))); + } + } + return profiles; + } + + @NonNull + public static String getProfileName(@Nullable String splitName) { + return splitName == null ? PROFILE_PRIMARY : splitName + ".split"; + } + + @NonNull + public static List<ProfilePath> getExternalProfiles(@NonNull PrimaryDexInfo dexInfo) { + return List.of(AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath()), + AidlUtils.buildProfilePathForDm(dexInfo.dexPath())); + } + + /** Basic information about a primary dex file (either the base APK or a split APK). */ + @Immutable + public static class PrimaryDexInfo { + private final @NonNull AndroidPackageSplit mSplit; + + PrimaryDexInfo(@NonNull AndroidPackageSplit split) { + mSplit = split; + } + + /** The path to the dex file. */ + public @NonNull String dexPath() { + return mSplit.getPath(); + } + + /** True if the dex file has code. */ + public boolean hasCode() { + return mSplit.isHasCode(); + } + + /** The name of the split, or null for base APK. */ + public @Nullable String splitName() { + return mSplit.getName(); + } + } + + /** + * Detailed information about a primary dex file (either the base APK or a split APK). It + * contains the class loader context in addition to what is in {@link PrimaryDexInfo}, but + * producing it requires {@link PackageState}. + */ + @Immutable + public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo { + private final @Nullable String mClassLoaderContext; + + DetailedPrimaryDexInfo( + @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) { + super(split); + mClassLoaderContext = classLoaderContext; + } + + /** + * A string describing the structure of the class loader that the dex file is loaded with. + */ + public @Nullable String classLoaderContext() { + return mClassLoaderContext; + } + } + + private static class PrimaryDexInfoBuilder { + @NonNull AndroidPackageSplit mSplit; + @Nullable String mRelativeDexPath = null; + @Nullable String mClassLoaderContext = null; + @Nullable String mClassLoaderName = null; + @Nullable PrimaryDexInfoBuilder mSplitDependency = null; + /** The class loader context of the shared libraries. Only applicable for the base APK. */ + @Nullable String mSharedLibrariesContext = null; + /** The class loader context for children to use when this dex file is used as a parent. */ + @Nullable String mContextForChildren = null; + + PrimaryDexInfoBuilder(@NonNull AndroidPackageSplit split) { + mSplit = split; + } + + PrimaryDexInfo build() { + return new PrimaryDexInfo(mSplit); + } + + DetailedPrimaryDexInfo buildDetailed() { + return new DetailedPrimaryDexInfo(mSplit, mClassLoaderContext); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java new file mode 100644 index 0000000000..a5481d9451 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext; +import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo; +import static com.android.server.art.Utils.Abi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.pm.PackageStateModulesUtils; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.DexFile; + +import com.google.auto.value.AutoValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> { + private static final String TAG = ArtManagerLocal.TAG; + + private final int mSharedGid; + + public PrimaryDexopter(@NonNull Context context, @NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + this(new Injector(context), pkgState, pkg, params, cancellationSignal); + } + + @VisibleForTesting + public PrimaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + super(injector, pkgState, pkg, params, cancellationSignal); + + mSharedGid = UserHandle.getSharedAppGid(pkgState.getAppId()); + if (mSharedGid < 0) { + throw new IllegalStateException( + String.format("Unable to get shared gid for package '%s' (app ID: %d)", + pkgState.getPackageName(), pkgState.getAppId())); + } + } + + @Override + protected boolean isInDalvikCache() throws RemoteException { + return Utils.isInDalvikCache(mPkgState, mInjector.getArtd()); + } + + @Override + @NonNull + protected List<DetailedPrimaryDexInfo> getDexInfoList() { + return PrimaryDexUtils.getDetailedDexInfo(mPkgState, mPkg); + } + + @Override + protected boolean isDexoptable(@NonNull DetailedPrimaryDexInfo dexInfo) { + if (!dexInfo.hasCode()) { + return false; + } + if ((mParams.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) { + return Objects.equals(mParams.getSplitName(), dexInfo.splitName()); + } + return true; + } + + @Override + protected boolean needsToBeShared(@NonNull DetailedPrimaryDexInfo dexInfo) { + return isSharedLibrary() + || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps( + mPkgState.getPackageName(), dexInfo.dexPath()); + } + + @Override + protected boolean isDexFilePublic(@NonNull DetailedPrimaryDexInfo dexInfo) { + // The filesystem permission of a primary dex file always has the S_IROTH bit. In practice, + // the accessibility is enforced by Application Sandbox, not filesystem permission. + return true; + } + + @Override + @NonNull + protected List<ProfilePath> getExternalProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) { + return PrimaryDexUtils.getExternalProfiles(dexInfo); + } + + @Override + @NonNull + protected PermissionSettings getPermissionSettings( + @NonNull DetailedPrimaryDexInfo dexInfo, boolean canBePublic) { + // The files and directories should belong to the system so that Package Manager can manage + // them (e.g., move them around). + // We don't need the "read" bit for "others" on the directories because others only need to + // access the files in the directories, but they don't need to "ls" the directories. + FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID /* uid */, + Process.SYSTEM_UID /* gid */, false /* isOtherReadable */, + true /* isOtherExecutable */); + FsPermission fileFsPermission = AidlUtils.buildFsPermission( + Process.SYSTEM_UID /* uid */, mSharedGid /* gid */, canBePublic); + // For primary dex, we can use the default SELinux context. + SeContext seContext = null; + return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext); + } + + @Override + @NonNull + protected List<Abi> getAllAbis(@NonNull DetailedPrimaryDexInfo dexInfo) { + return Utils.getAllAbis(mPkgState); + } + + @Override + @NonNull + protected ProfilePath buildRefProfilePath(@NonNull DetailedPrimaryDexInfo dexInfo) { + return PrimaryDexUtils.buildRefProfilePath(mPkgState, dexInfo); + } + + @Override + protected boolean isAppImageAllowed(@NonNull DetailedPrimaryDexInfo dexInfo) { + // Only allow app image for the base APK because having multiple app images is not + // supported. + // Additionally, disable app images if the app requests for the splits to be loaded in + // isolation because app images are unsupported for multiple class loaders (b/72696798). + // TODO(jiakaiz): Investigate whether this is still the best choice today. + return dexInfo.splitName() == null && !PrimaryDexUtils.isIsolatedSplitLoading(mPkg); + } + + @Override + @NonNull + protected OutputProfile buildOutputProfile( + @NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) { + return PrimaryDexUtils.buildOutputProfile( + mPkgState, dexInfo, Process.SYSTEM_UID, mSharedGid, isPublic); + } + + @Override + @NonNull + protected List<ProfilePath> getCurProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) { + return PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), mPkgState, dexInfo); + } + + @Override + @Nullable + protected DexMetadataPath buildDmPath(@NonNull DetailedPrimaryDexInfo dexInfo) { + return AidlUtils.buildDexMetadataPath(dexInfo.dexPath()); + } + + private boolean isSharedLibrary() { + return PackageStateModulesUtils.isLoadableInOtherProcesses(mPkgState, true /* codeOnly */); + } +} diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java new file mode 100644 index 0000000000..ac08856b67 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.model.ArtFlags.PriorityClassApi; + +import android.annotation.NonNull; +import android.annotation.StringDef; +import android.annotation.SystemApi; +import android.os.Build; +import android.os.SystemProperties; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import com.android.server.art.model.ArtFlags; +import com.android.server.pm.PackageManagerLocal; + +import dalvik.system.DexFile; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Maps a compilation reason to a compiler filter and a priority class. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class ReasonMapping { + private ReasonMapping() {} + + // Keep this in sync with `ArtShellCommand.printHelp` except for 'inactive'. + + /** Dexopting apps on the first boot after flashing or factory resetting the device. */ + public static final String REASON_FIRST_BOOT = "first-boot"; + /** Dexopting apps on the next boot after an OTA. */ + public static final String REASON_BOOT_AFTER_OTA = "boot-after-ota"; + /** Dexopting apps on the next boot after a mainline update. */ + public static final String REASON_BOOT_AFTER_MAINLINE_UPDATE = "boot-after-mainline-update"; + /** Installing an app after user presses the "install"/"update" button. */ + public static final String REASON_INSTALL = "install"; + /** Dexopting apps in the background. */ + public static final String REASON_BG_DEXOPT = "bg-dexopt"; + /** Invoked by cmdline. */ + public static final String REASON_CMDLINE = "cmdline"; + /** Downgrading the compiler filter when an app is not used for a long time. */ + public static final String REASON_INACTIVE = "inactive"; + + // Reasons for Play Install Hints (go/install-hints). + public static final String REASON_INSTALL_FAST = "install-fast"; + public static final String REASON_INSTALL_BULK = "install-bulk"; + public static final String REASON_INSTALL_BULK_SECONDARY = "install-bulk-secondary"; + public static final String REASON_INSTALL_BULK_DOWNGRADED = "install-bulk-downgraded"; + public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = + "install-bulk-secondary-downgraded"; + + /** @hide */ + public static final Set<String> REASONS_FOR_INSTALL = Set.of(REASON_INSTALL, + REASON_INSTALL_FAST, REASON_INSTALL_BULK, REASON_INSTALL_BULK_SECONDARY, + REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED); + + // Keep this in sync with `ArtShellCommand.printHelp`. + /** @hide */ + public static final Set<String> BATCH_DEXOPT_REASONS = Set.of(REASON_FIRST_BOOT, + REASON_BOOT_AFTER_OTA, REASON_BOOT_AFTER_MAINLINE_UPDATE, REASON_BG_DEXOPT); + + /** + * Reasons for {@link ArtManagerLocal#dexoptPackages}. + * + * @hide + */ + // clang-format off + @StringDef(prefix = "REASON_", value = { + REASON_FIRST_BOOT, + REASON_BOOT_AFTER_OTA, + REASON_BOOT_AFTER_MAINLINE_UPDATE, + REASON_BG_DEXOPT, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface BatchDexoptReason {} + + /** + * Reasons for {@link ArtManagerLocal#onBoot(String, Executor, Consumer<OperationProgress>)}. + * + * @hide + */ + // clang-format off + @StringDef(prefix = "REASON_", value = { + REASON_FIRST_BOOT, + REASON_BOOT_AFTER_OTA, + REASON_BOOT_AFTER_MAINLINE_UPDATE, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface BootReason {} + + /** + * Loads the compiler filter from the system property for the given reason and checks for + * validity. + * + * @throws IllegalArgumentException if the reason is invalid + * @throws IllegalStateException if the system property value is invalid + * + * @hide + */ + @NonNull + public static String getCompilerFilterForReason(@NonNull String reason) { + String value = SystemProperties.get("pm.dexopt." + reason); + if (TextUtils.isEmpty(value)) { + throw new IllegalArgumentException("No compiler filter for reason '" + reason + "'"); + } + if (!Utils.isValidArtServiceCompilerFilter(value)) { + throw new IllegalStateException( + "Got invalid compiler filter '" + value + "' for reason '" + reason + "'"); + } + return value; + } + + /** + * Loads the compiler filter from the system property for: + * - shared libraries + * - apps used by other apps without a dex metadata file + * + * @throws IllegalStateException if the system property value is invalid + * + * @hide + */ + @NonNull + public static String getCompilerFilterForShared() { + // "shared" is technically not a compilation reason, but the compiler filter is defined as a + // system property as if "shared" is a reason. + String value = getCompilerFilterForReason("shared"); + if (DexFile.isProfileGuidedCompilerFilter(value)) { + throw new IllegalStateException( + "Compiler filter for 'shared' must not be profile guided, got '" + value + "'"); + } + return value; + } + + /** + * Returns the priority for the given reason. + * + * @throws IllegalArgumentException if the reason is invalid + * @see PriorityClassApi + * + * @hide + */ + public static @PriorityClassApi byte getPriorityClassForReason(@NonNull String reason) { + switch (reason) { + case REASON_FIRST_BOOT: + case REASON_BOOT_AFTER_OTA: + case REASON_BOOT_AFTER_MAINLINE_UPDATE: + return ArtFlags.PRIORITY_BOOT; + case REASON_INSTALL_FAST: + return ArtFlags.PRIORITY_INTERACTIVE_FAST; + case REASON_INSTALL: + case REASON_CMDLINE: + return ArtFlags.PRIORITY_INTERACTIVE; + case REASON_BG_DEXOPT: + case REASON_INACTIVE: + case REASON_INSTALL_BULK: + case REASON_INSTALL_BULK_SECONDARY: + case REASON_INSTALL_BULK_DOWNGRADED: + case REASON_INSTALL_BULK_SECONDARY_DOWNGRADED: + return ArtFlags.PRIORITY_BACKGROUND; + default: + throw new IllegalArgumentException("No priority class for reason '" + reason + "'"); + } + } + + /** + * Loads the concurrency from the system property, for batch dexopt ({@link + * ArtManagerLocal#dexoptPackages}), or 1 if the system property is not found or cannot be + * parsed. + * + * @hide + */ + public static int getConcurrencyForReason(@NonNull @BatchDexoptReason String reason) { + return SystemProperties.getInt("pm.dexopt." + reason + ".concurrency", 1 /* def */); + } +} diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java new file mode 100644 index 0000000000..c8c63db76c --- /dev/null +++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext; +import static com.android.server.art.Utils.Abi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Build; +import android.os.CancellationSignal; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.art.model.DexoptParams; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import java.util.List; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class SecondaryDexopter extends Dexopter<DetailedSecondaryDexInfo> { + private static final String TAG = ArtManagerLocal.TAG; + + public SecondaryDexopter(@NonNull Context context, @NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + this(new Injector(context), pkgState, pkg, params, cancellationSignal); + } + + @VisibleForTesting + public SecondaryDexopter(@NonNull Injector injector, @NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, @NonNull DexoptParams params, + @NonNull CancellationSignal cancellationSignal) { + super(injector, pkgState, pkg, params, cancellationSignal); + } + + @Override + protected boolean isInDalvikCache() { + // A secondary dex file is added by the app, so it's always in a writable location and hence + // never uses dalvik-cache. + return false; + } + + @Override + @NonNull + protected List<DetailedSecondaryDexInfo> getDexInfoList() { + return mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo( + mPkgState.getPackageName()); + } + + @Override + protected boolean isDexoptable(@NonNull DetailedSecondaryDexInfo dexInfo) { + return true; + } + + @Override + protected boolean needsToBeShared(@NonNull DetailedSecondaryDexInfo dexInfo) { + return dexInfo.isUsedByOtherApps(); + } + + @Override + protected boolean isDexFilePublic(@NonNull DetailedSecondaryDexInfo dexInfo) { + return dexInfo.isDexFilePublic(); + } + + @Override + @NonNull + protected List<ProfilePath> getExternalProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) { + // A secondary dex file doesn't have any external profile to use. + return List.of(); + } + + @Override + @NonNull + protected PermissionSettings getPermissionSettings( + @NonNull DetailedSecondaryDexInfo dexInfo, boolean canBePublic) { + int uid = getUid(dexInfo); + // We need the "execute" bit for "others" even though `canBePublic` is false because the + // directory can contain other artifacts that needs to be public. + // We don't need the "read" bit for "others" on the directories because others only need to + // access the files in the directories, but they don't need to "ls" the directories. + FsPermission dirFsPermission = AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */, + false /* isOtherReadable */, true /* isOtherExecutable */); + FsPermission fileFsPermission = + AidlUtils.buildFsPermission(uid /* uid */, uid /* gid */, canBePublic); + SeContext seContext = AidlUtils.buildSeContext(mPkgState.getSeInfo(), uid); + return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext); + } + + @Override + @NonNull + protected List<Abi> getAllAbis(@NonNull DetailedSecondaryDexInfo dexInfo) { + return Utils.getAllAbisForNames(dexInfo.abiNames(), mPkgState); + } + + @Override + @NonNull + protected ProfilePath buildRefProfilePath(@NonNull DetailedSecondaryDexInfo dexInfo) { + return AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath()); + } + + @Override + protected boolean isAppImageAllowed(@NonNull DetailedSecondaryDexInfo dexInfo) { + // The runtime can only load the app image of the base APK. + return false; + } + + @Override + @NonNull + protected OutputProfile buildOutputProfile( + @NonNull DetailedSecondaryDexInfo dexInfo, boolean isPublic) { + int uid = getUid(dexInfo); + return AidlUtils.buildOutputProfileForSecondary(dexInfo.dexPath(), uid, uid, isPublic); + } + + @Override + @NonNull + protected List<ProfilePath> getCurProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) { + // A secondary dex file can only be loaded by one user, so there is only one profile. + return List.of(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); + } + + @Override + @Nullable + protected DexMetadataPath buildDmPath(@NonNull DetailedSecondaryDexInfo dexInfo) { + return null; + } + + private int getUid(@NonNull DetailedSecondaryDexInfo dexInfo) { + return dexInfo.userHandle().getUid(mPkgState.getAppId()); + } +} diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java new file mode 100644 index 0000000000..fc94d64f66 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/Utils.java @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.ProfilePath.TmpProfilePath; + +import android.R; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.role.RoleManager; +import android.apphibernation.AppHibernationManager; +import android.content.Context; +import android.os.Build; +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; + +import androidx.annotation.RequiresApi; + +import com.android.modules.utils.pm.PackageStateModulesUtils; +import com.android.server.art.model.DexoptParams; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.DexFile; +import dalvik.system.VMRuntime; + +import com.google.auto.value.AutoValue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +/** @hide */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public final class Utils { + public static final String TAG = ArtManagerLocal.TAG; + public static final String PLATFORM_PACKAGE_NAME = "android"; + + /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */ + private static final long TRACE_TAG_DALVIK = 1L << 14; + + private Utils() {} + + /** + * Checks if given array is null or has zero elements. + */ + public static <T> boolean isEmpty(@Nullable Collection<T> array) { + return array == null || array.isEmpty(); + } + + /** + * Checks if given array is null or has zero elements. + */ + public static <T> boolean isEmpty(@Nullable SparseArray<T> array) { + return array == null || array.size() == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** Returns the ABI information for the package. The primary ABI comes first. */ + @NonNull + public static List<Abi> getAllAbis(@NonNull PackageState pkgState) { + List<Abi> abis = new ArrayList<>(); + abis.add(getPrimaryAbi(pkgState)); + String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); + String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); + if (pkgSecondaryCpuAbi != null) { + Utils.check(pkgState.getPrimaryCpuAbi() != null); + String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi)); + abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */)); + } + // Primary and secondary ABIs should be guaranteed to have different ISAs. + if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) { + throw new IllegalStateException(String.format( + "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')", + pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name())); + } + return abis; + } + + /** + * Returns the ABI information for the ABIs with the given names. The primary ABI comes first, + * if given. + */ + @NonNull + public static List<Abi> getAllAbisForNames( + @NonNull Set<String> abiNames, @NonNull PackageState pkgState) { + Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi)); + Abi pkgPrimaryAbi = getPrimaryAbi(pkgState); + return abiNames.stream() + .map(name + -> Abi.create(name, VMRuntime.getInstructionSet(name), + name.equals(pkgPrimaryAbi.name()))) + .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed()) + .collect(Collectors.toList()); + } + + @NonNull + public static Abi getPrimaryAbi(@NonNull PackageState pkgState) { + String primaryCpuAbi = pkgState.getPrimaryCpuAbi(); + if (primaryCpuAbi != null) { + String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi)); + return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */); + } + // This is the most common case. The package manager can't infer the ABIs, probably because + // the package doesn't contain any native library. The app is launched with the device's + // preferred ABI. + String preferredAbi = Constants.getPreferredAbi(); + Utils.check(isNativeAbi(preferredAbi)); + return Abi.create( + preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */); + } + + /** + * If the given ISA isn't native to the device, returns the ISA that the native bridge + * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually + * launched with and therefore the ISA that should be used to compile the app. + */ + @NonNull + private static String getTranslatedIsa(@NonNull String isa) { + String abi64 = Constants.getNative64BitAbi(); + String abi32 = Constants.getNative32BitAbi(); + if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) + || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) { + return isa; + } + String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa); + if (TextUtils.isEmpty(translatedIsa)) { + throw new IllegalStateException(String.format("Unsupported isa '%s'", isa)); + } + return translatedIsa; + } + + @NonNull + private static String nativeIsaToAbi(@NonNull String isa) { + String abi64 = Constants.getNative64BitAbi(); + if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) { + return abi64; + } + String abi32 = Constants.getNative32BitAbi(); + if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) { + return abi32; + } + throw new IllegalStateException(String.format("Non-native isa '%s'", isa)); + } + + private static boolean isNativeAbi(@NonNull String abiName) { + return abiName.equals(Constants.getNative64BitAbi()) + || abiName.equals(Constants.getNative32BitAbi()); + } + + /** + * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache + * directory. + * + * This method is not needed for secondary dex files because they are always in writable + * locations. + */ + @NonNull + public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd) + throws RemoteException { + // The artifacts should be in the global dalvik-cache directory if: + // (1). the package is on a system partition, even if the partition is remounted read-write, + // or + // (2). the package is in any other readonly location. (At the time of writing, this only + // include Incremental FS.) + // + // Right now, we are using some heuristics to determine this. For (1), we can potentially + // use "libfstab" instead as a general solution, but for (2), unfortunately, we have to + // stick with heuristics. + // + // We cannot rely on access(2) because: + // - It doesn't take effective capabilities into account, from which artd gets root access + // to the filesystem. + // - The `faccessat` variant with the `AT_EACCESS` flag, which takes effective capabilities + // into account, is not supported by bionic. + // + // We cannot rely on `f_flags` returned by statfs(2) because: + // - Incremental FS is tagged as read-write while it's actually not. + return (pkgState.isSystem() && !pkgState.isUpdatedSystemApp()) + || artd.isIncrementalFsPath( + pkgState.getAndroidPackage().getSplits().get(0).getPath()); + } + + /** Returns true if the given string is a valid compiler filter. */ + public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) { + if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) { + return true; + } + return DexFile.isValidCompilerFilter(compilerFilter); + } + + @NonNull + public static IArtd getArtd() { + IArtd artd = IArtd.Stub.asInterface(ArtModuleServiceInitializer.getArtModuleServiceManager() + .getArtdServiceRegisterer() + .waitForService()); + if (artd == null) { + throw new IllegalStateException("Unable to connect to artd"); + } + return artd; + } + + public static boolean implies(boolean cond1, boolean cond2) { + return cond1 ? cond2 : true; + } + + public static void check(boolean cond) { + // This cannot be replaced with `assert` because `assert` is not enabled in Android. + if (!cond) { + throw new IllegalStateException("Check failed"); + } + } + + @NonNull + public static PackageState getPackageStateOrThrow( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { + PackageState pkgState = snapshot.getPackageState(packageName); + if (pkgState == null) { + throw new IllegalArgumentException("Package not found: " + packageName); + } + return pkgState; + } + + @NonNull + public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) { + AndroidPackage pkg = pkgState.getAndroidPackage(); + if (pkg == null) { + throw new IllegalArgumentException( + "Unable to get package " + pkgState.getPackageName()); + } + return pkg; + } + + @NonNull + public static String assertNonEmpty(@Nullable String str) { + if (TextUtils.isEmpty(str)) { + throw new IllegalArgumentException(); + } + return str; + } + + public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) { + getFuture(CompletableFuture.runAsync(runnable, executor)); + } + + public static <T> T getFuture(Future<T> future) { + try { + return future.get(); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new RuntimeException(cause); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns true if the given package is dexoptable. + * + * @param appHibernationManager the {@link AppHibernationManager} instance for checking + * hibernation status, or null to skip the check + */ + public static boolean canDexoptPackage( + @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) { + if (!PackageStateModulesUtils.isDexoptable(pkgState)) { + return false; + } + + // We do not dexopt unused packages. + // If `appHibernationManager` is null, the caller's intention is to skip the check. + if (appHibernationManager != null + && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) { + return false; + } + + return true; + } + + public static boolean shouldSkipDexoptDueToHibernation( + @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) { + return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName()) + && appHibernationManager.isOatArtifactDeletionEnabled(); + } + + public static long getPackageLastActiveTime(@NonNull PackageState pkgState, + @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) { + long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName()); + // The time where the last user installed the package the first time. + long lastFirstInstallTimeMs = + userManager.getUserHandles(true /* excludeDying */) + .stream() + .map(handle -> pkgState.getStateForUser(handle)) + .map(userState -> userState.getFirstInstallTimeMillis()) + .max(Long::compare) + .orElse(0l); + return Math.max(lastUsedAtMs, lastFirstInstallTimeMs); + } + + public static void deleteIfExistsSafe(@Nullable File file) { + if (file != null) { + deleteIfExistsSafe(file.toPath()); + } + } + + public static void deleteIfExistsSafe(@NonNull Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + Log.e(TAG, "Failed to delete file '" + path + "'", e); + } + } + + public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) { + return packageName.equals(context.getString(R.string.config_systemUi)); + } + + public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) { + RoleManager roleManager = context.getSystemService(RoleManager.class); + return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName); + } + + /** + * Gets the existing reference profile if one exists, or initializes a reference profile from an + * external profile. + * + * If the reference profile is initialized from an external profile, the returned profile path + * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the + * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link + * IArtd#deleteProfile}. + * + * @param dexPath the path to the dex file that the profile is checked against + * @param refProfile the path where an existing reference profile would be found, if present + * @param externalProfiles a list of external profiles to initialize the reference profile from, + * in the order of preference + * @param initOutput the final location to initialize the reference profile to + * + * @return a pair where the first element is the found or initialized profile, and the second + * element is true if the profile is readable by others. Returns null if there is no + * reference profile or external profile to use + */ + @Nullable + public static Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull IArtd artd, + @NonNull String dexPath, @NonNull ProfilePath refProfile, + @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput) + throws RemoteException { + try { + if (artd.isProfileUsable(refProfile, dexPath)) { + boolean isOtherReadable = + artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE; + return Pair.create(refProfile, isOtherReadable); + } + } catch (ServiceSpecificException e) { + Log.e(TAG, + "Failed to use the existing reference profile " + + AidlUtils.toString(refProfile), + e); + } + + ProfilePath initializedProfile = + initReferenceProfile(artd, dexPath, externalProfiles, initOutput); + return initializedProfile != null ? Pair.create(initializedProfile, true) : null; + } + + /** + * Similar to above, but never uses an existing profile. + * + * Unlike the one above, this method doesn't return a boolean flag to indicate if the profile is + * readable by others. The profile returned by this method is initialized form an external + * profile, meaning it has no user data, so it's always readable by others. + */ + @Nullable + public static ProfilePath initReferenceProfile(@NonNull IArtd artd, @NonNull String dexPath, + @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output) + throws RemoteException { + for (ProfilePath profile : externalProfiles) { + try { + // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt + // one, rewriting the profile is unnecessary because the dex location is known at + // build time and is correctly set in the profile header. However, the APK can also + // be an installed one, in which case partners may place a profile file next to the + // APK at install time. Rewriting the profile in the latter case is necessary. + if (artd.copyAndRewriteProfile(profile, output, dexPath)) { + return ProfilePath.tmpProfilePath(output.profilePath); + } + } catch (ServiceSpecificException e) { + Log.e(TAG, "Failed to initialize profile from " + AidlUtils.toString(profile), e); + } + } + + return null; + } + + public static void logArtdException(@NonNull RemoteException e) { + String message = "An error occurred when calling artd"; + if (e instanceof DeadObjectException) { + // We assume that `DeadObjectException` only happens in two cases: + // 1. artd crashed, in which case a native stack trace was logged. + // 2. artd was killed before system server during device shutdown, in which case the + // exception is expected. + // In either case, we don't need to surface the exception from here. + // The Java stack trace is intentionally omitted because it's not helpful. + Log.e(TAG, message); + } else { + // Not expected. Log wtf to surface it. + Slog.wtf(TAG, message, e); + } + } + + @AutoValue + public abstract static class Abi { + static @NonNull Abi create( + @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) { + return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi); + } + + // The ABI name. E.g., "arm64-v8a". + abstract @NonNull String name(); + + // The instruction set name. E.g., "arm64". + abstract @NonNull String isa(); + + abstract boolean isPrimaryAbi(); + } + + public static class Tracing implements AutoCloseable { + public Tracing(@NonNull String methodName) { + Trace.traceBegin(TRACE_TAG_DALVIK, methodName); + } + + @Override + public void close() { + Trace.traceEnd(TRACE_TAG_DALVIK); + } + } + + public static class TracingWithTimingLogging extends Tracing { + @NonNull private final String mTag; + @NonNull private final String mMethodName; + @NonNull private final long mStartTimeMs; + + public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) { + super(methodName); + mTag = tag; + mMethodName = methodName; + mStartTimeMs = SystemClock.elapsedRealtime(); + Log.d(tag, methodName); + } + + @Override + public void close() { + Log.d(mTag, + mMethodName + " took to complete: " + + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms"); + super.close(); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/model/ArtFlags.java b/libartservice/service/java/com/android/server/art/model/ArtFlags.java new file mode 100644 index 0000000000..cc4f826556 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.job.JobScheduler; + +import com.android.server.art.ArtManagerLocal; +import com.android.server.art.PriorityClass; +import com.android.server.art.ReasonMapping; +import com.android.server.pm.PackageManagerLocal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +public class ArtFlags { + // Common flags. + + /** + * Whether the operation is applied for primary dex'es (all APKs that are installed as part of + * the package, including the base APK and all other split APKs). + */ + public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0; + /** + * Whether the operation is applied for secondary dex'es (APKs/JARs that the app puts in its + * own data directory at runtime and loads with custom classloaders). + */ + public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1; + + // Flags specific to `dexoptPackage`. + + /** + * Whether to dexopt dependency libraries as well (dependencies that are declared by the app + * with <uses-library> tags and transitive dependencies). + */ + public static final int FLAG_SHOULD_INCLUDE_DEPENDENCIES = 1 << 2; + /** + * Whether the intention is to downgrade the compiler filter. If true, the dexopt will + * be skipped if the target compiler filter is better than or equal to the compiler filter + * of the existing dexopt artifacts, or dexopt artifacts do not exist. + */ + public static final int FLAG_SHOULD_DOWNGRADE = 1 << 3; + /** + * Whether to force dexopt. If true, the dexopt will be performed regardless of + * any existing dexopt artifacts. + */ + public static final int FLAG_FORCE = 1 << 4; + /** + * If set, the dexopt will be performed for a single split. Otherwise, the dexopt + * will be performed for all splits. {@link DexoptParams.Builder#setSplitName()} can be used + * to specify the split to dexopt. + * + * When this flag is set, {@link #FLAG_FOR_PRIMARY_DEX} must be set, and {@link + * #FLAG_FOR_SECONDARY_DEX} and {@link #FLAG_SHOULD_INCLUDE_DEPENDENCIES} must not be set. + */ + public static final int FLAG_FOR_SINGLE_SPLIT = 1 << 5; + /** + * If set, skips the dexopt if the remaining storage space is low. The threshold is + * controlled by the global settings {@code sys_storage_threshold_percentage} and {@code + * sys_storage_threshold_max_bytes}. + */ + public static final int FLAG_SKIP_IF_STORAGE_LOW = 1 << 6; + + /** + * Flags for {@link + * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String, int)}. + * + * @hide + */ + // clang-format off + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_FOR_PRIMARY_DEX, + FLAG_FOR_SECONDARY_DEX, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface GetStatusFlags {} + + /** + * Default flags that are used when {@link + * ArtManagerLocal#getDexoptStatus(PackageManagerLocal.FilteredSnapshot, String)} is + * called. + * Value: {@link #FLAG_FOR_PRIMARY_DEX}, {@link #FLAG_FOR_SECONDARY_DEX}. + */ + public static @GetStatusFlags int defaultGetStatusFlags() { + return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX; + } + + /** + * Flags for {@link DexoptParams}. + * + * @hide + */ + // clang-format off + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_FOR_PRIMARY_DEX, + FLAG_FOR_SECONDARY_DEX, + FLAG_SHOULD_INCLUDE_DEPENDENCIES, + FLAG_SHOULD_DOWNGRADE, + FLAG_FORCE, + FLAG_FOR_SINGLE_SPLIT, + FLAG_SKIP_IF_STORAGE_LOW, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface DexoptFlags {} + + /** + * Default flags that are used when + * {@link DexoptParams.Builder#Builder(String)} is called. + * + * @hide + */ + public static @DexoptFlags int defaultDexoptFlags(@NonNull String reason) { + switch (reason) { + case ReasonMapping.REASON_INSTALL: + case ReasonMapping.REASON_INSTALL_FAST: + case ReasonMapping.REASON_INSTALL_BULK: + case ReasonMapping.REASON_INSTALL_BULK_SECONDARY: + case ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED: + case ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED: + return FLAG_FOR_PRIMARY_DEX; + case ReasonMapping.REASON_INACTIVE: + return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX | FLAG_SHOULD_DOWNGRADE; + case ReasonMapping.REASON_FIRST_BOOT: + case ReasonMapping.REASON_BOOT_AFTER_OTA: + case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE: + return FLAG_FOR_PRIMARY_DEX | FLAG_SHOULD_INCLUDE_DEPENDENCIES; + case ReasonMapping.REASON_BG_DEXOPT: + return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX + | FLAG_SHOULD_INCLUDE_DEPENDENCIES | FLAG_SKIP_IF_STORAGE_LOW; + case ReasonMapping.REASON_CMDLINE: + default: + return FLAG_FOR_PRIMARY_DEX | FLAG_FOR_SECONDARY_DEX + | FLAG_SHOULD_INCLUDE_DEPENDENCIES; + } + } + + // Keep in sync with `PriorityClass` except for `PRIORITY_NONE`. + // Keep this in sync with `ArtShellCommand.printHelp` except for 'PRIORITY_NONE'. + + /** + * Initial value. Not expected. + * + * @hide + */ + public static final int PRIORITY_NONE = -1; + /** Indicates that the operation blocks boot. */ + public static final int PRIORITY_BOOT = PriorityClass.BOOT; + /** + * Indicates that a human is waiting on the result and the operation is more latency sensitive + * than usual. + */ + public static final int PRIORITY_INTERACTIVE_FAST = PriorityClass.INTERACTIVE_FAST; + /** Indicates that a human is waiting on the result. */ + public static final int PRIORITY_INTERACTIVE = PriorityClass.INTERACTIVE; + /** Indicates that the operation runs in background. */ + public static final int PRIORITY_BACKGROUND = PriorityClass.BACKGROUND; + + /** + * Indicates the priority of an operation. The value affects the resource usage and the process + * priority. A higher value may result in faster execution but may consume more resources and + * compete for resources with other processes. + * + * @hide + */ + // clang-format off + @IntDef(prefix = "PRIORITY_", value = { + PRIORITY_NONE, + PRIORITY_BOOT, + PRIORITY_INTERACTIVE_FAST, + PRIORITY_INTERACTIVE, + PRIORITY_BACKGROUND, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface PriorityClassApi {} + + /** The job has been successfully scheduled. */ + public static final int SCHEDULE_SUCCESS = 0; + + /** @see JobScheduler#RESULT_FAILURE */ + public static final int SCHEDULE_JOB_SCHEDULER_FAILURE = 1; + + /** The job is disabled by the system property {@code pm.dexopt.disable_bg_dexopt}. */ + public static final int SCHEDULE_DISABLED_BY_SYSPROP = 2; + + /** + * Indicates the result of scheduling a background dexopt job. + * + * @hide + */ + // clang-format off + @IntDef(prefix = "SCHEDULE_", value = { + SCHEDULE_SUCCESS, + SCHEDULE_JOB_SCHEDULER_FAILURE, + SCHEDULE_DISABLED_BY_SYSPROP, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface ScheduleStatus {} + + private ArtFlags() {} +} diff --git a/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java new file mode 100644 index 0000000000..ffe5500b81 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/BatchDexoptParams.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class BatchDexoptParams { + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + public static final class Builder { + private @NonNull List<String> mPackageNames; // This is assumed immutable. + private @NonNull DexoptParams mDexoptParams; + + /** @hide */ + public Builder( + @NonNull List<String> defaultPackages, @NonNull DexoptParams defaultDexoptParams) { + mPackageNames = defaultPackages; // The argument is assumed immutable. + mDexoptParams = defaultDexoptParams; + } + + /** + * Sets the list of packages to dexopt. The dexopt will be scheduled in the given + * order. + * + * If not called, the default list will be used. + */ + @NonNull + public Builder setPackages(@NonNull List<String> packageNames) { + mPackageNames = Collections.unmodifiableList(new ArrayList<>(packageNames)); + return this; + } + + /** + * Sets the params for dexopting each package. + * + * If not called, the default params built from {@link DexoptParams#Builder(String)} will + * be used. + */ + @NonNull + public Builder setDexoptParams(@NonNull DexoptParams dexoptParams) { + mDexoptParams = dexoptParams; + return this; + } + + /** Returns the built object. */ + @NonNull + public BatchDexoptParams build() { + return new AutoValue_BatchDexoptParams(mPackageNames, mDexoptParams); + } + } + + /** @hide */ + protected BatchDexoptParams() {} + + /** The ordered list of packages to dexopt. */ + public abstract @NonNull List<String> getPackages(); + + /** The params for dexopting each package. */ + public abstract @NonNull DexoptParams getDexoptParams(); +} diff --git a/libartservice/service/java/com/android/server/art/model/Config.java b/libartservice/service/java/com/android/server/art/model/Config.java new file mode 100644 index 0000000000..49bc9308da --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/Config.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import static com.android.server.art.ArtManagerLocal.BatchDexoptStartCallback; +import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback; +import static com.android.server.art.ArtManagerLocal.ScheduleBackgroundDexoptJobCallback; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.art.ArtManagerLocal; + +import com.google.auto.value.AutoValue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A class that stores the configurations set by the consumer of ART Service at runtime. This class + * is thread-safe. + * + * @hide + */ +public class Config { + /** @see ArtManagerLocal#setBatchDexoptStartCallback(Executor, BatchDexoptStartCallback) */ + @GuardedBy("this") + @Nullable + private Callback<BatchDexoptStartCallback, Void> mBatchDexoptStartCallback = null; + + /** + * @see ArtManagerLocal#setScheduleBackgroundDexoptJobCallback(Executor, + * ScheduleBackgroundDexoptJobCallback) + */ + @GuardedBy("this") + @Nullable + private Callback<ScheduleBackgroundDexoptJobCallback, Void> + mScheduleBackgroundDexoptJobCallback = null; + + /** + * @see ArtManagerLocal#addDexoptDoneCallback(Executor, DexoptDoneCallback) + */ + @GuardedBy("this") + @NonNull + private LinkedHashMap<DexoptDoneCallback, Callback<DexoptDoneCallback, Boolean>> + mDexoptDoneCallbacks = new LinkedHashMap<>(); + + public synchronized void setBatchDexoptStartCallback( + @NonNull Executor executor, @NonNull BatchDexoptStartCallback callback) { + mBatchDexoptStartCallback = Callback.create(callback, executor); + } + + public synchronized void clearBatchDexoptStartCallback() { + mBatchDexoptStartCallback = null; + } + + @Nullable + public synchronized Callback<BatchDexoptStartCallback, Void> getBatchDexoptStartCallback() { + return mBatchDexoptStartCallback; + } + + public synchronized void setScheduleBackgroundDexoptJobCallback( + @NonNull Executor executor, @NonNull ScheduleBackgroundDexoptJobCallback callback) { + mScheduleBackgroundDexoptJobCallback = Callback.create(callback, executor); + } + + public synchronized void clearScheduleBackgroundDexoptJobCallback() { + mScheduleBackgroundDexoptJobCallback = null; + } + + @Nullable + public synchronized Callback<ScheduleBackgroundDexoptJobCallback, Void> + getScheduleBackgroundDexoptJobCallback() { + return mScheduleBackgroundDexoptJobCallback; + } + + public synchronized void addDexoptDoneCallback(boolean onlyIncludeUpdates, + @NonNull Executor executor, @NonNull DexoptDoneCallback callback) { + if (mDexoptDoneCallbacks.putIfAbsent( + callback, Callback.create(callback, executor, onlyIncludeUpdates)) + != null) { + throw new IllegalStateException("callback already added"); + } + } + + public synchronized void removeDexoptDoneCallback(@NonNull DexoptDoneCallback callback) { + mDexoptDoneCallbacks.remove(callback); + } + + @NonNull + public synchronized List<Callback<DexoptDoneCallback, Boolean>> getDexoptDoneCallbacks() { + return new ArrayList<>(mDexoptDoneCallbacks.values()); + } + + @AutoValue + public static abstract class Callback<CallbackType, ExtraType> { + public abstract @NonNull CallbackType get(); + public abstract @NonNull Executor executor(); + public abstract @Nullable ExtraType extra(); + static <CallbackType, ExtraType> @NonNull Callback<CallbackType, ExtraType> create( + @NonNull CallbackType callback, @NonNull Executor executor, + @Nullable ExtraType extra) { + return new AutoValue_Config_Callback<CallbackType, ExtraType>( + callback, executor, extra); + } + static <CallbackType> @NonNull Callback<CallbackType, Void> create( + @NonNull CallbackType callback, @NonNull Executor executor) { + return new AutoValue_Config_Callback<CallbackType, Void>( + callback, executor, null /* extra */); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/model/DeleteResult.java b/libartservice/service/java/com/android/server/art/model/DeleteResult.java new file mode 100644 index 0000000000..e8e1520d99 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DeleteResult.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class DeleteResult { + /** @hide */ + protected DeleteResult() {} + + /** @hide */ + public static @NonNull DeleteResult create(long freedBytes) { + return new AutoValue_DeleteResult(freedBytes); + } + + /** The amount of the disk space freed by the deletion, in bytes. */ + public abstract long getFreedBytes(); +} diff --git a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java new file mode 100644 index 0000000000..932813f4f4 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.Immutable; + +/** + * Detailed information about a dex file. + * + * @hide + */ +@Immutable +public interface DetailedDexInfo { + /** The path to the dex file. */ + @NonNull String dexPath(); + + /** + * A string describing the structure of the class loader that the dex file is loaded with, or + * null if the class loader context is invalid. + */ + @Nullable String classLoaderContext(); +} diff --git a/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java new file mode 100644 index 0000000000..1f4c0131ce --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DexContainerFileUseInfo.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.UserHandle; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +import java.util.List; +import java.util.Set; + +/** + * The information about the use of a dex container file. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class DexContainerFileUseInfo { + /** @hide */ + protected DexContainerFileUseInfo() {} + + /** @hide */ + public static @NonNull DexContainerFileUseInfo create(@NonNull String dexContainerFile, + @NonNull UserHandle userHandle, @NonNull Set<String> loadingPackages) { + return new AutoValue_DexContainerFileUseInfo(dexContainerFile, userHandle, loadingPackages); + } + + /** The absolute path to the dex container file. */ + public abstract @NonNull String getDexContainerFile(); + + /** The {@link UserHandle} that represents the human user who loads the dex file. */ + public abstract @NonNull UserHandle getUserHandle(); + + /** The names of packages that load the dex file. Guaranteed to be non-empty. */ + public abstract @NonNull Set<String> getLoadingPackages(); +} diff --git a/libartservice/service/java/com/android/server/art/model/DexoptParams.java b/libartservice/service/java/com/android/server/art/model/DexoptParams.java new file mode 100644 index 0000000000..cf86ad6904 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DexoptParams.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import static com.android.server.art.model.ArtFlags.DexoptFlags; +import static com.android.server.art.model.ArtFlags.PriorityClassApi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.Immutable; +import com.android.server.art.ArtConstants; +import com.android.server.art.ReasonMapping; +import com.android.server.art.Utils; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +@Immutable +public class DexoptParams { + /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + public static final class Builder { + private DexoptParams mParams = new DexoptParams(); + + /** + * Creates a builder. + * + * Uses default flags ({@link ArtFlags#defaultDexoptFlags()}). + * + * @param reason Compilation reason. Can be a string defined in {@link ReasonMapping} or a + * custom string. If the value is a string defined in {@link ReasonMapping}, it + * determines the compiler filter and/or the priority class, if those values are not + * explicitly set. If the value is a custom string, the priority class and the + * compiler filter must be explicitly set. + */ + public Builder(@NonNull String reason) { + this(reason, ArtFlags.defaultDexoptFlags(reason)); + } + + /** + * Same as above, but allows to specify flags. + */ + public Builder(@NonNull String reason, @DexoptFlags int flags) { + mParams.mReason = reason; + setFlags(flags); + } + + /** Replaces all flags with the given value. */ + @NonNull + public Builder setFlags(@DexoptFlags int value) { + mParams.mFlags = value; + return this; + } + + /** Replaces the flags specified by the mask with the given value. */ + @NonNull + public Builder setFlags(@DexoptFlags int value, @DexoptFlags int mask) { + mParams.mFlags = (mParams.mFlags & ~mask) | (value & mask); + return this; + } + + /** + * The target compiler filter, passed as the {@code --compiler-filer} option to dex2oat. + * Supported values are listed in + * https://source.android.com/docs/core/dalvik/configure#compilation_options. + * + * Note that the compiler filter might be adjusted before the execution based on factors + * like whether the profile is available or whether the app is used by other apps. If not + * set, the default compiler filter for the given reason will be used. + */ + @NonNull + public Builder setCompilerFilter(@NonNull String value) { + mParams.mCompilerFilter = value; + return this; + } + + /** + * The priority of the operation. If not set, the default priority class for the given + * reason will be used. + * + * @see PriorityClassApi + */ + @NonNull + public Builder setPriorityClass(@PriorityClassApi int value) { + mParams.mPriorityClass = value; + return this; + } + + /** + * The name of the split to dexopt, or null for the base split. This option is only + * available when {@link ArtFlags#FLAG_FOR_SINGLE_SPLIT} is set. + */ + @NonNull + public Builder setSplitName(@Nullable String value) { + mParams.mSplitName = value; + return this; + } + + /** + * Returns the built object. + * + * @throws IllegalArgumentException if the built options would be invalid + */ + @NonNull + public DexoptParams build() { + if (mParams.mReason.isEmpty()) { + throw new IllegalArgumentException("Reason must not be empty"); + } + if (mParams.mReason.equals(ArtConstants.REASON_VDEX)) { + throw new IllegalArgumentException( + "Reason must not be '" + ArtConstants.REASON_VDEX + "'"); + } + + if (mParams.mCompilerFilter.isEmpty()) { + mParams.mCompilerFilter = ReasonMapping.getCompilerFilterForReason(mParams.mReason); + } else if (!Utils.isValidArtServiceCompilerFilter(mParams.mCompilerFilter)) { + throw new IllegalArgumentException( + "Invalid compiler filter '" + mParams.mCompilerFilter + "'"); + } + + if (mParams.mPriorityClass == ArtFlags.PRIORITY_NONE) { + mParams.mPriorityClass = ReasonMapping.getPriorityClassForReason(mParams.mReason); + } else if (mParams.mPriorityClass < 0 || mParams.mPriorityClass > 100) { + throw new IllegalArgumentException("Invalid priority class " + + mParams.mPriorityClass + ". Must be between 0 and 100"); + } + + if ((mParams.mFlags & (ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX)) + == 0) { + throw new IllegalArgumentException("Nothing to dexopt"); + } + + if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0 + && (mParams.mFlags & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) { + throw new IllegalArgumentException( + "FLAG_SHOULD_INCLUDE_DEPENDENCIES must not set if FLAG_FOR_PRIMARY_DEX is " + + "not set."); + } + + if ((mParams.mFlags & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) { + if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0) { + throw new IllegalArgumentException( + "FLAG_FOR_PRIMARY_DEX must be set when FLAG_FOR_SINGLE_SPLIT is set"); + } + if ((mParams.mFlags + & (ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)) + != 0) { + throw new IllegalArgumentException( + "FLAG_FOR_SECONDARY_DEX and FLAG_SHOULD_INCLUDE_DEPENDENCIES must " + + "not be set when FLAG_FOR_SINGLE_SPLIT is set"); + } + } else { + if (mParams.mSplitName != null) { + throw new IllegalArgumentException( + "Split name must not be set when FLAG_FOR_SINGLE_SPLIT is not set"); + } + } + + return mParams; + } + } + + /** + * A value indicating that dexopt shouldn't be run. This value is consumed by ART Services and + * is not propagated to dex2oat. + */ + public static final String COMPILER_FILTER_NOOP = "skip"; + + private @DexoptFlags int mFlags = 0; + private @NonNull String mCompilerFilter = ""; + private @PriorityClassApi int mPriorityClass = ArtFlags.PRIORITY_NONE; + private @NonNull String mReason = ""; + private @Nullable String mSplitName = null; + + private DexoptParams() {} + + /** Returns all flags. */ + public @DexoptFlags int getFlags() { + return mFlags; + } + + /** The target compiler filter. */ + public @NonNull String getCompilerFilter() { + return mCompilerFilter; + } + + /** The priority class. */ + public @PriorityClassApi int getPriorityClass() { + return mPriorityClass; + } + + /** + * The compilation reason. + * + * DO NOT directly use the string value to determine the resource usage and the process + * priority. Use {@link #getPriorityClass}. + */ + public @NonNull String getReason() { + return mReason; + } + + /** The name of the split to dexopt, or null for the base split. */ + public @Nullable String getSplitName() { + return mSplitName; + } +} diff --git a/libartservice/service/java/com/android/server/art/model/DexoptResult.java b/libartservice/service/java/com/android/server/art/model/DexoptResult.java new file mode 100644 index 0000000000..79f9b5f80a --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.DurationMillisLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class DexoptResult { + // Possible values of {@link #DexoptResultStatus}. + // A larger number means a higher priority. If multiple dex container files are processed, the + // final status will be the one with the highest priority. + /** Dexopt is skipped because there is no need to do it. */ + public static final int DEXOPT_SKIPPED = 10; + /** Dexopt is performed successfully. */ + public static final int DEXOPT_PERFORMED = 20; + /** Dexopt is failed. */ + public static final int DEXOPT_FAILED = 30; + /** Dexopt is cancelled. */ + public static final int DEXOPT_CANCELLED = 40; + + /** @hide */ + // clang-format off + @IntDef(prefix = {"DEXOPT_"}, value = { + DEXOPT_SKIPPED, + DEXOPT_FAILED, + DEXOPT_PERFORMED, + DEXOPT_CANCELLED, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface DexoptResultStatus {} + + /** @hide */ + protected DexoptResult() {} + + /** @hide */ + public static @NonNull DexoptResult create(@NonNull String requestedCompilerFilter, + @NonNull String reason, @NonNull List<PackageDexoptResult> packageDexoptResult) { + return new AutoValue_DexoptResult(requestedCompilerFilter, reason, packageDexoptResult); + } + + /** + * The requested compiler filter. Note that the compiler filter might be adjusted before the + * execution based on factors like whether the profile is available or whether the app is + * used by other apps. + * + * @see DexoptParams.Builder#setCompilerFilter(String) + * @see DexContainerFileDexoptResult#getActualCompilerFilter() + */ + public abstract @NonNull String getRequestedCompilerFilter(); + + /** The compilation reason. */ + public abstract @NonNull String getReason(); + + /** + * The result of each individual package. + * + * If the request is to dexopt a single package without dexopting dependencies, the only + * element is the result of the requested package. + * + * If the request is to dexopt a single package with {@link + * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} set, the first element is the result of the + * requested package, and the rest are the results of the dependency packages. + * + * If the request is to dexopt multiple packages, the list contains the results of all the + * requested packages. The results of their dependency packages are also included if {@link + * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} is set. + * + * If the request is a batch dexopt operation that got cancelled, the list still has an entry + * for every package that was requested to be optimized. + */ + public abstract @NonNull List<PackageDexoptResult> getPackageDexoptResults(); + + /** The final status. */ + public @DexoptResultStatus int getFinalStatus() { + return getPackageDexoptResults() + .stream() + .mapToInt(result -> result.getStatus()) + .max() + .orElse(DEXOPT_SKIPPED); + } + + /** @hide */ + @NonNull + public static String dexoptResultStatusToString(@DexoptResultStatus int status) { + switch (status) { + case DexoptResult.DEXOPT_SKIPPED: + return "SKIPPED"; + case DexoptResult.DEXOPT_PERFORMED: + return "PERFORMED"; + case DexoptResult.DEXOPT_FAILED: + return "FAILED"; + case DexoptResult.DEXOPT_CANCELLED: + return "CANCELLED"; + } + throw new IllegalArgumentException("Unknown dexopt status " + status); + } + + /** + * Describes the result of a package. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @Immutable + @AutoValue + public static abstract class PackageDexoptResult { + /** @hide */ + protected PackageDexoptResult() {} + + /** @hide */ + public static @NonNull PackageDexoptResult create(@NonNull String packageName, + @NonNull List<DexContainerFileDexoptResult> dexContainerFileDexoptResults, + @Nullable @DexoptResultStatus Integer packageLevelStatus) { + return new AutoValue_DexoptResult_PackageDexoptResult( + packageName, dexContainerFileDexoptResults, packageLevelStatus); + } + + /** The package name. */ + public abstract @NonNull String getPackageName(); + + /** + * The results of dexopting dex container files. Note that there can be multiple entries + * for the same dex container file, but for different ABIs. + */ + public abstract @NonNull List<DexContainerFileDexoptResult> + getDexContainerFileDexoptResults(); + + /** @hide */ + @Nullable @DexoptResultStatus public abstract Integer getPackageLevelStatus(); + + /** The overall status of the package. */ + public @DexoptResultStatus int getStatus() { + return getPackageLevelStatus() != null ? getPackageLevelStatus() + : getDexContainerFileDexoptResults() + .stream() + .mapToInt(result -> result.getStatus()) + .max() + .orElse(DEXOPT_SKIPPED); + } + + /** True if the package has any artifacts updated by this operation. */ + public boolean hasUpdatedArtifacts() { + return getDexContainerFileDexoptResults().stream().anyMatch( + result -> result.getStatus() == DEXOPT_PERFORMED); + } + } + + /** + * Describes the result of dexopting a dex container file. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @Immutable + @AutoValue + public static abstract class DexContainerFileDexoptResult { + /** @hide */ + protected DexContainerFileDexoptResult() {} + + /** @hide */ + public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile, + boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter, + @DexoptResultStatus int status, long dex2oatWallTimeMillis, + long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes, + boolean isSkippedDueToStorageLow) { + return new AutoValue_DexoptResult_DexContainerFileDexoptResult(dexContainerFile, + isPrimaryAbi, abi, compilerFilter, status, dex2oatWallTimeMillis, + dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow); + } + + /** The absolute path to the dex container file. */ + public abstract @NonNull String getDexContainerFile(); + + /** + * If true, the dexopt is for the primary ABI of the package (the ABI that the + * application is launched with). Otherwise, the dexopt is for an ABI that other + * applications might be launched with when using this application's code. + */ + public abstract boolean isPrimaryAbi(); + + /** + * Returns the ABI that the dexopt is for. Possible values are documented at + * https://developer.android.com/ndk/guides/abis#sa. + */ + public abstract @NonNull String getAbi(); + + /** + * The actual compiler filter. + * + * @see DexoptParams.Builder#setCompilerFilter(String) + */ + public abstract @NonNull String getActualCompilerFilter(); + + /** The status of dexopting this dex container file. */ + public abstract @DexoptResultStatus int getStatus(); + + /** + * The wall time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was + * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value. + */ + public abstract @DurationMillisLong long getDex2oatWallTimeMillis(); + + /** + * The CPU time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was + * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value. + */ + public abstract @DurationMillisLong long getDex2oatCpuTimeMillis(); + + /** + * The total size, in bytes, of the dexopt artifacts. Returns 0 if {@link #getStatus()} + * is not {@link #DEXOPT_PERFORMED}. + */ + public abstract long getSizeBytes(); + + /** + * The total size, in bytes, of the previous dexopt artifacts that has been replaced. + * Returns 0 if there were no previous dexopt artifacts or {@link #getStatus()} is not + * {@link #DEXOPT_PERFORMED}. + */ + public abstract long getSizeBeforeBytes(); + + /** @hide */ + public abstract boolean isSkippedDueToStorageLow(); + + @Override + @NonNull + public String toString() { + return String.format("DexContainerFileDexoptResult{" + + "dexContainerFile=%s, " + + "primaryAbi=%b, " + + "abi=%s, " + + "actualCompilerFilter=%s, " + + "status=%s, " + + "dex2oatWallTimeMillis=%d, " + + "dex2oatCpuTimeMillis=%d, " + + "sizeBytes=%d, " + + "sizeBeforeBytes=%d}", + getDexContainerFile(), isPrimaryAbi(), getAbi(), getActualCompilerFilter(), + DexoptResult.dexoptResultStatusToString(getStatus()), + getDex2oatWallTimeMillis(), getDex2oatCpuTimeMillis(), getSizeBytes(), + getSizeBeforeBytes()); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/model/DexoptStatus.java b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java new file mode 100644 index 0000000000..40130ea05b --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DexoptStatus.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +import java.util.List; + +/** + * Describes the dexopt status of a package. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class DexoptStatus { + /** @hide */ + protected DexoptStatus() {} + + /** @hide */ + public static @NonNull DexoptStatus create( + @NonNull List<DexContainerFileDexoptStatus> dexContainerFileDexoptStatuses) { + return new AutoValue_DexoptStatus(dexContainerFileDexoptStatuses); + } + + /** + * The statuses of the dex container file dexopts. Note that there can be multiple entries + * for the same dex container file, but for different ABIs. + */ + @NonNull public abstract List<DexContainerFileDexoptStatus> getDexContainerFileDexoptStatuses(); + + /** + * Describes the dexopt status of a dex container file. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + @Immutable + @AutoValue + public abstract static class DexContainerFileDexoptStatus { + /** @hide */ + protected DexContainerFileDexoptStatus() {} + + /** @hide */ + public static @NonNull DexContainerFileDexoptStatus create(@NonNull String dexContainerFile, + boolean isPrimaryDex, boolean isPrimaryAbi, @NonNull String abi, + @NonNull String compilerFilter, @NonNull String compilationReason, + @NonNull String locationDebugString) { + return new AutoValue_DexoptStatus_DexContainerFileDexoptStatus(dexContainerFile, + isPrimaryDex, isPrimaryAbi, abi, compilerFilter, compilationReason, + locationDebugString); + } + + /** The absolute path to the dex container file. */ + public abstract @NonNull String getDexContainerFile(); + + /** + * If true, the dex container file is a primary dex (the base APK or a split APK). + * Otherwise, it's a secondary dex (a APK or a JAR that the package sideloaded into its data + * directory). + */ + public abstract boolean isPrimaryDex(); + + /** + * If true, the dexopt is for the primary ABI of the package (the ABI that the + * application is launched with). Otherwise, the dexopt is for an ABI that other + * applications might be launched with when using this application's code. + */ + public abstract boolean isPrimaryAbi(); + + /** + * Returns the ABI that the dexopt is for. Possible values are documented at + * https://developer.android.com/ndk/guides/abis#sa. + */ + public abstract @NonNull String getAbi(); + + /** + * A human-readable string that describes the compiler filter. + * + * Possible values are: + * <ul> + * <li>A valid value of the {@code --compiler-filer} option passed to {@code dex2oat}, if + * the dexopt artifacts are valid. See + * https://source.android.com/docs/core/dalvik/configure#compilation_options. + * <li>{@code "run-from-apk"}, if the dexopt artifacts do not exist. + * <li>{@code "run-from-apk-fallback"}, if the dexopt artifacts exist but are invalid + * because the dex container file has changed. + * <li>{@code "error"}, if an unexpected error occurs. + * </ul> + */ + public abstract @NonNull String getCompilerFilter(); + + /** + * A string that describes the compilation reason. + * + * Possible values are: + * <ul> + * <li>The compilation reason, in text format, passed to {@code dex2oat}. + * <li>{@code "unknown"}: if the reason is empty or the dexopt artifacts do not exist. + * <li>{@code "error"}: if an unexpected error occurs. + * </ul> + * + * Note that this value can differ from the requested compilation reason passed to {@link + * DexoptParams.Builder}. Specifically, if the requested reason is for app install (e.g., + * "install"), and a DM file is passed to {@code dex2oat}, a "-dm" suffix will be appended + * to the actual reason (e.g., "install-dm"). Other compilation reasons remain unchanged + * even if a DM file is passed to {@code dex2oat}. + * + * Also note that the "-dm" suffix does <b>not</b> imply anything in the DM file being used + * by {@code dex2oat}. The compilation reason can still be "install-dm" even if {@code + * dex2oat} left all contents of the DM file unused or an empty DM file is passed to + * {@code dex2oat}. + */ + public abstract @NonNull String getCompilationReason(); + + /** + * A human-readable string that describes the location of the dexopt artifacts. + * + * Note that this string is for debugging purposes only. There is no stability guarantees + * for the format of the string. DO NOT use it programmatically. + */ + public abstract @NonNull String getLocationDebugString(); + } +} diff --git a/libartservice/service/java/com/android/server/art/model/OperationProgress.java b/libartservice/service/java/com/android/server/art/model/OperationProgress.java new file mode 100644 index 0000000000..a47a556bdc --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/OperationProgress.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.model; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.annotations.Immutable; + +import com.google.auto.value.AutoValue; + +/** @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@Immutable +@AutoValue +public abstract class OperationProgress { + /** @hide */ + protected OperationProgress() {} + + /** @hide */ + public static @NonNull OperationProgress create(int current, int total) { + return new AutoValue_OperationProgress(current, total); + } + + /** The overall progress, in the range of [0, 100]. */ + public int getPercentage() { + return 100 * getCurrent() / getTotal(); + } + + /** + * The number of processed items. Can be 0, which means the operation was just started. + * + * Currently, this is the number of packages, for which dexopt has been done, regardless + * of the results (performed, failed, skipped, etc.). + * + * @hide + */ + public abstract int getCurrent(); + + /** + * The total number of items. Stays constant during the operation. + * + * Currently, this is the total number of packages to dexopt. + * + * @hide + */ + public abstract int getTotal(); +} diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index a27dfa5370..2e3bd5fbc6 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -16,29 +16,1050 @@ package com.android.server.art; +import static android.os.ParcelFileDescriptor.AutoCloseInputStream; + +import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; +import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus; +import static com.android.server.art.testing.TestingUtils.deepEq; +import static com.android.server.art.testing.TestingUtils.inAnyOrder; +import static com.android.server.art.testing.TestingUtils.inAnyOrderDeepEquals; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.AdditionalMatchers.not; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.apphibernation.AppHibernationManager; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.ServiceSpecificException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; + import androidx.test.filters.SmallTest; -import com.android.server.art.ArtManagerLocal; +import com.android.modules.utils.pm.PackageStateModulesUtils; +import com.android.server.art.model.Config; +import com.android.server.art.model.DeleteResult; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.model.DexoptStatus; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.art.testing.TestingUtils; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageUserState; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.Mock; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; @SmallTest -@RunWith(MockitoJUnitRunner.class) +@RunWith(Parameterized.class) public class ArtManagerLocalTest { + private static final String PKG_NAME_1 = "com.example.foo"; + private static final String PKG_NAME_2 = "com.android.bar"; + private static final String PKG_NAME_HIBERNATING = "com.example.hibernating"; + private static final int INACTIVE_DAYS = 1; + private static final long CURRENT_TIME_MS = 10000000000l; + private static final long RECENT_TIME_MS = + CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) + 1; + private static final long NOT_RECENT_TIME_MS = + CURRENT_TIME_MS - TimeUnit.DAYS.toMillis(INACTIVE_DAYS) - 1; + + @Rule + public StaticMockitoRule mockitoRule = new StaticMockitoRule( + SystemProperties.class, Constants.class, PackageStateModulesUtils.class); + + @Mock private ArtManagerLocal.Injector mInjector; + @Mock private PackageManagerLocal mPackageManagerLocal; + @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; + @Mock private IArtd mArtd; + @Mock private DexoptHelper mDexoptHelper; + @Mock private AppHibernationManager mAppHibernationManager; + @Mock private UserManager mUserManager; + @Mock private DexUseManagerLocal mDexUseManager; + @Mock private StorageManager mStorageManager; + private PackageState mPkgState1; + private AndroidPackage mPkg1; + private List<DetailedSecondaryDexInfo> mSecondaryDexInfo1; + private Config mConfig; + + // True if the primary dex'es are in a system partition. + @Parameter(0) public boolean mIsInSystemPartition; + // True if the primary dex'es are in Incremental FS. + @Parameter(1) public boolean mIsInIncrementalFs; + // True if the artifacts should be in dalvik-cache. + @Parameter(2) public boolean mExpectedIsInDalvikCache; + private ArtManagerLocal mArtManagerLocal; + @Parameters(name = "isInReadonlyPartition={0},isInIncrementalFs={1}," + + "expectedIsInDalvikCache={2}") + public static Iterable<Object[]> + data() { + return List.of(new Object[] {true, false, true}, new Object[] {false, true, true}, + new Object[] {false, false, false}); + } + @Before - public void setUp() { - mArtManagerLocal = new ArtManagerLocal(); + public void setUp() throws Exception { + mConfig = new Config(); + + // Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs. + // These are the default test setups. They may or may not be used depending on the code path + // that each test case examines. + lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal); + lenient().when(mInjector.getArtd()).thenReturn(mArtd); + lenient().when(mInjector.getDexoptHelper()).thenReturn(mDexoptHelper); + lenient().when(mInjector.getConfig()).thenReturn(mConfig); + lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager); + lenient().when(mInjector.getUserManager()).thenReturn(mUserManager); + lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false); + lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false); + lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager); + lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS); + lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager); + + Path tempDir = Files.createTempDirectory("temp"); + tempDir.toFile().deleteOnExit(); + lenient().when(mInjector.getTempDir()).thenReturn(tempDir.toString()); + + lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile"); + lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile"); + lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify"); + lenient() + .when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update"))) + .thenReturn("verify"); + lenient().when(SystemProperties.get(eq("pm.dexopt.inactive"))).thenReturn("verify"); + lenient() + .when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt())) + .thenReturn(3); + lenient() + .when(SystemProperties.getInt( + eq("pm.dexopt.boot-after-mainline-update.concurrency"), anyInt())) + .thenReturn(3); + lenient() + .when(SystemProperties.getInt(eq("pm.dexopt.inactive.concurrency"), anyInt())) + .thenReturn(3); + lenient() + .when(SystemProperties.getInt( + eq("pm.dexopt.downgrade_after_inactive_days"), anyInt())) + .thenReturn(INACTIVE_DAYS); + + // No ISA translation. + lenient() + .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa.")))) + .thenReturn(""); + + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + lenient().when(Constants.isBootImageProfilingEnabled()).thenReturn(true); + + lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false); + lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true); + + lenient() + .when(mUserManager.getUserHandles(anyBoolean())) + .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1))); + + // All packages are by default recently used. + lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS); + mSecondaryDexInfo1 = createSecondaryDexInfo(); + lenient() + .doReturn(mSecondaryDexInfo1) + .when(mDexUseManager) + .getSecondaryDexInfo(eq(PKG_NAME_1)); + lenient() + .doReturn(mSecondaryDexInfo1) + .when(mDexUseManager) + .getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME_1)); + + simulateStorageNotLow(); + + lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot); + List<PackageState> pkgStates = createPackageStates(); + for (PackageState pkgState : pkgStates) { + lenient() + .when(mSnapshot.getPackageState(pkgState.getPackageName())) + .thenReturn(pkgState); + } + var packageStateMap = pkgStates.stream().collect( + Collectors.toMap(PackageState::getPackageName, it -> it)); + lenient().when(mSnapshot.getPackageStates()).thenReturn(packageStateMap); + mPkgState1 = mSnapshot.getPackageState(PKG_NAME_1); + mPkg1 = mPkgState1.getAndroidPackage(); + + lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mIsInIncrementalFs); + + // By default, none of the profiles are usable. + lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false); + lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false); + + mArtManagerLocal = new ArtManagerLocal(mInjector); + } + + @Test + public void testdeleteDexoptArtifacts() throws Exception { + when(mArtd.deleteArtifacts(any())).thenReturn(1l); + + DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); + assertThat(result.getFreedBytes()).isEqualTo(5); + + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */))); + + // Verify that there are no more calls than the ones above. + verify(mArtd, times(5)).deleteArtifacts(any()); + } + + @Test + public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception { + lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64"); + lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86"); + lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("x86"); + when(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64")); + + when(mArtd.deleteArtifacts(any())).thenReturn(1l); + + DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); + assertThat(result.getFreedBytes()).isEqualTo(5); + + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "x86_64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "x86", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "x86_64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "x86", mExpectedIsInDalvikCache))); + // We assume that the ISA got from `DexUseManagerLocal` is already the translated one. + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */))); + + // Verify that there are no more calls than the ones above. + verify(mArtd, times(5)).deleteArtifacts(any()); + } + + @Test(expected = IllegalArgumentException.class) + public void testdeleteDexoptArtifactsPackageNotFound() throws Exception { + when(mSnapshot.getPackageState(anyString())).thenReturn(null); + + mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); + } + + @Test(expected = IllegalArgumentException.class) + public void testdeleteDexoptArtifactsNoPackage() throws Exception { + when(mPkgState1.getAndroidPackage()).thenReturn(null); + + mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); + } + + @Test + public void testGetDexoptStatus() throws Exception { + doReturn(createGetDexoptStatusResult( + "speed", "compilation-reason-0", "location-debug-string-0")) + .when(mArtd) + .getDexoptStatus("/data/app/foo/base.apk", "arm64", "PCL[]"); + doReturn(createGetDexoptStatusResult( + "speed-profile", "compilation-reason-1", "location-debug-string-1")) + .when(mArtd) + .getDexoptStatus("/data/app/foo/base.apk", "arm", "PCL[]"); + doReturn(createGetDexoptStatusResult( + "verify", "compilation-reason-2", "location-debug-string-2")) + .when(mArtd) + .getDexoptStatus("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]"); + doReturn(createGetDexoptStatusResult( + "extract", "compilation-reason-3", "location-debug-string-3")) + .when(mArtd) + .getDexoptStatus("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]"); + doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown")) + .when(mArtd) + .getDexoptStatus("/data/user/0/foo/1.apk", "arm64", "CLC"); + + DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); + + assertThat(result.getDexContainerFileDexoptStatuses()) + .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptStatus>deepEquality()) + .containsExactly( + DexContainerFileDexoptStatus.create("/data/app/foo/base.apk", + true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "speed", "compilation-reason-0", "location-debug-string-0"), + DexContainerFileDexoptStatus.create("/data/app/foo/base.apk", + true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", + "speed-profile", "compilation-reason-1", "location-debug-string-1"), + DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk", + true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "verify", "compilation-reason-2", "location-debug-string-2"), + DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk", + true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", + "extract", "compilation-reason-3", "location-debug-string-3"), + DexContainerFileDexoptStatus.create("/data/user/0/foo/1.apk", + false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "run-from-apk", "unknown", "unknown")); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetDexoptStatusPackageNotFound() throws Exception { + when(mSnapshot.getPackageState(anyString())).thenReturn(null); + + mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetDexoptStatusNoPackage() throws Exception { + when(mPkgState1.getAndroidPackage()).thenReturn(null); + + mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); + } + + @Test + public void testGetDexoptStatusNonFatalError() throws Exception { + when(mArtd.getDexoptStatus(any(), any(), any())) + .thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message")); + + DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); + + List<DexContainerFileDexoptStatus> statuses = result.getDexContainerFileDexoptStatuses(); + assertThat(statuses.size()).isEqualTo(5); + + for (DexContainerFileDexoptStatus status : statuses) { + assertThat(status.getCompilerFilter()).isEqualTo("error"); + assertThat(status.getCompilationReason()).isEqualTo("error"); + assertThat(status.getLocationDebugString()).isEqualTo("some error message"); + } + } + + @Test + public void testClearAppProfiles() throws Exception { + mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1); + + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"))); + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary"))); + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary"))); + + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"))); + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk"))); + } + + @Test(expected = IllegalArgumentException.class) + public void testClearAppProfilesPackageNotFound() throws Exception { + when(mSnapshot.getPackageState(anyString())).thenReturn(null); + + mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1); + } + + @Test(expected = IllegalArgumentException.class) + public void testClearAppProfilesNoPackage() throws Exception { + when(mPkgState1.getAndroidPackage()).thenReturn(null); + + mArtManagerLocal.clearAppProfiles(mSnapshot, PKG_NAME_1); + } + + @Test + public void testDexoptPackage() throws Exception { + var params = new DexoptParams.Builder("install").build(); + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + when(mDexoptHelper.dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params), + same(cancellationSignal), any())) + .thenReturn(result); + + assertThat( + mArtManagerLocal.dexoptPackage(mSnapshot, PKG_NAME_1, params, cancellationSignal)) + .isSameInstanceAs(result); + } + + @Test + public void testResetDexoptStatus() throws Exception { + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + when(mDexoptHelper.dexopt( + any(), deepEq(List.of(PKG_NAME_1)), any(), same(cancellationSignal), any())) + .thenReturn(result); + + assertThat(mArtManagerLocal.resetDexoptStatus(mSnapshot, PKG_NAME_1, cancellationSignal)) + .isSameInstanceAs(result); + + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"))); + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary"))); + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary"))); + + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "arm64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/base.apk", "arm", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache))); + + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"))); + verify(mArtd).deleteProfile( + deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk"))); + + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */))); + } + + @Test + public void testDexoptPackages() throws Exception { + var dexoptResult = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_2)).thenReturn(CURRENT_TIME_MS); + simulateStorageLow(); + + // It should use the default package list and params. The list is sorted by last active + // time in descending order. + doReturn(dexoptResult) + .when(mDexoptHelper) + .dexopt(any(), deepEq(List.of(PKG_NAME_2, PKG_NAME_1)), + argThat(params -> params.getReason().equals("bg-dexopt")), + same(cancellationSignal), any(), any(), any()); + + assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */)) + .isSameInstanceAs(dexoptResult); + + // Nothing to downgrade. + verify(mDexoptHelper, never()) + .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")), + any(), any(), any(), any()); + } + + @Test + public void testDexoptPackagesRecentlyInstalled() throws Exception { + // The package is recently installed but hasn't been used. + PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1)); + when(userState.getFirstInstallTimeMillis()).thenReturn(RECENT_TIME_MS); + when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(0l); + simulateStorageLow(); + + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + // PKG_NAME_1 should be dexopted. + doReturn(result) + .when(mDexoptHelper) + .dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), + argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(), + any(), any()); + + mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + + // PKG_NAME_1 should not be downgraded. + verify(mDexoptHelper, never()) + .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")), + any(), any(), any(), any()); + } + + @Test + public void testDexoptPackagesInactive() throws Exception { + // PKG_NAME_1 is neither recently installed nor recently used. + PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1)); + when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS); + when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS); + simulateStorageLow(); + + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + // PKG_NAME_1 should not be dexopted. + doReturn(result) + .when(mDexoptHelper) + .dexopt(any(), deepEq(List.of(PKG_NAME_2)), + argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(), + any(), any()); + + // PKG_NAME_1 should be downgraded. + doReturn(result) + .when(mDexoptHelper) + .dexopt(any(), deepEq(List.of(PKG_NAME_1)), + argThat(params -> params.getReason().equals("inactive")), any(), any(), + any(), any()); + + mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + } + + @Test + public void testDexoptPackagesInactiveStorageNotLow() throws Exception { + // PKG_NAME_1 is neither recently installed nor recently used. + PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1)); + when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS); + when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS); + + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + // PKG_NAME_1 should not be dexopted. + doReturn(result) + .when(mDexoptHelper) + .dexopt(any(), deepEq(List.of(PKG_NAME_2)), + argThat(params -> params.getReason().equals("bg-dexopt")), any(), any(), + any(), any()); + + mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + + // PKG_NAME_1 should not be downgraded because the storage is not low. + verify(mDexoptHelper, never()) + .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")), + any(), any(), any(), any()); + } + + @Test + public void testDexoptPackagesBootAfterMainlineUpdate() throws Exception { + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + lenient().when(mInjector.isSystemUiPackage(PKG_NAME_1)).thenReturn(true); + lenient().when(mInjector.isLauncherPackage(PKG_NAME_2)).thenReturn(true); + + // It should dexopt the system UI and the launcher. + when(mDexoptHelper.dexopt( + any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), any(), any(), any(), any(), any())) + .thenReturn(result); + + mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + } + + @Test + public void testDexoptPackagesBootAfterMainlineUpdatePackagesNotFound() throws Exception { + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + // PKG_NAME_1 is neither recently installed nor recently used. + PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1)); + lenient().when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS); + lenient() + .when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)) + .thenReturn(NOT_RECENT_TIME_MS); + simulateStorageLow(); + + // It should dexopt the system UI and the launcher, but they are not found. + when(mDexoptHelper.dexopt(any(), deepEq(List.of()), any(), any(), any(), any(), any())) + .thenReturn(result); + + mArtManagerLocal.dexoptPackages(mSnapshot, "boot-after-mainline-update", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + + // It should never downgrade apps, even if the storage is low. + verify(mDexoptHelper, never()) + .dexopt(any(), any(), argThat(params -> params.getReason().equals("inactive")), + any(), any(), any(), any()); + } + + @Test + public void testDexoptPackagesOverride() throws Exception { + // PKG_NAME_1 is neither recently installed nor recently used. + PackageUserState userState = mPkgState1.getStateForUser(UserHandle.of(1)); + when(userState.getFirstInstallTimeMillis()).thenReturn(NOT_RECENT_TIME_MS); + when(mDexUseManager.getPackageLastUsedAtMs(PKG_NAME_1)).thenReturn(NOT_RECENT_TIME_MS); + simulateStorageLow(); + + var params = new DexoptParams.Builder("bg-dexopt").build(); + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(), + (snapshot, reason, defaultPackages, builder, passedSignal) -> { + assertThat(reason).isEqualTo("bg-dexopt"); + assertThat(defaultPackages).containsExactly(PKG_NAME_2); + assertThat(passedSignal).isSameInstanceAs(cancellationSignal); + builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params); + }); + + // It should use the overridden package list and params. + doReturn(result) + .when(mDexoptHelper) + .dexopt(any(), deepEq(List.of(PKG_NAME_1)), same(params), any(), any(), any(), + any()); + + mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + + // It should not downgrade PKG_NAME_1 because it's in the overridden package list. It should + // not downgrade PKG_NAME_2 either because it's not an inactive package. + verify(mDexoptHelper, never()) + .dexopt(any(), any(), argThat(params2 -> params2.getReason().equals("inactive")), + any(), any(), any(), any()); + } + + @Test + public void testDexoptPackagesOverrideCleared() throws Exception { + var params = new DexoptParams.Builder("bg-dexopt").build(); + var result = mock(DexoptResult.class); + var cancellationSignal = new CancellationSignal(); + + mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(), + (snapshot, reason, defaultPackages, builder, passedSignal) -> { + builder.setPackages(List.of(PKG_NAME_1)).setDexoptParams(params); + }); + mArtManagerLocal.clearBatchDexoptStartCallback(); + + // It should use the default package list and params. + when(mDexoptHelper.dexopt(any(), inAnyOrder(PKG_NAME_1, PKG_NAME_2), not(same(params)), + same(cancellationSignal), any(), any(), any())) + .thenReturn(result); + + assertThat(mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */)) + .isSameInstanceAs(result); + } + + @Test(expected = IllegalStateException.class) + public void testDexoptPackagesOverrideReasonChanged() throws Exception { + var params = new DexoptParams.Builder("first-boot").build(); + var cancellationSignal = new CancellationSignal(); + + mArtManagerLocal.setBatchDexoptStartCallback(ForkJoinPool.commonPool(), + (snapshot, reason, defaultPackages, builder, passedSignal) -> { + builder.setDexoptParams(params); + }); + + mArtManagerLocal.dexoptPackages(mSnapshot, "bg-dexopt", cancellationSignal, + null /* processCallbackExecutor */, null /* processCallback */); + } + + @Test + public void testSnapshotAppProfile() throws Exception { + var options = new MergeProfileOptions(); + options.forceMerge = true; + options.forBootImage = false; + + File tempFile = File.createTempFile("primary", ".prof"); + tempFile.deleteOnExit(); + + ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"); + String dexPath = "/data/app/foo/base.apk"; + + when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true); + + when(mArtd.mergeProfiles(deepEq(List.of(refProfile, + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "primary"))), + isNull(), + deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "primary", + Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)), + deepEq(List.of(dexPath)), deepEq(options))) + .thenAnswer(invocation -> { + try (var writer = new FileWriter(tempFile)) { + writer.write("snapshot"); + } catch (IOException e) { + throw new RuntimeException(e); + } + var output = invocation.<OutputProfile>getArgument(2); + output.profilePath.tmpPath = tempFile.getPath(); + return true; + }); + + ParcelFileDescriptor fd = + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */); + + verify(mArtd).deleteProfile( + argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempFile.getPath()))); + + assertThat(fd.getStatSize()).isGreaterThan(0); + try (InputStream inputStream = new AutoCloseInputStream(fd)) { + String contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + assertThat(contents).isEqualTo("snapshot"); + } } @Test - public void testScaffolding() { - assertThat(true).isTrue(); + public void testSnapshotAppProfileFromDm() throws Exception { + String tempPathForRef = "/temp/path/for/ref"; + File tempFileForSnapshot = File.createTempFile("primary", ".prof"); + tempFileForSnapshot.deleteOnExit(); + + ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"); + String dexPath = "/data/app/foo/base.apk"; + + // Simulate that the reference profile doesn't exist. + when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false); + + // The DM file is usable. + when(mArtd.copyAndRewriteProfile( + deepEq(AidlUtils.buildProfilePathForDm(dexPath)), any(), eq(dexPath))) + .thenAnswer(invocation -> { + var output = invocation.<OutputProfile>getArgument(1); + output.profilePath.tmpPath = tempPathForRef; + return true; + }); + + // Verify that the reference file initialized from the DM file is used. + when(mArtd.mergeProfiles( + argThat(profiles + -> profiles.stream().anyMatch(profile + -> profile.getTag() == ProfilePath.tmpProfilePath + && profile.getTmpProfilePath().tmpPath.equals( + tempPathForRef))), + isNull(), any(), deepEq(List.of(dexPath)), any())) + .thenAnswer(invocation -> { + var output = invocation.<OutputProfile>getArgument(2); + output.profilePath.tmpPath = tempFileForSnapshot.getPath(); + return true; + }); + + ParcelFileDescriptor fd = + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */); + + verify(mArtd).deleteProfile(argThat(profile + -> profile.getTmpProfilePath().tmpPath.equals(tempFileForSnapshot.getPath()))); + verify(mArtd).deleteProfile( + argThat(profile -> profile.getTmpProfilePath().tmpPath.equals(tempPathForRef))); + } + + @Test + public void testSnapshotAppProfileSplit() throws Exception { + ProfilePath refProfile = + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"); + String dexPath = "/data/app/foo/split_0.apk"; + + when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(true); + + when(mArtd.mergeProfiles(deepEq(List.of(refProfile, + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "split_0.split"))), + isNull(), + deepEq(AidlUtils.buildOutputProfileForPrimary(PKG_NAME_1, "split_0.split", + Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)), + deepEq(List.of(dexPath)), any())) + .thenReturn(false); + + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "split_0"); + } + + @Test + public void testSnapshotAppProfileEmpty() throws Exception { + when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false); + + ParcelFileDescriptor fd = + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */); + + verify(mArtd, never()).deleteProfile(any()); + + assertThat(fd.getStatSize()).isEqualTo(0); + try (InputStream inputStream = new AutoCloseInputStream(fd)) { + assertThat(inputStream.readAllBytes()).isEmpty(); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testSnapshotAppProfilePackageNotFound() throws Exception { + when(mSnapshot.getPackageState(anyString())).thenReturn(null); + + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */); + } + + @Test(expected = IllegalArgumentException.class) + public void testSnapshotAppProfileNoPackage() throws Exception { + when(mPkgState1.getAndroidPackage()).thenReturn(null); + + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */); + } + + @Test(expected = IllegalArgumentException.class) + public void testSnapshotAppProfileSplitNotFound() throws Exception { + mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, "non-existent-split"); + } + + @Test + public void testDumpAppProfile() throws Exception { + var options = new MergeProfileOptions(); + options.dumpOnly = true; + + when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options))) + .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`. + + ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile( + mSnapshot, PKG_NAME_1, null /* splitName */, false /* dumpClassesAndMethods */); + } + + @Test + public void testDumpAppProfileDumpClassesAndMethods() throws Exception { + var options = new MergeProfileOptions(); + options.dumpClassesAndMethods = true; + + when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options))) + .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`. + + ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile( + mSnapshot, PKG_NAME_1, null /* splitName */, true /* dumpClassesAndMethods */); + } + + @Test + public void testSnapshotBootImageProfile() throws Exception { + // `lenient()` is required to allow mocking the same method multiple times. + lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1"); + lenient().when(Constants.getenv("SYSTEMSERVERCLASSPATH")).thenReturn("sscp0:sscp1"); + lenient().when(Constants.getenv("STANDALONE_SYSTEMSERVER_JARS")).thenReturn("sssj0:sssj1"); + + var options = new MergeProfileOptions(); + options.forceMerge = true; + options.forBootImage = true; + + when(mArtd.mergeProfiles( + inAnyOrderDeepEquals( + AidlUtils.buildProfilePathForPrimaryRef("android", "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, "android", "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, "android", "primary"), + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_2, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_2, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_2, "primary"), + AidlUtils.buildProfilePathForPrimaryRef( + PKG_NAME_HIBERNATING, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_HIBERNATING, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_HIBERNATING, "primary")), + isNull(), + deepEq(AidlUtils.buildOutputProfileForPrimary("android", "primary", + Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */)), + deepEq(List.of("bcp0", "bcp1", "sscp0", "sscp1", "sssj0", "sssj1")), + deepEq(options))) + .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`. + + mArtManagerLocal.snapshotBootImageProfile(mSnapshot); + } + + @Test + public void testCleanup() throws Exception { + // It should keep all artifacts. + doReturn(createGetDexoptStatusResult("speed-profile", "bg-dexopt", "location")) + .when(mArtd) + .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm64"), any()); + doReturn(createGetDexoptStatusResult("verify", "cmdline", "location")) + .when(mArtd) + .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any()); + + // It should only keep VDEX files. + doReturn(createGetDexoptStatusResult("verify", "vdex", "location")) + .when(mArtd) + .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm64"), any()); + doReturn(createGetDexoptStatusResult("verify", "vdex", "location")) + .when(mArtd) + .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm"), any()); + + // It should not keep any artifacts. + doReturn(createGetDexoptStatusResult("run-from-apk", "unknown", "unknown")) + .when(mArtd) + .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm"), any()); + + when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1)); + mArtManagerLocal.cleanup(mSnapshot); + + verify(mArtd).cleanup( + inAnyOrderDeepEquals(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "primary"), + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForPrimaryCur( + 1 /* userId */, PKG_NAME_1, "split_0.split"), + AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"), + AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")), + inAnyOrderDeepEquals(AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64", + mExpectedIsInDalvikCache), + AidlUtils.buildArtifactsPath( + "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)), + inAnyOrderDeepEquals( + VdexPath.artifactsPath(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm64", mExpectedIsInDalvikCache)), + VdexPath.artifactsPath(AidlUtils.buildArtifactsPath( + "/data/app/foo/split_0.apk", "arm", mExpectedIsInDalvikCache)))); + } + + private AndroidPackage createPackage(boolean multiSplit) { + AndroidPackage pkg = mock(AndroidPackage.class); + + var baseSplit = mock(AndroidPackageSplit.class); + lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk"); + lenient().when(baseSplit.isHasCode()).thenReturn(true); + + if (multiSplit) { + // split_0 has code while split_1 doesn't. + var split0 = mock(AndroidPackageSplit.class); + lenient().when(split0.getName()).thenReturn("split_0"); + lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk"); + lenient().when(split0.isHasCode()).thenReturn(true); + var split1 = mock(AndroidPackageSplit.class); + lenient().when(split1.getName()).thenReturn("split_1"); + lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk"); + lenient().when(split1.isHasCode()).thenReturn(false); + + lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1)); + } else { + lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit)); + } + + return pkg; + } + + private PackageUserState createPackageUserState() { + PackageUserState pkgUserState = mock(PackageUserState.class); + lenient().when(pkgUserState.isInstalled()).thenReturn(true); + // All packages are by default pre-installed. + lenient().when(pkgUserState.getFirstInstallTimeMillis()).thenReturn(0l); + return pkgUserState; + } + + private PackageState createPackageState( + String packageName, boolean isDexoptable, boolean multiSplit) { + PackageState pkgState = mock(PackageState.class); + + lenient().when(pkgState.getPackageName()).thenReturn(packageName); + lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a"); + lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a"); + lenient().when(pkgState.isSystem()).thenReturn(mIsInSystemPartition); + lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false); + + AndroidPackage pkg = createPackage(multiSplit); + lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg); + + PackageUserState pkgUserState0 = createPackageUserState(); + lenient().when(pkgState.getStateForUser(UserHandle.of(0))).thenReturn(pkgUserState0); + PackageUserState pkgUserState1 = createPackageUserState(); + lenient().when(pkgState.getStateForUser(UserHandle.of(1))).thenReturn(pkgUserState1); + + lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(isDexoptable); + + return pkgState; + } + + private List<PackageState> createPackageStates() { + PackageState pkgState1 = + createPackageState(PKG_NAME_1, true /* isDexoptable */, true /* multiSplit */); + + PackageState pkgState2 = + createPackageState(PKG_NAME_2, true /* isDexoptable */, false /* multiSplit */); + + // This should not be dexopted because it's hibernating. However, it should be included + // when snapshotting boot image profile. + PackageState pkgHibernatingState = createPackageState( + PKG_NAME_HIBERNATING, true /* isDexoptable */, false /* multiSplit */); + lenient() + .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING)) + .thenReturn(true); + + // This should not be dexopted because it's not dexoptable. + PackageState nonDexoptablePkgState = createPackageState( + "com.example.non-dexoptable", false /* isDexoptable */, false /* multiSplit */); + + return List.of(pkgState1, pkgState2, pkgHibernatingState, nonDexoptablePkgState); + } + + private GetDexoptStatusResult createGetDexoptStatusResult( + String compilerFilter, String compilationReason, String locationDebugString) { + var getDexoptStatusResult = new GetDexoptStatusResult(); + getDexoptStatusResult.compilerFilter = compilerFilter; + getDexoptStatusResult.compilationReason = compilationReason; + getDexoptStatusResult.locationDebugString = locationDebugString; + return getDexoptStatusResult; + } + + private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception { + var dexInfo = mock(DetailedSecondaryDexInfo.class); + lenient().when(dexInfo.dexPath()).thenReturn("/data/user/0/foo/1.apk"); + lenient().when(dexInfo.abiNames()).thenReturn(Set.of("arm64-v8a")); + lenient().when(dexInfo.classLoaderContext()).thenReturn("CLC"); + return List.of(dexInfo); + } + + private void simulateStorageLow() throws Exception { + lenient() + .when(mStorageManager.getAllocatableBytes(any())) + .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES - 1); + } + + private void simulateStorageNotLow() throws Exception { + lenient() + .when(mStorageManager.getAllocatableBytes(any())) + .thenReturn(ArtManagerLocal.DOWNGRADE_THRESHOLD_ABOVE_LOW_BYTES); } } diff --git a/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java new file mode 100644 index 0000000000..3528caf3c3 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art; + +import static com.android.server.art.model.Config.Callback; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.os.CancellationSignal; +import android.os.SystemProperties; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.BackgroundDexoptJob.CompletedResult; +import com.android.server.art.BackgroundDexoptJob.FatalErrorResult; +import com.android.server.art.BackgroundDexoptJob.Result; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.Config; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.PackageManagerLocal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BackgroundDexoptJobTest { + private static final long TIMEOUT_SEC = 10; + + @Rule + public StaticMockitoRule mockitoRule = + new StaticMockitoRule(SystemProperties.class, BackgroundDexoptJobService.class); + + @Mock private BackgroundDexoptJob.Injector mInjector; + @Mock private ArtManagerLocal mArtManagerLocal; + @Mock private PackageManagerLocal mPackageManagerLocal; + @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; + @Mock private JobScheduler mJobScheduler; + @Mock private DexoptResult mDexoptResult; + @Mock private BackgroundDexoptJobService mJobService; + @Mock private JobParameters mJobParameters; + private Config mConfig; + private BackgroundDexoptJob mBackgroundDexoptJob; + private Semaphore mJobFinishedCalled = new Semaphore(0); + + @Before + public void setUp() throws Exception { + lenient() + .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean())) + .thenReturn(false); + + lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot); + + mConfig = new Config(); + + lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal); + lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal); + lenient().when(mInjector.getConfig()).thenReturn(mConfig); + lenient().when(mInjector.getJobScheduler()).thenReturn(mJobScheduler); + + mBackgroundDexoptJob = new BackgroundDexoptJob(mInjector); + lenient().when(BackgroundDexoptJobService.getJob()).thenReturn(mBackgroundDexoptJob); + + lenient() + .doAnswer(invocation -> { + mJobFinishedCalled.release(); + return null; + }) + .when(mJobService) + .jobFinished(any(), anyBoolean()); + + lenient() + .when(mJobParameters.getStopReason()) + .thenReturn(JobParameters.STOP_REASON_UNDEFINED); + } + + @Test + public void testStart() { + when(mArtManagerLocal.dexoptPackages( + same(mSnapshot), eq(ReasonMapping.REASON_BG_DEXOPT), any(), any(), any())) + .thenReturn(mDexoptResult); + + Result result = Utils.getFuture(mBackgroundDexoptJob.start()); + assertThat(result).isInstanceOf(CompletedResult.class); + assertThat(((CompletedResult) result).dexoptResult()).isSameInstanceAs(mDexoptResult); + + verify(mArtManagerLocal).cleanup(same(mSnapshot)); + } + + @Test + public void testStartAlreadyRunning() { + Semaphore dexoptDone = new Semaphore(0); + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenAnswer(invocation -> { + assertThat(dexoptDone.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + return mDexoptResult; + }); + + Future<Result> future1 = mBackgroundDexoptJob.start(); + Future<Result> future2 = mBackgroundDexoptJob.start(); + assertThat(future1).isSameInstanceAs(future2); + + dexoptDone.release(); + Utils.getFuture(future1); + + verify(mArtManagerLocal, times(1)).dexoptPackages(any(), any(), any(), any(), any()); + } + + @Test + public void testStartAnother() { + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenReturn(mDexoptResult); + + Future<Result> future1 = mBackgroundDexoptJob.start(); + Utils.getFuture(future1); + Future<Result> future2 = mBackgroundDexoptJob.start(); + Utils.getFuture(future2); + assertThat(future1).isNotSameInstanceAs(future2); + } + + @Test + public void testStartFatalError() { + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenThrow(IllegalStateException.class); + + Result result = Utils.getFuture(mBackgroundDexoptJob.start()); + assertThat(result).isInstanceOf(FatalErrorResult.class); + } + + @Test + public void testStartIgnoreDisabled() { + lenient() + .when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean())) + .thenReturn(true); + + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenReturn(mDexoptResult); + + // The `start` method should ignore the system property. The system property is for + // `schedule`. + Utils.getFuture(mBackgroundDexoptJob.start()); + } + + @Test + public void testCancel() { + Semaphore dexoptCancelled = new Semaphore(0); + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenAnswer(invocation -> { + assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + var cancellationSignal = invocation.<CancellationSignal>getArgument(2); + assertThat(cancellationSignal.isCanceled()).isTrue(); + return mDexoptResult; + }); + + Future<Result> future = mBackgroundDexoptJob.start(); + mBackgroundDexoptJob.cancel(); + dexoptCancelled.release(); + Utils.getFuture(future); + } + + @Test + public void testSchedule() { + var captor = ArgumentCaptor.forClass(JobInfo.class); + when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS); + + assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS); + + JobInfo jobInfo = captor.getValue(); + assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS); + assertThat(jobInfo.isRequireDeviceIdle()).isTrue(); + assertThat(jobInfo.isRequireCharging()).isTrue(); + assertThat(jobInfo.isRequireBatteryNotLow()).isTrue(); + assertThat(jobInfo.isRequireStorageNotLow()).isFalse(); + } + + @Test + public void testScheduleDisabled() { + when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean())) + .thenReturn(true); + + assertThat(mBackgroundDexoptJob.schedule()) + .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP); + + verify(mJobScheduler, never()).schedule(any()); + } + + @Test + public void testScheduleOverride() { + mConfig.setScheduleBackgroundDexoptJobCallback(Runnable::run, builder -> { + builder.setRequiresBatteryNotLow(false); + builder.setPriority(JobInfo.PRIORITY_LOW); + }); + + var captor = ArgumentCaptor.forClass(JobInfo.class); + when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS); + + assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS); + + JobInfo jobInfo = captor.getValue(); + assertThat(jobInfo.getIntervalMillis()).isEqualTo(BackgroundDexoptJob.JOB_INTERVAL_MS); + assertThat(jobInfo.isRequireDeviceIdle()).isTrue(); + assertThat(jobInfo.isRequireCharging()).isTrue(); + assertThat(jobInfo.isRequireBatteryNotLow()).isFalse(); + assertThat(jobInfo.getPriority()).isEqualTo(JobInfo.PRIORITY_LOW); + } + + @Test + public void testScheduleOverrideCleared() { + mConfig.setScheduleBackgroundDexoptJobCallback( + Runnable::run, builder -> { builder.setRequiresBatteryNotLow(false); }); + mConfig.clearScheduleBackgroundDexoptJobCallback(); + + var captor = ArgumentCaptor.forClass(JobInfo.class); + when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS); + + assertThat(mBackgroundDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS); + + JobInfo jobInfo = captor.getValue(); + assertThat(jobInfo.isRequireBatteryNotLow()).isTrue(); + } + + @Test(expected = IllegalStateException.class) + public void testScheduleOverrideStorageNotLow() { + mConfig.setScheduleBackgroundDexoptJobCallback( + Runnable::run, builder -> { builder.setRequiresStorageNotLow(true); }); + + mBackgroundDexoptJob.schedule(); + } + + @Test + public void testUnschedule() { + mBackgroundDexoptJob.unschedule(); + verify(mJobScheduler).cancel(anyInt()); + } + + @Test + public void testWantsRescheduleFalsePerformed() throws Exception { + when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_PERFORMED); + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenReturn(mDexoptResult); + + mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters); + assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */); + } + + @Test + public void testWantsRescheduleFalseFatalError() throws Exception { + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenThrow(RuntimeException.class); + + mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters); + assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + verify(mJobService).jobFinished(any(), eq(false) /* wantsReschedule */); + } + + @Test + public void testWantsRescheduleTrue() throws Exception { + when(mDexoptResult.getFinalStatus()).thenReturn(DexoptResult.DEXOPT_CANCELLED); + when(mArtManagerLocal.dexoptPackages(any(), any(), any(), any(), any())) + .thenReturn(mDexoptResult); + + mBackgroundDexoptJob.onStartJob(mJobService, mJobParameters); + assertThat(mJobFinishedCalled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + verify(mJobService).jobFinished(any(), eq(true) /* wantsReschedule */); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/DebouncerTest.java b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java new file mode 100644 index 0000000000..bf0bc709bf --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/DebouncerTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.lenient; + +import androidx.test.filters.SmallTest; + +import com.android.server.art.testing.MockClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class DebouncerTest { + private MockClock mMockClock; + private Debouncer mDebouncer; + + @Before + public void setUp() throws Exception { + mMockClock = new MockClock(); + mDebouncer = + new Debouncer(100 /* intervalMs */, () -> mMockClock.createScheduledExecutor()); + } + + @Test + public void test() throws Exception { + List<Integer> list = new ArrayList<>(); + + mDebouncer.maybeRunAsync(() -> list.add(1)); + mDebouncer.maybeRunAsync(() -> list.add(2)); + mMockClock.advanceTime(100); + mDebouncer.maybeRunAsync(() -> list.add(3)); + mMockClock.advanceTime(99); + mDebouncer.maybeRunAsync(() -> list.add(4)); + mMockClock.advanceTime(99); + mDebouncer.maybeRunAsync(() -> list.add(5)); + mMockClock.advanceTime(1000); + + assertThat(list).containsExactly(2, 5).inOrder(); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java new file mode 100644 index 0000000000..5850e617d8 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; +import static com.android.server.art.DexUseManagerLocal.DexLoader; +import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Environment; +import android.os.Process; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.storage.StorageManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.model.DexContainerFileUseInfo; +import com.android.server.art.proto.DexUseProto; +import com.android.server.art.testing.MockClock; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.PathClassLoader; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DexUseManagerTest { + private static final String LOADING_PKG_NAME = "com.example.loadingpackage"; + private static final String OWNING_PKG_NAME = "com.example.owningpackage"; + private static final String BASE_APK = "/data/app/" + OWNING_PKG_NAME + "/base.apk"; + private static final String SPLIT_APK = "/data/app/" + OWNING_PKG_NAME + "/split_0.apk"; + + @Rule + public StaticMockitoRule mockitoRule = + new StaticMockitoRule(SystemProperties.class, Constants.class, Process.class); + + private final UserHandle mUserHandle = Binder.getCallingUserHandle(); + + /** + * The default value of `isDexFilePublic` returned by `getSecondaryDexInfo`. The value doesn't + * matter because it's undefined, but it's needed for deep equality check, to make the test + * simpler. + */ + private final boolean mDefaultIsDexFilePublic = true; + + @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; + @Mock private DexUseManagerLocal.Injector mInjector; + @Mock private IArtd mArtd; + @Mock private Context mContext; + private DexUseManagerLocal mDexUseManager; + private String mCeDir; + private String mDeDir; + private MockClock mMockClock; + private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; + private File mTempFile; + private Map<String, PackageState> mPackageStates; + + @Before + public void setUp() throws Exception { + // No ISA translation. + lenient() + .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa.")))) + .thenReturn(""); + + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + + lenient().when(Process.isIsolatedUid(anyInt())).thenReturn(false); + + mPackageStates = new HashMap<>(); + + PackageState loadingPkgState = createPackageState(LOADING_PKG_NAME, "armeabi-v7a"); + addPackage(LOADING_PKG_NAME, loadingPkgState); + PackageState owningPkgState = createPackageState(OWNING_PKG_NAME, "arm64-v8a"); + addPackage(OWNING_PKG_NAME, owningPkgState); + PackageState platformPkgState = + createPackageState(Utils.PLATFORM_PACKAGE_NAME, "arm64-v8a"); + addPackage(Utils.PLATFORM_PACKAGE_NAME, platformPkgState); + + lenient().when(mSnapshot.getPackageStates()).thenReturn(mPackageStates); + + mBroadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); + lenient() + .when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any())) + .thenReturn(mock(Intent.class)); + + mCeDir = Environment + .getDataCePackageDirectoryForUser(StorageManager.UUID_DEFAULT, + Binder.getCallingUserHandle(), OWNING_PKG_NAME) + .toString(); + mDeDir = Environment + .getDataDePackageDirectoryForUser(StorageManager.UUID_DEFAULT, + Binder.getCallingUserHandle(), OWNING_PKG_NAME) + .toString(); + mMockClock = new MockClock(); + + mTempFile = File.createTempFile("package-dex-usage", ".pb"); + mTempFile.deleteOnExit(); + + lenient().when(mInjector.getArtd()).thenReturn(mArtd); + lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l); + lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath()); + lenient() + .when(mInjector.createScheduledExecutor()) + .thenAnswer(invocation -> mMockClock.createScheduledExecutor()); + lenient().when(mInjector.getContext()).thenReturn(mContext); + lenient().when(mInjector.getAllPackageNames()).thenReturn(mPackageStates.keySet()); + + mDexUseManager = new DexUseManagerLocal(mInjector); + mDexUseManager.systemReady(); + } + + @Test + public void testPrimaryDexOwned() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC")); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)) + .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse(); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty(); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)) + .isFalse(); + } + + @Test + public void testPrimaryDexOwnedIsolated() { + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC")); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)) + .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue(); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty(); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)) + .isFalse(); + } + + @Test + public void testPrimaryDexOwnedSplitIsolated() { + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC")); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)).isEmpty(); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isFalse(); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)) + .containsExactly(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)).isTrue(); + } + + @Test + public void testPrimaryDexOthers() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC")); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)) + .containsExactly(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, BASE_APK)).isTrue(); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)).isEmpty(); + assertThat(mDexUseManager.isPrimaryDexUsedByOtherApps(OWNING_PKG_NAME, SPLIT_APK)) + .isFalse(); + } + + /** Checks that it ignores and dedups things correctly. */ + @Test + public void testPrimaryDexMultipleEntries() throws Exception { + verifyPrimaryDexMultipleEntries( + false /* saveAndLoad */, false /* shutdown */, false /* cleanup */); + } + + /** Checks that it saves data after some time has passed and loads data correctly. */ + @Test + public void testPrimaryDexMultipleEntriesPersisted() throws Exception { + verifyPrimaryDexMultipleEntries( + true /*saveAndLoad */, false /* shutdown */, false /* cleanup */); + } + + /** Checks that it saves data when the device is being shutdown and loads data correctly. */ + @Test + public void testPrimaryDexMultipleEntriesPersistedDueToShutdown() throws Exception { + verifyPrimaryDexMultipleEntries( + true /*saveAndLoad */, true /* shutdown */, false /* cleanup */); + } + + /** Checks that it doesn't accidentally cleanup any entry that is needed. */ + @Test + public void testPrimaryDexMultipleEntriesSurviveCleanup() throws Exception { + verifyPrimaryDexMultipleEntries( + false /*saveAndLoad */, false /* shutdown */, true /* cleanup */); + } + + private void verifyPrimaryDexMultipleEntries( + boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception { + when(mInjector.getCurrentTimeMillis()).thenReturn(1000l); + + lenient() + .when(mArtd.getDexFileVisibility(BASE_APK)) + .thenReturn(FileVisibility.OTHER_READABLE); + lenient() + .when(mArtd.getDexFileVisibility(SPLIT_APK)) + .thenReturn(FileVisibility.OTHER_READABLE); + + // These should be ignored. + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(BASE_APK, "CLC")); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, + Map.of("/data/app/" + OWNING_PKG_NAME + "/non-existing.apk", "CLC")); + + // Some of these should be deduped. + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC", SPLIT_APK, "CLC")); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(BASE_APK, "CLC")); + + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC")); + when(mInjector.getCurrentTimeMillis()).thenReturn(2000l); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC")); + + if (saveAndLoad) { + if (shutdown) { + mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class)); + } else { + // MockClock runs tasks synchronously. + mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); + } + mDexUseManager = new DexUseManagerLocal(mInjector); + } + + if (cleanup) { + // Nothing should be cleaned up. + mDexUseManager.cleanup(); + } + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, BASE_APK)) + .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */), + DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */), + DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)); + + assertThat(mDexUseManager.getPrimaryDexLoaders(OWNING_PKG_NAME, SPLIT_APK)) + .containsExactly(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)); + + assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l); + } + + @Test + public void testSecondaryDexOwned() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("arm64-v8a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)), + false /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC"); + } + + @Test + public void testSecondaryDexOwnedIsolated() { + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mDeDir + "/foo.apk", "CLC")); + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mDeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("arm64-v8a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC"); + } + + @Test + public void testSecondaryDexOthers() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("armeabi-v7a"), + Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC"); + } + + @Test + public void testSecondaryDexUnsupportedClc() { + mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME, + Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT)); + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT, Set.of("armeabi-v7a"), + Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + assertThat(dexInfoList.get(0).classLoaderContext()).isNull(); + } + + @Test + public void testSecondaryDexVariableClc() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC2")); + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS, + Set.of("arm64-v8a", "armeabi-v7a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */), + DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + assertThat(dexInfoList.get(0).classLoaderContext()).isNull(); + } + + /** Checks that it ignores and dedups things correctly. */ + @Test + public void testSecondaryDexMultipleEntries() throws Exception { + verifySecondaryDexMultipleEntries( + false /*saveAndLoad */, false /* shutdown */, false /* cleanup */); + } + + /** Checks that it saves data after some time has passed and loads data correctly. */ + @Test + public void testSecondaryDexMultipleEntriesPersisted() throws Exception { + verifySecondaryDexMultipleEntries( + true /*saveAndLoad */, false /* shutdown */, false /* cleanup */); + } + + /** Checks that it saves data when the device is being shutdown and loads data correctly. */ + @Test + public void testSecondaryDexMultipleEntriesPersistedDueToShutdown() throws Exception { + verifySecondaryDexMultipleEntries( + true /*saveAndLoad */, true /* shutdown */, false /* cleanup */); + } + + /** Checks that it doesn't accidentally cleanup any entry that is needed. */ + @Test + public void testSecondaryDexMultipleEntriesSurviveCleanup() throws Exception { + verifySecondaryDexMultipleEntries( + false /*saveAndLoad */, false /* shutdown */, true /* cleanup */); + } + + private void verifySecondaryDexMultipleEntries( + boolean saveAndLoad, boolean shutdown, boolean cleanup) throws Exception { + when(mInjector.getCurrentTimeMillis()).thenReturn(1000l); + + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + // These should be ignored. + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of("/some/non-existing.apk", "CLC")); + + // Some of these should be deduped. + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, + Map.of(mCeDir + "/foo.apk", "CLC", mCeDir + "/bar.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, + Map.of(mCeDir + "/foo.apk", "UpdatedCLC", mCeDir + "/bar.apk", "UpdatedCLC")); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "UpdatedCLC")); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "DifferentCLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "UpdatedDifferentCLC")); + + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, + Map.of(mCeDir + "/baz.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT)); + + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + when(mInjector.getCurrentTimeMillis()).thenReturn(2000l); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, + Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT)); + + if (saveAndLoad) { + if (shutdown) { + mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class)); + } else { + // MockClock runs tasks synchronously. + mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); + } + mDexUseManager = new DexUseManagerLocal(mInjector); + } + + if (cleanup) { + // Nothing should be cleaned up. + mDexUseManager.cleanup(); + } + + List<? extends SecondaryDexInfo> dexInfoList = + mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME); + assertThat(dexInfoList) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "UpdatedCLC", Set.of("arm64-v8a", "armeabi-v7a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, + false /* isolatedProcess */), + DexLoader.create(OWNING_PKG_NAME, + true /* isolatedProcess */), + DexLoader.create(LOADING_PKG_NAME, + false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic), + DetailedSecondaryDexInfo.create(mCeDir + "/bar.apk", mUserHandle, + SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS, + Set.of("arm64-v8a", "armeabi-v7a"), + Set.of(DexLoader.create( + OWNING_PKG_NAME, false /* isolatedProcess */), + DexLoader.create( + LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic), + DetailedSecondaryDexInfo.create(mCeDir + "/baz.apk", mUserHandle, + SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT, + Set.of("arm64-v8a"), + Set.of(DexLoader.create( + OWNING_PKG_NAME, false /* isolatedProcess */)), + false /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + + assertThat(mDexUseManager.getSecondaryDexContainerFileUseInfo(OWNING_PKG_NAME)) + .containsExactly(DexContainerFileUseInfo.create(mCeDir + "/foo.apk", mUserHandle, + Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)), + DexContainerFileUseInfo.create(mCeDir + "/bar.apk", mUserHandle, + Set.of(OWNING_PKG_NAME, LOADING_PKG_NAME)), + DexContainerFileUseInfo.create( + mCeDir + "/baz.apk", mUserHandle, Set.of(OWNING_PKG_NAME))); + + assertThat(mDexUseManager.getPackageLastUsedAtMs(OWNING_PKG_NAME)).isEqualTo(2000l); + } + + @Test + public void testFilteredDetailedSecondaryDexPublic() throws Exception { + when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("arm64-v8a", "armeabi-v7a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */), + DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, true /* isDexFilePublic */)); + } + + @Test + public void testFilteredDetailedSecondaryDexPrivate() throws Exception { + when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("arm64-v8a"), + Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)), + false /* isUsedByOtherApps */, false /* isDexFilePublic */)); + } + + @Test + public void testFilteredDetailedSecondaryDexFilteredDueToVisibility() throws Exception { + when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + when(Process.isIsolatedUid(anyInt())).thenReturn(true); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty(); + } + + @Test + public void testFilteredDetailedSecondaryDexFilteredDueToNotFound() throws Exception { + when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND); + + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty(); + } + + @Test + public void testCleanup() throws Exception { + PackageState pkgState = createPackageState("com.example.deletedpackage", "arm64-v8a"); + addPackage("com.example.deletedpackage", pkgState); + lenient() + .when(mArtd.getDexFileVisibility("/data/app/com.example.deletedpackage/base.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + lenient() + .when(mArtd.getDexFileVisibility(BASE_APK)) + .thenReturn(FileVisibility.OTHER_READABLE); + // Simulate that a package loads its own dex file and another package's dex file. + mDexUseManager.notifyDexContainersLoaded(mSnapshot, "com.example.deletedpackage", + Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC", BASE_APK, "CLC")); + // Simulate that another package loads this package's dex file. + mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME, + Map.of("/data/app/com.example.deletedpackage/base.apk", "CLC")); + // Simulate that the package is then deleted. + removePackage("com.example.deletedpackage"); + + // Simulate that a primary dex file is loaded and then deleted. + lenient() + .when(mArtd.getDexFileVisibility(SPLIT_APK)) + .thenReturn(FileVisibility.OTHER_READABLE); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(SPLIT_APK, "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(SPLIT_APK, "CLC")); + lenient().when(mArtd.getDexFileVisibility(SPLIT_APK)).thenReturn(FileVisibility.NOT_FOUND); + + // Simulate that a secondary dex file is loaded and then deleted. + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")) + .thenReturn(FileVisibility.NOT_FOUND); + + // Create an entry that should be kept. + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/bar.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/bar.apk", "CLC")); + + // Simulate that a secondary dex file is loaded by another package and then made private. + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/baz.apk", "CLC")); + lenient() + .when(mArtd.getDexFileVisibility(mCeDir + "/baz.apk")) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + // Simulate that all the files of a package are deleted. The whole container entry of the + // package should be cleaned up, though the package still exists. + lenient() + .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME, + Map.of("/data/app/" + LOADING_PKG_NAME + "/base.apk", "CLC")); + lenient() + .when(mArtd.getDexFileVisibility("/data/app/" + LOADING_PKG_NAME + "/base.apk")) + .thenReturn(FileVisibility.NOT_FOUND); + + // Run cleanup. + mDexUseManager.cleanup(); + + // Save. + mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); + + // Check that the entries are removed from the proto. Normally, we should check the return + // values of the public get methods instead of checking the raw proto. However, here we want + // to make sure that the container entries are cleaned up when they are empty so that they + // don't cost extra memory or storage. + // Note that every repeated field must not contain more than one entry, to keep the + // textproto deterministic. + DexUseProto proto; + try (InputStream in = new FileInputStream(mTempFile.getPath())) { + proto = DexUseProto.parseFrom(in); + } + String textproto = proto.toString(); + // Remove the first line, which is an auto-generated comment. + textproto = textproto.substring(textproto.indexOf('\n') + 1).trim(); + assertThat(textproto).isEqualTo("package_dex_use {\n" + + " owning_package_name: \"com.example.owningpackage\"\n" + + " secondary_dex_use {\n" + + " dex_file: \"/data/user/0/com.example.owningpackage/bar.apk\"\n" + + " record {\n" + + " abi_name: \"arm64-v8a\"\n" + + " class_loader_context: \"CLC\"\n" + + " last_used_at_ms: 0\n" + + " loading_package_name: \"com.example.owningpackage\"\n" + + " }\n" + + " user_id {\n" + + " }\n" + + " }\n" + + "}"); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnknownPackage() { + mDexUseManager.notifyDexContainersLoaded(mSnapshot, "bogus", Map.of(BASE_APK, "CLC")); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyMap() { + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, Map.of()); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullKey() { + var map = new HashMap<String, String>(); + map.put(null, "CLC"); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map); + } + + @Test(expected = IllegalArgumentException.class) + public void testNonAbsoluteKey() { + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, OWNING_PKG_NAME, Map.of("a/b.jar", "CLC")); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullValue() { + var map = new HashMap<String, String>(); + map.put(mCeDir + "/foo.apk", null); + mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME, map); + } + + @Test + public void testFileNotFound() { + // It should fail to load the file. + when(mInjector.getFilename()).thenReturn("/nonexisting/file"); + mDexUseManager = new DexUseManagerLocal(mInjector); + + // Add some arbitrary data to see if it works fine after a failed load. + mDexUseManager.notifyDexContainersLoaded( + mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC")); + + assertThat(mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME)) + .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, + "CLC", Set.of("armeabi-v7a"), + Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)), + true /* isUsedByOtherApps */, mDefaultIsDexFilePublic)); + } + + private AndroidPackage createPackage(String packageName) { + AndroidPackage pkg = mock(AndroidPackage.class); + lenient().when(pkg.getStorageUuid()).thenReturn(StorageManager.UUID_DEFAULT); + + var baseSplit = mock(AndroidPackageSplit.class); + lenient().when(baseSplit.getPath()).thenReturn("/data/app/" + packageName + "/base.apk"); + lenient().when(baseSplit.isHasCode()).thenReturn(true); + lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName()); + + var split0 = mock(AndroidPackageSplit.class); + lenient().when(split0.getName()).thenReturn("split_0"); + lenient().when(split0.getPath()).thenReturn("/data/app/" + packageName + "/split_0.apk"); + lenient().when(split0.isHasCode()).thenReturn(true); + + lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0)); + + return pkg; + } + + private PackageState createPackageState(String packageName, String primaryAbi) { + PackageState pkgState = mock(PackageState.class); + lenient().when(pkgState.getPackageName()).thenReturn(packageName); + AndroidPackage pkg = createPackage(packageName); + lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg); + lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi); + return pkgState; + } + + private void addPackage(String packageName, PackageState pkgState) { + lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(pkgState); + mPackageStates.put(packageName, pkgState); + } + + private void removePackage(String packageName) { + lenient().when(mSnapshot.getPackageState(packageName)).thenReturn(null); + mPackageStates.remove(packageName); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java new file mode 100644 index 0000000000..f08035dcee --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java @@ -0,0 +1,859 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.DexoptResultStatus; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.apphibernation.AppHibernationManager; +import android.os.CancellationSignal; +import android.os.PowerManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.pm.PackageStateModulesUtils; +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.Config; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.model.OperationProgress; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.SharedLibrary; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DexoptHelperTest { + private static final String PKG_NAME_FOO = "com.example.foo"; + private static final String PKG_NAME_BAR = "com.example.bar"; + private static final String PKG_NAME_LIB1 = "com.example.lib1"; + private static final String PKG_NAME_LIB2 = "com.example.lib2"; + private static final String PKG_NAME_LIB3 = "com.example.lib3"; + private static final String PKG_NAME_LIB4 = "com.example.lib4"; + private static final String PKG_NAME_LIBBAZ = "com.example.libbaz"; + + @Rule + public StaticMockitoRule mockitoRule = new StaticMockitoRule(PackageStateModulesUtils.class); + + @Mock private DexoptHelper.Injector mInjector; + @Mock private PrimaryDexopter mPrimaryDexopter; + @Mock private SecondaryDexopter mSecondaryDexopter; + @Mock private AppHibernationManager mAhm; + @Mock private PowerManager mPowerManager; + @Mock private PowerManager.WakeLock mWakeLock; + @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; + private PackageState mPkgStateFoo; + private PackageState mPkgStateBar; + private PackageState mPkgStateLib1; + private PackageState mPkgStateLib2; + private PackageState mPkgStateLib4; + private PackageState mPkgStateLibbaz; + private AndroidPackage mPkgFoo; + private AndroidPackage mPkgBar; + private AndroidPackage mPkgLib1; + private AndroidPackage mPkgLib2; + private AndroidPackage mPkgLib4; + private AndroidPackage mPkgLibbaz; + private CancellationSignal mCancellationSignal; + private ExecutorService mExecutor; + private List<DexContainerFileDexoptResult> mPrimaryResults; + private List<DexContainerFileDexoptResult> mSecondaryResults; + private Config mConfig; + private DexoptParams mParams; + private List<String> mRequestedPackages; + private DexoptHelper mDexoptHelper; + + @Before + public void setUp() throws Exception { + lenient() + .when(mPowerManager.newWakeLock(eq(PowerManager.PARTIAL_WAKE_LOCK), any())) + .thenReturn(mWakeLock); + + lenient().when(mAhm.isHibernatingGlobally(any())).thenReturn(false); + lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(true); + + mCancellationSignal = new CancellationSignal(); + mExecutor = Executors.newSingleThreadExecutor(); + mConfig = new Config(); + + preparePackagesAndLibraries(); + + mPrimaryResults = + createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */, + DexoptResult.DEXOPT_PERFORMED /* status2 */); + mSecondaryResults = createResults("/data/user_de/0/foo/foo.apk", + DexoptResult.DEXOPT_PERFORMED /* status1 */, + DexoptResult.DEXOPT_PERFORMED /* status2 */); + + lenient() + .when(mInjector.getPrimaryDexopter(any(), any(), any(), any())) + .thenReturn(mPrimaryDexopter); + lenient().when(mPrimaryDexopter.dexopt()).thenReturn(mPrimaryResults); + + lenient() + .when(mInjector.getSecondaryDexopter(any(), any(), any(), any())) + .thenReturn(mSecondaryDexopter); + lenient().when(mSecondaryDexopter.dexopt()).thenReturn(mSecondaryResults); + + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAhm); + lenient().when(mInjector.getPowerManager()).thenReturn(mPowerManager); + lenient().when(mInjector.getConfig()).thenReturn(mConfig); + + mDexoptHelper = new DexoptHelper(mInjector); + } + + @After + public void tearDown() { + mExecutor.shutdown(); + } + + @Test + public void testDexopt() throws Exception { + // Only package libbaz fails. + var failingPrimaryDexopter = mock(PrimaryDexopter.class); + List<DexContainerFileDexoptResult> partialFailureResults = + createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */, + DexoptResult.DEXOPT_FAILED /* status2 */); + lenient().when(failingPrimaryDexopter.dexopt()).thenReturn(partialFailureResults); + when(mInjector.getPrimaryDexopter(same(mPkgStateLibbaz), any(), any(), any())) + .thenReturn(failingPrimaryDexopter); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile"); + assertThat(result.getReason()).isEqualTo("install"); + assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_FAILED); + + // The requested packages must come first. + assertThat(result.getPackageDexoptResults()).hasSize(6); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_FAILED, + List.of(partialFailureResults, mSecondaryResults)); + checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + + // The order matters. It should acquire the wake lock only once, at the beginning, and + // release the wake lock at the end. When running in a single thread, it should dexopt + // primary dex files and the secondary dex files together for each package, and it should + // dexopt requested packages, in the given order, and then dexopt dependencies. + InOrder inOrder = inOrder(mInjector, mWakeLock); + inOrder.verify(mWakeLock).setWorkSource(any()); + inOrder.verify(mWakeLock).acquire(anyLong()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateFoo), same(mPkgFoo), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateFoo), same(mPkgFoo), same(mParams), any()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateBar), same(mPkgBar), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateBar), same(mPkgBar), same(mParams), any()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateLib1), same(mPkgLib1), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateLib1), same(mPkgLib1), same(mParams), any()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateLib2), same(mPkgLib2), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateLib2), same(mPkgLib2), same(mParams), any()); + inOrder.verify(mInjector).getPrimaryDexopter( + same(mPkgStateLib4), same(mPkgLib4), same(mParams), any()); + inOrder.verify(mInjector).getSecondaryDexopter( + same(mPkgStateLib4), same(mPkgLib4), same(mParams), any()); + inOrder.verify(mWakeLock).release(); + + verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */); + + verifyNoMoreInteractions(mWakeLock); + } + + @Test + public void testDexoptNoDependencies() throws Exception { + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getPackageDexoptResults()).hasSize(3); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + + verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */); + } + + @Test + public void testDexoptPrimaryOnly() throws Exception { + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getPackageDexoptResults()).hasSize(6); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + + verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */); + } + + @Test + public void testDexoptPrimaryOnlyNoDependencies() throws Exception { + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(0, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getPackageDexoptResults()).hasSize(3); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults)); + + verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */); + } + + @Test + public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception { + when(mPrimaryDexopter.dexopt()).thenAnswer(invocation -> { + mCancellationSignal.cancel(); + return mPrimaryResults; + }); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_CANCELLED); + + assertThat(result.getPackageDexoptResults()).hasSize(6); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_CANCELLED, + List.of(mPrimaryResults)); + checkPackageResult( + result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_CANCELLED, List.of()); + checkPackageResult( + result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_CANCELLED, List.of()); + checkPackageResult( + result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_CANCELLED, List.of()); + checkPackageResult( + result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_CANCELLED, List.of()); + checkPackageResult( + result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_CANCELLED, List.of()); + + verify(mInjector).getPrimaryDexopter( + same(mPkgStateFoo), same(mPkgFoo), same(mParams), any()); + + verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */); + } + + // This test verifies that every child thread can register its own listener on the cancellation + // signal through `setOnCancelListener` (i.e., the listeners don't overwrite each other). + @Test + public void testDexoptCancelledDuringDex2oatInvocationsMultiThreaded() throws Exception { + final int NUM_PACKAGES = 6; + final long TIMEOUT_SEC = 10; + var dexoptStarted = new Semaphore(0); + var dexoptCancelled = new Semaphore(0); + + when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> { + var cancellationSignal = invocation.<CancellationSignal>getArgument(3); + var dexopter = mock(PrimaryDexopter.class); + when(dexopter.dexopt()).thenAnswer(innerInvocation -> { + // Simulate that the child thread registers its own listener. + var isListenerCalled = new AtomicBoolean(false); + cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true)); + + dexoptStarted.release(); + assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + // Verify that the listener is called. + assertThat(isListenerCalled.get()).isTrue(); + + return mPrimaryResults; + }); + return dexopter; + }); + + ExecutorService dexoptExecutor = Executors.newFixedThreadPool(NUM_PACKAGES); + Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> { + return mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor); + }); + + try { + // Wait for all dexopt operations to start. + for (int i = 0; i < NUM_PACKAGES; i++) { + assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + } + + mCancellationSignal.cancel(); + + for (int i = 0; i < NUM_PACKAGES; i++) { + dexoptCancelled.release(); + } + } finally { + dexoptExecutor.shutdown(); + Utils.getFuture(future); + } + } + + // This test verifies that dexopt operation on the current thread can be cancelled. + @Test + public void testDexoptCancelledDuringDex2oatInvocationsOnCurrentThread() throws Exception { + final long TIMEOUT_SEC = 10; + var dexoptStarted = new Semaphore(0); + var dexoptCancelled = new Semaphore(0); + + when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> { + var cancellationSignal = invocation.<CancellationSignal>getArgument(3); + var dexopter = mock(PrimaryDexopter.class); + when(dexopter.dexopt()).thenAnswer(innerInvocation -> { + if (cancellationSignal.isCanceled()) { + return mPrimaryResults; + } + + var isListenerCalled = new AtomicBoolean(false); + cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true)); + + dexoptStarted.release(); + assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + // Verify that the listener is called. + assertThat(isListenerCalled.get()).isTrue(); + + return mPrimaryResults; + }); + return dexopter; + }); + + // Use the current thread (the one in ForkJoinPool). + Executor dexoptExecutor = Runnable::run; + Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> { + return mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor); + }); + + try { + // Only one dexopt operation should start. + assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + mCancellationSignal.cancel(); + + dexoptCancelled.release(); + } finally { + Utils.getFuture(future); + } + } + + @Test + public void testDexoptNotDexoptable() throws Exception { + when(PackageStateModulesUtils.isDexoptable(mPkgStateFoo)).thenReturn(false); + + mRequestedPackages = List.of(PKG_NAME_FOO); + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED); + assertThat(result.getPackageDexoptResults()).hasSize(1); + checkPackageResult( + result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of()); + + verifyNoDexopt(); + } + + @Test + public void testDexoptLibraryNotDexoptable() throws Exception { + when(PackageStateModulesUtils.isDexoptable(mPkgStateLib1)).thenReturn(false); + + mRequestedPackages = List.of(PKG_NAME_FOO); + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED); + assertThat(result.getPackageDexoptResults()).hasSize(1); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + + verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 1 /* expectedSecondaryTimes */); + } + + @Test + public void testDexoptIsHibernating() throws Exception { + lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true); + + mRequestedPackages = List.of(PKG_NAME_FOO); + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED); + checkPackageResult( + result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of()); + + verifyNoDexopt(); + } + + @Test + public void testDexoptIsHibernatingButOatArtifactDeletionDisabled() throws Exception { + lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true); + lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(result.getPackageDexoptResults()).hasSize(6); + checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED, + List.of(mPrimaryResults, mSecondaryResults)); + } + + @Test + public void testDexoptAlwaysReleasesWakeLock() throws Exception { + when(mPrimaryDexopter.dexopt()).thenThrow(IllegalStateException.class); + + try { + mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + } catch (Exception ignored) { + } + + verify(mWakeLock).release(); + } + + @Test(expected = IllegalArgumentException.class) + public void testDexoptPackageNotFound() throws Exception { + when(mSnapshot.getPackageState(any())).thenReturn(null); + + mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + verifyNoDexopt(); + } + + @Test(expected = IllegalArgumentException.class) + public void testDexoptNoPackage() throws Exception { + lenient().when(mPkgStateFoo.getAndroidPackage()).thenReturn(null); + + mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + verifyNoDexopt(); + } + + @Test + public void testDexoptSplit() throws Exception { + mRequestedPackages = List.of(PKG_NAME_FOO); + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + + mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + } + + @Test + public void testDexoptSplitNotFound() throws Exception { + mRequestedPackages = List.of(PKG_NAME_FOO); + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_bogus") + .build(); + + assertThrows(IllegalArgumentException.class, () -> { + mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + }); + } + + @Test + public void testCallbacks() throws Exception { + List<DexoptResult> list1 = new ArrayList<>(); + mConfig.addDexoptDoneCallback( + false /* onlyIncludeUpdates */, Runnable::run, result -> list1.add(result)); + + List<DexoptResult> list2 = new ArrayList<>(); + mConfig.addDexoptDoneCallback( + false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result)); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(list1).containsExactly(result); + assertThat(list2).containsExactly(result); + } + + @Test + public void testCallbackRemoved() throws Exception { + List<DexoptResult> list1 = new ArrayList<>(); + DexoptDoneCallback callback1 = result -> list1.add(result); + mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback1); + + List<DexoptResult> list2 = new ArrayList<>(); + mConfig.addDexoptDoneCallback( + false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result)); + + mConfig.removeDexoptDoneCallback(callback1); + + DexoptResult result = mDexoptHelper.dexopt( + mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor); + + assertThat(list1).isEmpty(); + assertThat(list2).containsExactly(result); + } + + @Test(expected = IllegalStateException.class) + public void testCallbackAlreadyAdded() throws Exception { + List<DexoptResult> list = new ArrayList<>(); + DexoptDoneCallback callback = result -> list.add(result); + mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback); + mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback); + } + + // Tests `addDexoptDoneCallback` with `onlyIncludeUpdates` being true and false. + @Test + public void testCallbackWithFailureResults() throws Exception { + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(0, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + // This list should collect all results. + List<DexoptResult> listAll = new ArrayList<>(); + mConfig.addDexoptDoneCallback( + false /* onlyIncludeUpdates */, Runnable::run, result -> listAll.add(result)); + + // This list should only collect results that have updates. + List<DexoptResult> listOnlyIncludeUpdates = new ArrayList<>(); + mConfig.addDexoptDoneCallback(true /* onlyIncludeUpdates */, Runnable::run, + result -> listOnlyIncludeUpdates.add(result)); + + // Dexopt partially fails on package "foo". + List<DexContainerFileDexoptResult> partialFailureResults = + createResults("/data/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */, + DexoptResult.DEXOPT_FAILED /* status2 */); + var fooPrimaryDexopter = mock(PrimaryDexopter.class); + when(mInjector.getPrimaryDexopter(same(mPkgStateFoo), any(), any(), any())) + .thenReturn(fooPrimaryDexopter); + when(fooPrimaryDexopter.dexopt()).thenReturn(partialFailureResults); + + // Dexopt totally fails on package "bar". + List<DexContainerFileDexoptResult> totalFailureResults = + createResults("/data/app/bar/base.apk", DexoptResult.DEXOPT_FAILED /* status1 */, + DexoptResult.DEXOPT_FAILED /* status2 */); + var barPrimaryDexopter = mock(PrimaryDexopter.class); + when(mInjector.getPrimaryDexopter(same(mPkgStateBar), any(), any(), any())) + .thenReturn(barPrimaryDexopter); + when(barPrimaryDexopter.dexopt()).thenReturn(totalFailureResults); + + DexoptResult resultWithSomeUpdates = mDexoptHelper.dexopt(mSnapshot, + List.of(PKG_NAME_FOO, PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor); + DexoptResult resultWithNoUpdates = mDexoptHelper.dexopt( + mSnapshot, List.of(PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor); + + assertThat(listAll).containsExactly(resultWithSomeUpdates, resultWithNoUpdates); + + assertThat(listOnlyIncludeUpdates).hasSize(1); + assertThat(listOnlyIncludeUpdates.get(0) + .getPackageDexoptResults() + .stream() + .map(PackageDexoptResult::getPackageName) + .collect(Collectors.toList())) + .containsExactly(PKG_NAME_FOO); + } + + @Test + public void testProgressCallback() throws Exception { + mParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX, + ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) + .build(); + + // Delay the executor to verify that the commands passed to the executor are not bound to + // changing variables. + var progressCallbackExecutor = new DelayedExecutor(); + Consumer<OperationProgress> progressCallback = mock(Consumer.class); + + mDexoptHelper.dexopt(mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor, + progressCallbackExecutor, progressCallback); + + progressCallbackExecutor.runAll(); + + InOrder inOrder = inOrder(progressCallback); + inOrder.verify(progressCallback) + .accept(eq(OperationProgress.create(0 /* current */, 3 /* total */))); + inOrder.verify(progressCallback) + .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */))); + inOrder.verify(progressCallback) + .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */))); + inOrder.verify(progressCallback) + .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */))); + } + + private AndroidPackage createPackage(boolean multiSplit) { + AndroidPackage pkg = mock(AndroidPackage.class); + + var baseSplit = mock(AndroidPackageSplit.class); + + if (multiSplit) { + var split0 = mock(AndroidPackageSplit.class); + lenient().when(split0.getName()).thenReturn("split_0"); + + lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0)); + } else { + lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit)); + } + + return pkg; + } + + private PackageState createPackageState( + String packageName, List<SharedLibrary> deps, boolean multiSplit) { + PackageState pkgState = mock(PackageState.class); + lenient().when(pkgState.getPackageName()).thenReturn(packageName); + lenient().when(pkgState.getAppId()).thenReturn(12345); + lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(deps); + AndroidPackage pkg = createPackage(multiSplit); + lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg); + lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(true); + return pkgState; + } + + private SharedLibrary createLibrary( + String libraryName, String packageName, List<SharedLibrary> deps) { + SharedLibrary library = mock(SharedLibrary.class); + lenient().when(library.getName()).thenReturn(libraryName); + lenient().when(library.getPackageName()).thenReturn(packageName); + lenient().when(library.getDependencies()).thenReturn(deps); + lenient().when(library.isNative()).thenReturn(false); + return library; + } + + private void preparePackagesAndLibraries() { + // Dependency graph: + // foo bar + // | | + // lib1a (lib1) lib1b (lib1) lib1c (lib1) + // / \ / \ | + // / \ / \ | + // libbaz (libbaz) lib2 (lib2) lib4 (lib4) lib3 (lib3) + // + // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1". + + mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ); + + // The native library is not dexoptable. + SharedLibrary libNative = createLibrary("libnative", "com.example.libnative", List.of()); + lenient().when(libNative.isNative()).thenReturn(true); + + SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of()); + SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of()); + SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of()); + SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of()); + SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2)); + SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, libNative, lib4)); + SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3)); + + mPkgStateFoo = + createPackageState(PKG_NAME_FOO, List.of(lib1a, libNative), true /* multiSplit */); + mPkgFoo = mPkgStateFoo.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo); + + mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b), false /* multiSplit */); + mPkgBar = mPkgStateBar.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar); + + mPkgStateLib1 = createPackageState( + PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4), false /* multiSplit */); + mPkgLib1 = mPkgStateLib1.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1); + + mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of(), false /* multiSplit */); + mPkgLib2 = mPkgStateLib2.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2); + + // This should not be considered as a transitive dependency of any requested package, even + // though it is a dependency of package "lib1". + PackageState pkgStateLib3 = + createPackageState(PKG_NAME_LIB3, List.of(), false /* multiSplit */); + lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3); + + mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of(), false /* multiSplit */); + mPkgLib4 = mPkgStateLib4.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4); + + mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of(), false /* multiSplit */); + mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage(); + lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz); + } + + private void verifyNoDexopt() { + verify(mInjector, never()).getPrimaryDexopter(any(), any(), any(), any()); + verify(mInjector, never()).getSecondaryDexopter(any(), any(), any(), any()); + } + + private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) { + verify(mInjector, times(expectedPrimaryTimes)) + .getPrimaryDexopter(any(), any(), any(), any()); + verify(mInjector, times(expectedSecondaryTimes)) + .getSecondaryDexopter(any(), any(), any(), any()); + } + + private List<DexContainerFileDexoptResult> createResults( + String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2) { + return List.of(DexContainerFileDexoptResult.create(dexPath, true /* isPrimaryAbi */, + "arm64-v8a", "verify", status1, 100 /* dex2oatWallTimeMillis */, + 400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, + 0 /* sizeBeforeBytes */, false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create(dexPath, false /* isPrimaryAbi */, + "armeabi-v7a", "verify", status2, 100 /* dex2oatWallTimeMillis */, + 400 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */)); + } + + private void checkPackageResult(DexoptResult result, int index, String packageName, + @DexoptResult.DexoptResultStatus int status, + List<List<DexContainerFileDexoptResult>> dexContainerFileDexoptResults) { + PackageDexoptResult packageResult = result.getPackageDexoptResults().get(index); + assertThat(packageResult.getPackageName()).isEqualTo(packageName); + assertThat(packageResult.getStatus()).isEqualTo(status); + assertThat(packageResult.getDexContainerFileDexoptResults()) + .containsExactlyElementsIn(dexContainerFileDexoptResults.stream() + .flatMap(r -> r.stream()) + .collect(Collectors.toList())); + } + + /** An executor that delays execution until `runAll` is called. */ + private static class DelayedExecutor implements Executor { + private List<Runnable> mCommands = new ArrayList<>(); + + public void execute(Runnable command) { + mCommands.add(command); + } + + public void runAll() { + for (Runnable command : mCommands) { + command.run(); + } + mCommands.clear(); + } + } +} diff --git a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java new file mode 100644 index 0000000000..3833249cb9 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.DexUseManagerLocal.DexLoader; +import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; +import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +import android.annotation.NonNull; +import android.os.SystemProperties; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.model.DexoptStatus; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DumpHelperTest { + private static final String PKG_NAME_FOO = "com.example1.foo"; + private static final String PKG_NAME_BAR = "com.example2.bar"; + + @Rule + public StaticMockitoRule mockitoRule = + new StaticMockitoRule(SystemProperties.class, Constants.class); + + @Mock private DumpHelper.Injector mInjector; + @Mock private ArtManagerLocal mArtManagerLocal; + @Mock private DexUseManagerLocal mDexUseManagerLocal; + @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; + @Mock private IArtd mArtd; + + private DumpHelper mDumpHelper; + + @Before + public void setUp() throws Exception { + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + + // No ISA translation. + lenient() + .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa.")))) + .thenReturn(""); + + lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal); + lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManagerLocal); + lenient().when(mInjector.getArtd()).thenReturn(mArtd); + + Map<String, PackageState> pkgStates = createPackageStates(); + lenient().when(mSnapshot.getPackageStates()).thenReturn(pkgStates); + for (var entry : pkgStates.entrySet()) { + lenient().when(mSnapshot.getPackageState(entry.getKey())).thenReturn(entry.getValue()); + } + + setUpForFoo(); + setUpForBar(); + + mDumpHelper = new DumpHelper(mInjector); + } + + @Test + public void testDump() throws Exception { + String expected = "[com.example1.foo]\n" + + " path: /data/app/foo/base.apk\n" + + " arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n" + + " [location is /data/app/foo/oat/arm64/base.odex]\n" + + " arm: [status=verify] [reason=install]\n" + + " [location is /data/app/foo/oat/arm/base.odex]\n" + + " path: /data/app/foo/split_0.apk\n" + + " arm64: [status=verify] [reason=vdex] [primary-abi]\n" + + " [location is primary.vdex in /data/app/foo/split_0.dm]\n" + + " arm: [status=verify] [reason=vdex]\n" + + " [location is primary.vdex in /data/app/foo/split_0.dm]\n" + + " used by other apps: [com.example2.bar (isa=arm)]\n" + + " known secondary dex files:\n" + + " /data/user_de/0/foo/1.apk (removed)\n" + + " arm: [status=run-from-apk] [reason=unknown]\n" + + " [location is unknown]\n" + + " class loader context: =VaryingClassLoaderContexts=\n" + + " com.example1.foo (isolated): CLC1\n" + + " com.example3.baz: CLC2\n" + + " used by other apps: [com.example1.foo (isolated) (isa=arm64), com.example3.baz (removed)]\n" + + " /data/user_de/0/foo/2.apk (public)\n" + + " arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n" + + " [location is /data/user_de/0/foo/oat/arm64/2.odex]\n" + + " arm: [status=verify] [reason=vdex]\n" + + " [location is /data/user_de/0/foo/oat/arm/2.vdex]\n" + + " class loader context: PCL[]\n" + + "[com.example2.bar]\n" + + " path: /data/app/bar/base.apk\n" + + " arm: [status=verify] [reason=install] [primary-abi]\n" + + " [location is /data/app/bar/oat/arm/base.odex]\n" + + " arm64: [status=verify] [reason=install]\n" + + " [location is /data/app/bar/oat/arm64/base.odex]\n"; + + var stringWriter = new StringWriter(); + mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot); + assertThat(stringWriter.toString()).isEqualTo(expected); + } + + private PackageState createPackageState(@NonNull String packageName, int appId, + boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) { + var pkgState = mock(PackageState.class); + lenient().when(pkgState.getPackageName()).thenReturn(packageName); + lenient().when(pkgState.getAppId()).thenReturn(appId); + lenient() + .when(pkgState.getAndroidPackage()) + .thenReturn(hasPackage ? mock(AndroidPackage.class) : null); + lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi); + lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn(secondaryAbi); + return pkgState; + } + + private Map<String, PackageState> createPackageStates() { + var pkgStates = new HashMap<String, PackageState>(); + pkgStates.put(PKG_NAME_FOO, + createPackageState(PKG_NAME_FOO, 10001 /* appId */, true /* hasPackage */, + "arm64-v8a", "armeabi-v7a")); + pkgStates.put(PKG_NAME_BAR, + createPackageState(PKG_NAME_BAR, 10003 /* appId */, true /* hasPackage */, + "armeabi-v7a", "arm64-v8a")); + // This should not be included in the output because it has a negative app id. + pkgStates.put("com.android.art", + createPackageState("com.android.art", -1 /* appId */, true /* hasPackage */, + "arm64-v8a", "armeabi-v7a")); + // This should not be included in the output because it does't have AndroidPackage. + pkgStates.put("com.example.null", + createPackageState("com.example.null", 10010 /* appId */, false /* hasPackage */, + "arm64-v8a", "armeabi-v7a")); + return pkgStates; + } + + private void setUpForFoo() throws Exception { + // The order of the primary dex files and the ABIs should be kept in the output. Secondary + // dex files should be reordered in lexicographical order. + var status = DexoptStatus.create(List.of( + DexContainerFileDexoptStatus.create("/data/app/foo/base.apk", + true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "speed-profile", "bg-dexopt", "/data/app/foo/oat/arm64/base.odex"), + DexContainerFileDexoptStatus.create("/data/app/foo/base.apk", + true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify", + "install", "/data/app/foo/oat/arm/base.odex"), + DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk", + true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", "verify", + "vdex", "primary.vdex in /data/app/foo/split_0.dm"), + DexContainerFileDexoptStatus.create("/data/app/foo/split_0.apk", + true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify", + "vdex", "primary.vdex in /data/app/foo/split_0.dm"), + DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk", + false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "speed-profile", "bg-dexopt", "/data/user_de/0/foo/oat/arm64/2.odex"), + DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk", + false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify", + "vdex", "/data/user_de/0/foo/oat/arm/2.vdex"), + DexContainerFileDexoptStatus.create("/data/user_de/0/foo/1.apk", + false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", + "run-from-apk", "unknown", "unknown"))); + + lenient() + .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_FOO))) + .thenReturn(status); + + // The output should not show "used by other apps:". + lenient() + .when(mDexUseManagerLocal.getPrimaryDexLoaders( + PKG_NAME_FOO, "/data/app/foo/base.apk")) + .thenReturn(Set.of()); + + // The output should not show "foo" in "used by other apps:". + lenient() + .when(mDexUseManagerLocal.getPrimaryDexLoaders( + PKG_NAME_FOO, "/data/app/foo/split_0.apk")) + .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */), + DexLoader.create(PKG_NAME_BAR, false /* isolatedProcess */))); + + var info1 = mock(SecondaryDexInfo.class); + lenient().when(info1.dexPath()).thenReturn("/data/user_de/0/foo/1.apk"); + lenient() + .when(info1.displayClassLoaderContext()) + .thenReturn(SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS); + + lenient() + .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO, + "/data/user_de/0/foo/1.apk", + DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */))) + .thenReturn("CLC1"); + lenient() + .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO, + "/data/user_de/0/foo/1.apk", + DexLoader.create("com.example3.baz", false /* isolatedProcess */))) + .thenReturn("CLC2"); + + var loaders = new HashSet<DexLoader>(); + // The output should show "foo" with "(isolated)" in "used by other apps:". + loaders.add(DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */)); + // The output should show "baz" with "(removed)" in "used by other apps:". + loaders.add(DexLoader.create("com.example3.baz", false /* isolatedProcess */)); + lenient().when(info1.loaders()).thenReturn(loaders); + + // The output should show the dex path with "(removed)". + lenient() + .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/1.apk")) + .thenReturn(FileVisibility.NOT_FOUND); + + var info2 = mock(SecondaryDexInfo.class); + lenient().when(info2.dexPath()).thenReturn("/data/user_de/0/foo/2.apk"); + lenient().when(info2.displayClassLoaderContext()).thenReturn("PCL[]"); + lenient() + .when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO, + "/data/user_de/0/foo/2.apk", + DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */))) + .thenReturn("PCL[]"); + // The output should not show "used by other apps:". + lenient() + .when(info2.loaders()) + .thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */))); + lenient() + .when(mArtd.getDexFileVisibility("/data/user_de/0/foo/2.apk")) + .thenReturn(FileVisibility.OTHER_READABLE); + + lenient() + .doReturn(List.of(info1, info2)) + .when(mDexUseManagerLocal) + .getSecondaryDexInfo(PKG_NAME_FOO); + } + + private void setUpForBar() { + // The order of the ABI should be kept in the output, despite that it's different from the + // order for package "foo". + // The output should not show "known secondary dex files:". + var status = DexoptStatus.create( + List.of(DexContainerFileDexoptStatus.create("/data/app/bar/base.apk", + true /* isPrimaryDex */, true /* isPrimaryAbi */, "armeabi-v7a", + "verify", "install", "/data/app/bar/oat/arm/base.odex"), + DexContainerFileDexoptStatus.create("/data/app/bar/base.apk", + true /* isPrimaryDex */, false /* isPrimaryAbi */, "arm64-v8a", + "verify", "install", "/data/app/bar/oat/arm64/base.odex"))); + + lenient() + .when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_BAR))) + .thenReturn(status); + + lenient() + .when(mDexUseManagerLocal.getPrimaryDexLoaders( + PKG_NAME_BAR, "/data/app/bar/base.apk")) + .thenReturn(Set.of()); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java new file mode 100644 index 0000000000..a54b3714cc --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo; +import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +import androidx.test.filters.SmallTest; + +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.SharedLibrary; + +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.DexClassLoader; +import dalvik.system.PathClassLoader; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class PrimaryDexUtilsTest { + @Before + public void setUp() {} + + @Test + public void testGetDexInfo() { + List<PrimaryDexInfo> infos = + PrimaryDexUtils.getDexInfo(createPackage(false /* isIsolatedSplitLoading */)); + checkBasicInfo(infos); + } + + @Test + public void testGetDetailedDexInfo() { + List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo( + createPackageState(), createPackage(false /* isIsolatedSplitLoading */)); + checkBasicInfo(infos); + + String sharedLibrariesContext = "{" + + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#" + + "PCL[library_3.jar]#" + + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}" + + "}"; + + assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext); + assertThat(infos.get(1).classLoaderContext()) + .isEqualTo("PCL[base.apk]" + sharedLibrariesContext); + assertThat(infos.get(2).classLoaderContext()).isEqualTo(null); + assertThat(infos.get(3).classLoaderContext()) + .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext); + assertThat(infos.get(4).classLoaderContext()) + .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]" + + sharedLibrariesContext); + } + + @Test + public void testGetDetailedDexInfoIsolated() { + List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo( + createPackageState(), createPackage(true /* isIsolatedSplitLoading */)); + checkBasicInfo(infos); + + String sharedLibrariesContext = "{" + + "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#" + + "PCL[library_3.jar]#" + + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}" + + "}"; + + assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext); + assertThat(infos.get(1).classLoaderContext()) + .isEqualTo("PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext); + assertThat(infos.get(2).classLoaderContext()).isEqualTo(null); + assertThat(infos.get(3).classLoaderContext()) + .isEqualTo("DLC[];PCL[base.apk]" + sharedLibrariesContext); + assertThat(infos.get(4).classLoaderContext()).isEqualTo("PCL[]"); + assertThat(infos.get(5).classLoaderContext()).isEqualTo("PCL[];PCL[split_3.apk]"); + } + + private <T extends PrimaryDexInfo> void checkBasicInfo(List<T> infos) { + assertThat(infos.get(0).dexPath()).isEqualTo("/data/app/foo/base.apk"); + assertThat(infos.get(0).hasCode()).isTrue(); + assertThat(infos.get(0).splitName()).isNull(); + + assertThat(infos.get(1).dexPath()).isEqualTo("/data/app/foo/split_0.apk"); + assertThat(infos.get(1).hasCode()).isTrue(); + assertThat(infos.get(1).splitName()).isEqualTo("split_0"); + + assertThat(infos.get(2).dexPath()).isEqualTo("/data/app/foo/split_1.apk"); + assertThat(infos.get(2).hasCode()).isFalse(); + assertThat(infos.get(2).splitName()).isEqualTo("split_1"); + + assertThat(infos.get(3).dexPath()).isEqualTo("/data/app/foo/split_2.apk"); + assertThat(infos.get(3).hasCode()).isTrue(); + assertThat(infos.get(3).splitName()).isEqualTo("split_2"); + + assertThat(infos.get(4).dexPath()).isEqualTo("/data/app/foo/split_3.apk"); + assertThat(infos.get(4).hasCode()).isTrue(); + assertThat(infos.get(4).splitName()).isEqualTo("split_3"); + + assertThat(infos.get(5).dexPath()).isEqualTo("/data/app/foo/split_4.apk"); + assertThat(infos.get(5).hasCode()).isTrue(); + assertThat(infos.get(5).splitName()).isEqualTo("split_4"); + } + + private AndroidPackage createPackage(boolean isIsolatedSplitLoading) { + AndroidPackage pkg = mock(AndroidPackage.class); + + var baseSplit = mock(AndroidPackageSplit.class); + lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk"); + lenient().when(baseSplit.isHasCode()).thenReturn(true); + lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName()); + + var split0 = mock(AndroidPackageSplit.class); + lenient().when(split0.getName()).thenReturn("split_0"); + lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk"); + lenient().when(split0.isHasCode()).thenReturn(true); + + var split1 = mock(AndroidPackageSplit.class); + lenient().when(split1.getName()).thenReturn("split_1"); + lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk"); + lenient().when(split1.isHasCode()).thenReturn(false); + + var split2 = mock(AndroidPackageSplit.class); + lenient().when(split2.getName()).thenReturn("split_2"); + lenient().when(split2.getPath()).thenReturn("/data/app/foo/split_2.apk"); + lenient().when(split2.isHasCode()).thenReturn(true); + + var split3 = mock(AndroidPackageSplit.class); + lenient().when(split3.getName()).thenReturn("split_3"); + lenient().when(split3.getPath()).thenReturn("/data/app/foo/split_3.apk"); + lenient().when(split3.isHasCode()).thenReturn(true); + + var split4 = mock(AndroidPackageSplit.class); + lenient().when(split4.getName()).thenReturn("split_4"); + lenient().when(split4.getPath()).thenReturn("/data/app/foo/split_4.apk"); + lenient().when(split4.isHasCode()).thenReturn(true); + + var splits = List.of(baseSplit, split0, split1, split2, split3, split4); + lenient().when(pkg.getSplits()).thenReturn(splits); + + if (isIsolatedSplitLoading) { + // split_0: PCL(PathClassLoader), depends on split_2. + // split_1: no code. + // split_2: DLC(DelegateLastClassLoader), depends on base. + // split_3: PCL(DexClassLoader), no dependency. + // split_4: PCL(null), depends on split_3. + lenient().when(split0.getClassLoaderName()).thenReturn(PathClassLoader.class.getName()); + lenient().when(split1.getClassLoaderName()).thenReturn(null); + lenient() + .when(split2.getClassLoaderName()) + .thenReturn(DelegateLastClassLoader.class.getName()); + lenient().when(split3.getClassLoaderName()).thenReturn(DexClassLoader.class.getName()); + lenient().when(split4.getClassLoaderName()).thenReturn(null); + + lenient().when(split0.getDependencies()).thenReturn(List.of(split2)); + lenient().when(split2.getDependencies()).thenReturn(List.of(baseSplit)); + lenient().when(split4.getDependencies()).thenReturn(List.of(split3)); + lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(true); + } else { + lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(false); + } + + return pkg; + } + + private PackageState createPackageState() { + PackageState pkgState = mock(PackageState.class); + + lenient().when(pkgState.getPackageName()).thenReturn("com.example.foo"); + + // Base depends on library 2, 3, 4. + // Library 2, 4 depends on library 1. + List<SharedLibrary> usesLibraryInfos = new ArrayList<>(); + + // The native library should not be added to the CLC. + SharedLibrary libraryNative = mock(SharedLibrary.class); + lenient().when(libraryNative.getAllCodePaths()).thenReturn(List.of("library_native.so")); + lenient().when(libraryNative.getDependencies()).thenReturn(null); + lenient().when(libraryNative.isNative()).thenReturn(true); + usesLibraryInfos.add(libraryNative); + + SharedLibrary library1 = mock(SharedLibrary.class); + lenient() + .when(library1.getAllCodePaths()) + .thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar")); + lenient().when(library1.getDependencies()).thenReturn(null); + lenient().when(library1.isNative()).thenReturn(false); + + SharedLibrary library2 = mock(SharedLibrary.class); + lenient().when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar")); + lenient().when(library2.getDependencies()).thenReturn(List.of(library1, libraryNative)); + lenient().when(library2.isNative()).thenReturn(false); + usesLibraryInfos.add(library2); + + SharedLibrary library3 = mock(SharedLibrary.class); + lenient().when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar")); + lenient().when(library3.getDependencies()).thenReturn(null); + lenient().when(library3.isNative()).thenReturn(false); + usesLibraryInfos.add(library3); + + SharedLibrary library4 = mock(SharedLibrary.class); + lenient().when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar")); + lenient().when(library4.getDependencies()).thenReturn(List.of(library1)); + lenient().when(library4.isNative()).thenReturn(false); + usesLibraryInfos.add(library4); + + lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(usesLibraryInfos); + + return pkgState; + } +} diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java new file mode 100644 index 0000000000..31ea915f07 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.AidlUtils.buildFsPermission; +import static com.android.server.art.AidlUtils.buildOutputArtifacts; +import static com.android.server.art.AidlUtils.buildPermissionSettings; +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.testing.TestingUtils.deepEq; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.pm.ApplicationInfo; +import android.os.Process; +import android.os.ServiceSpecificException; +import android.os.SystemProperties; + +import androidx.test.filters.SmallTest; + +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.testing.TestingUtils; + +import dalvik.system.DexFile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.ArgumentMatcher; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(Parameterized.class) +public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { + private DexoptParams mDexoptParams; + + private PrimaryDexopter mPrimaryDexopter; + + @Parameter(0) public Params mParams; + + @Parameters(name = "{0}") + public static Iterable<Params> data() { + List<Params> list = new ArrayList<>(); + Params params; + + // Baseline. + params = new Params(); + list.add(params); + + params = new Params(); + params.mRequestedCompilerFilter = "speed"; + params.mExpectedCompilerFilter = "speed"; + list.add(params); + + params = new Params(); + params.mIsSystem = true; + params.mExpectedIsInDalvikCache = true; + list.add(params); + + params = new Params(); + params.mIsSystem = true; + params.mIsUpdatedSystemApp = true; + list.add(params); + + params = new Params(); + params.mIsIncrementalFsPath = true; + params.mExpectedIsInDalvikCache = true; + list.add(params); + + params = new Params(); + params.mIsSystem = true; + params.mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED; + params.mExpectedIsInDalvikCache = true; + params.mExpectedIsHiddenApiPolicyEnabled = false; + list.add(params); + + params = new Params(); + params.mIsDebuggable = true; + params.mRequestedCompilerFilter = "speed"; + params.mExpectedCompilerFilter = "verify"; + params.mExpectedIsDebuggable = true; + list.add(params); + + params = new Params(); + params.mIsVmSafeMode = true; + params.mRequestedCompilerFilter = "speed"; + params.mExpectedCompilerFilter = "verify"; + list.add(params); + + params = new Params(); + params.mIsUseEmbeddedDex = true; + params.mRequestedCompilerFilter = "speed"; + params.mExpectedCompilerFilter = "verify"; + list.add(params); + + params = new Params(); + params.mAlwaysDebuggable = true; + params.mExpectedIsDebuggable = true; + list.add(params); + + params = new Params(); + params.mIsSystemUi = true; + params.mExpectedCompilerFilter = "speed"; + list.add(params); + + params = new Params(); + params.mIsLauncher = true; + params.mExpectedCompilerFilter = "speed-profile"; + list.add(params); + + params = new Params(); + params.mForce = true; + params.mShouldDowngrade = false; + params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + list.add(params); + + params = new Params(); + params.mForce = true; + params.mShouldDowngrade = true; + params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + list.add(params); + + params = new Params(); + params.mShouldDowngrade = true; + params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_WORSE; + list.add(params); + + params = new Params(); + // This should not change the result. + params.mSkipIfStorageLow = true; + list.add(params); + + return list; + } + + @Before + public void setUp() throws Exception { + super.setUp(); + + lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(mParams.mIsSystemUi); + lenient().when(mInjector.isLauncherPackage(any())).thenReturn(mParams.mIsLauncher); + + lenient() + .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean())) + .thenReturn(mParams.mAlwaysDebuggable); + + lenient().when(mPkg.isVmSafeMode()).thenReturn(mParams.mIsVmSafeMode); + lenient().when(mPkg.isDebuggable()).thenReturn(mParams.mIsDebuggable); + lenient().when(mPkg.getTargetSdkVersion()).thenReturn(123); + lenient() + .when(mPkgState.getHiddenApiEnforcementPolicy()) + .thenReturn(mParams.mHiddenApiEnforcementPolicy); + lenient().when(mPkg.isUseEmbeddedDex()).thenReturn(mParams.mIsUseEmbeddedDex); + lenient().when(mPkgState.isSystem()).thenReturn(mParams.mIsSystem); + lenient().when(mPkgState.isUpdatedSystemApp()).thenReturn(mParams.mIsUpdatedSystemApp); + + // Make all profile-related operations succeed so that "speed-profile" doesn't fall back to + // "verify". + lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(true); + lenient().when(mArtd.getProfileVisibility(any())).thenReturn(FileVisibility.OTHER_READABLE); + lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false); + + lenient().when(mArtd.isIncrementalFsPath(any())).thenReturn(mParams.mIsIncrementalFsPath); + + mDexoptParams = + new DexoptParams.Builder("install") + .setCompilerFilter(mParams.mRequestedCompilerFilter) + .setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE) + .setFlags(mParams.mForce ? ArtFlags.FLAG_FORCE : 0, ArtFlags.FLAG_FORCE) + .setFlags(mParams.mShouldDowngrade ? ArtFlags.FLAG_SHOULD_DOWNGRADE : 0, + ArtFlags.FLAG_SHOULD_DOWNGRADE) + .setFlags(mParams.mSkipIfStorageLow ? ArtFlags.FLAG_SKIP_IF_STORAGE_LOW : 0, + ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) + .build(); + + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + } + + @Test + public void testDexopt() throws Exception { + PermissionSettings permissionSettings = buildPermissionSettings( + buildFsPermission(Process.SYSTEM_UID /* uid */, Process.SYSTEM_UID /* gid */, + false /* isOtherReadable */, true /* isOtherExecutable */), + buildFsPermission(Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */, + true /* isOtherReadable */), + null /* seContext */); + + // No need to check `generateAppImage`. It is checked in `PrimaryDexopterTest`. + ArgumentMatcher<DexoptOptions> dexoptOptionsMatcher = options + -> options.compilationReason.equals("install") && options.targetSdkVersion == 123 + && options.debuggable == mParams.mExpectedIsDebuggable + && options.hiddenApiPolicyEnabled == mParams.mExpectedIsHiddenApiPolicyEnabled + && options.comments.equals( + String.format("app-version-name:%s,app-version-code:%d,art-version:%d", + APP_VERSION_NAME, APP_VERSION_CODE, ART_VERSION)); + + when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class)); + when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND); + + // The first one is normal. + doReturn(dexoptIsNeeded()) + .when(mArtd) + .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]", + mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger); + doReturn(createArtdDexoptResult(false /* cancelled */, 100 /* wallTimeMs */, + 400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */)) + .when(mArtd) + .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64", + mParams.mExpectedIsInDalvikCache, permissionSettings)), + eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"), + eq(mParams.mExpectedCompilerFilter), any() /* profile */, + isNull() /* inputVdex */, isNull() /* dmFile */, + eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any()); + + // The second one fails on `dexopt`. + doReturn(dexoptIsNeeded()) + .when(mArtd) + .getDexoptNeeded("/data/app/foo/base.apk", "arm", "PCL[]", + mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger); + doThrow(ServiceSpecificException.class) + .when(mArtd) + .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm", + mParams.mExpectedIsInDalvikCache, permissionSettings)), + eq("/data/app/foo/base.apk"), eq("arm"), eq("PCL[]"), + eq(mParams.mExpectedCompilerFilter), any() /* profile */, + isNull() /* inputVdex */, isNull() /* dmFile */, + eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any()); + + // The third one doesn't need dexopt. + doReturn(dexoptIsNotNeeded()) + .when(mArtd) + .getDexoptNeeded("/data/app/foo/split_0.apk", "arm64", "PCL[base.apk]", + mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger); + + // The fourth one is normal. + doReturn(dexoptIsNeeded()) + .when(mArtd) + .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]", + mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger); + doReturn(createArtdDexoptResult(false /* cancelled */, 200 /* wallTimeMs */, + 200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */)) + .when(mArtd) + .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm", + mParams.mExpectedIsInDalvikCache, permissionSettings)), + eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"), + eq(mParams.mExpectedCompilerFilter), any() /* profile */, + isNull() /* inputVdex */, isNull() /* dmFile */, + eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any()); + + assertThat(mPrimaryDexopter.dexopt()) + .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality()) + .containsExactly( + DexContainerFileDexoptResult.create("/data/app/foo/base.apk", + true /* isPrimaryAbi */, "arm64-v8a", + mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED, + 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */, + 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create("/data/app/foo/base.apk", + false /* isPrimaryAbi */, "armeabi-v7a", + mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk", + true /* isPrimaryAbi */, "arm64-v8a", + mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create("/data/app/foo/split_0.apk", + false /* isPrimaryAbi */, "armeabi-v7a", + mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED, + 200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */, + 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */)); + + // Verify that there are no more calls than the ones above. + verify(mArtd, times(3)) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + any()); + } + + private static class Params { + // Package information. + public boolean mIsSystem = false; + public boolean mIsUpdatedSystemApp = false; + public boolean mIsIncrementalFsPath = false; + public int mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED; + public boolean mIsVmSafeMode = false; + public boolean mIsDebuggable = false; + public boolean mIsSystemUi = false; + public boolean mIsLauncher = false; + public boolean mIsUseEmbeddedDex = false; + + // Options. + public String mRequestedCompilerFilter = "verify"; + public boolean mForce = false; + public boolean mShouldDowngrade = false; + public boolean mSkipIfStorageLow = false; + + // System properties. + public boolean mAlwaysDebuggable = false; + + // Expectations. + public String mExpectedCompilerFilter = "verify"; + public int mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + public boolean mExpectedIsInDalvikCache = false; + public boolean mExpectedIsDebuggable = false; + public boolean mExpectedIsHiddenApiPolicyEnabled = true; + + public String toString() { + return String.format("isSystem=%b," + + "isUpdatedSystemApp=%b," + + "isIncrementalFsPath=%b," + + "mHiddenApiEnforcementPolicy=%d," + + "isVmSafeMode=%b," + + "isDebuggable=%b," + + "isSystemUi=%b," + + "isLauncher=%b," + + "isUseEmbeddedDex=%b," + + "requestedCompilerFilter=%s," + + "force=%b," + + "shouldDowngrade=%b," + + "mSkipIfStorageLow=%b," + + "alwaysDebuggable=%b" + + " => " + + "targetCompilerFilter=%s," + + "expectedDexoptTrigger=%d," + + "expectedIsInDalvikCache=%b," + + "expectedIsDebuggable=%b," + + "expectedIsHiddenApiPolicyEnabled=%b", + mIsSystem, mIsUpdatedSystemApp, mIsIncrementalFsPath, + mHiddenApiEnforcementPolicy, mIsVmSafeMode, mIsDebuggable, mIsSystemUi, + mIsLauncher, mIsUseEmbeddedDex, mRequestedCompilerFilter, mForce, + mShouldDowngrade, mSkipIfStorageLow, mAlwaysDebuggable, mExpectedCompilerFilter, + mExpectedDexoptTrigger, mExpectedIsInDalvikCache, mExpectedIsDebuggable, + mExpectedIsHiddenApiPolicyEnabled); + } + } +} diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java new file mode 100644 index 0000000000..f02ebf0603 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.testing.TestingUtils.deepEq; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Process; +import android.os.ServiceSpecificException; +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.testing.TestingUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PrimaryDexopterTest extends PrimaryDexopterTestBase { + private final String mDexPath = "/data/app/foo/base.apk"; + private final ProfilePath mRefProfile = + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary"); + private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath); + private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath); + private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath); + private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary( + PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */); + private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary( + PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */); + + private final String mSplit0DexPath = "/data/app/foo/split_0.apk"; + private final ProfilePath mSplit0RefProfile = + AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split"); + + private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.COMPILER_FILTER_IS_SAME + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE + | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE + | DexoptTrigger.NEED_EXTRACTION; + + private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions(); + + private final ArtdDexoptResult mArtdDexoptResult = + createArtdDexoptResult(false /* cancelled */); + + private DexoptParams mDexoptParams = + new DexoptParams.Builder("install").setCompilerFilter("speed-profile").build(); + + private PrimaryDexopter mPrimaryDexopter; + + private List<ProfilePath> mUsedProfiles; + + @Before + public void setUp() throws Exception { + super.setUp(); + + // By default, none of the profiles are usable. + lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false); + lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false); + + // By default, no DM file exists. + lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND); + + // Dexopt is by default needed and successful. + lenient() + .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt())) + .thenReturn(dexoptIsNeeded()); + lenient() + .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), + any(), any())) + .thenReturn(mArtdDexoptResult); + + lenient() + .when(mArtd.createCancellationSignal()) + .thenReturn(mock(IArtdCancellationSignal.class)); + + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + mUsedProfiles = new ArrayList<>(); + } + + @Test + public void testDexoptInputVdex() throws Exception { + // null. + doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR)) + .when(mArtd) + .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt()); + doReturn(mArtdDexoptResult) + .when(mArtd) + .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(), + anyInt(), any(), any()); + + // ArtifactsPath, isInDalvikCache=true. + doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE)) + .when(mArtd) + .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt()); + doReturn(mArtdDexoptResult) + .when(mArtd) + .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(), + deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath( + mDexPath, "arm", true /* isInDalvikCache */))), + any(), anyInt(), any(), any()); + + // ArtifactsPath, isInDalvikCache=false. + doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX)) + .when(mArtd) + .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt()); + doReturn(mArtdDexoptResult) + .when(mArtd) + .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(), + deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath( + mSplit0DexPath, "arm64", false /* isInDalvikCache */))), + any(), anyInt(), any(), any()); + + // DexMetadataPath. + doReturn(dexoptIsNeeded(ArtifactsLocation.DM)) + .when(mArtd) + .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt()); + doReturn(mArtdDexoptResult) + .when(mArtd) + .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(), + anyInt(), any(), any()); + + mPrimaryDexopter.dexopt(); + } + + @Test + public void testDexoptDm() throws Exception { + lenient() + .when(mArtd.getDmFileVisibility(deepEq(mDmFile))) + .thenReturn(FileVisibility.OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + verify(mArtd, times(2)) + .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile), + anyInt(), + argThat(dexoptOptions + -> dexoptOptions.compilationReason.equals("install-dm")), + any()); + verify(mArtd, times(2)) + .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(), + anyInt(), + argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")), + any()); + } + + @Test + public void testDexoptUsesRefProfile() throws Exception { + makeProfileUsable(mRefProfile); + when(mArtd.getProfileVisibility(deepEq(mRefProfile))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + // Other profiles are also usable, but they shouldn't be used. + makeProfileUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + mPrimaryDexopter.dexopt(); + + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile, + false /* isOtherReadable */, true /* generateAppImage */); + + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile, + false /* isOtherReadable */, true /* generateAppImage */); + + // There is no profile for split 0, so it should fall back to "verify". + verify(mArtd).getDexoptNeeded( + eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger)); + checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "verify"); + + verify(mArtd).getDexoptNeeded( + eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger)); + checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "verify"); + + verifyProfileNotUsed(mPrebuiltProfile); + verifyProfileNotUsed(mDmProfile); + } + + @Test + public void testDexoptUsesPublicRefProfile() throws Exception { + // The ref profile is usable and public. + makeProfileUsable(mRefProfile); + when(mArtd.getProfileVisibility(deepEq(mRefProfile))) + .thenReturn(FileVisibility.OTHER_READABLE); + + // Other profiles are also usable, but they shouldn't be used. + makeProfileUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + mPrimaryDexopter.dexopt(); + + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile, + true /* isOtherReadable */, true /* generateAppImage */); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile, + true /* isOtherReadable */, true /* generateAppImage */); + + verifyProfileNotUsed(mPrebuiltProfile); + verifyProfileNotUsed(mDmProfile); + } + + @Test + public void testDexoptUsesPrebuiltProfile() throws Exception { + makeProfileNotUsable(mRefProfile); + makeProfileUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + mPrimaryDexopter.dexopt(); + + InOrder inOrder = inOrder(mArtd); + + inOrder.verify(mArtd).copyAndRewriteProfile( + deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath)); + + checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + + inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath)); + + verifyProfileNotUsed(mRefProfile); + verifyProfileNotUsed(mDmProfile); + } + + @Test + public void testDexoptMergesProfiles() throws Exception { + when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled); + when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled); + + when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true); + + makeProfileUsable(mRefProfile); + when(mArtd.getProfileVisibility(deepEq(mRefProfile))) + .thenReturn(FileVisibility.OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + InOrder inOrder = inOrder(mArtd); + + inOrder.verify(mArtd).mergeProfiles( + deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur( + 0 /* userId */, PKG_NAME, "primary"), + AidlUtils.buildProfilePathForPrimaryCur( + 2 /* userId */, PKG_NAME, "primary"))), + deepEq(mRefProfile), deepEq(mPrivateOutputProfile), deepEq(List.of(mDexPath)), + deepEq(mMergeProfileOptions)); + + // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs. + inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), + eq(mBetterOrSameDexoptTrigger)); + checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64", + ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath), + false /* isOtherReadable */, true /* generateAppImage */); + + inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"), + eq(mBetterOrSameDexoptTrigger)); + checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm", + ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath), + false /* isOtherReadable */, true /* generateAppImage */); + + inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath)); + + inOrder.verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary"))); + inOrder.verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary"))); + } + + @Test + public void testDexoptMergesProfilesMergeFailed() throws Exception { + when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled); + when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled); + + when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false); + + makeProfileUsable(mRefProfile); + when(mArtd.getProfileVisibility(deepEq(mRefProfile))) + .thenReturn(FileVisibility.OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + // It should still use "speed-profile", but with the existing reference profile only. + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile, + true /* isOtherReadable */, true /* generateAppImage */); + + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile, + true /* isOtherReadable */, true /* generateAppImage */); + + verify(mArtd, never()).deleteProfile(any()); + verify(mArtd, never()).commitTmpProfile(any()); + } + + @Test + public void testDexoptUsesDmProfile() throws Exception { + makeProfileNotUsable(mRefProfile); + makeProfileNotUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + mPrimaryDexopter.dexopt(); + + verify(mArtd).copyAndRewriteProfile( + deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath)); + + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + + verifyProfileNotUsed(mRefProfile); + verifyProfileNotUsed(mPrebuiltProfile); + } + + @Test + public void testDexoptDeletesProfileOnFailure() throws Exception { + makeProfileNotUsable(mRefProfile); + makeProfileNotUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(), + any(), any())) + .thenThrow(ServiceSpecificException.class); + + mPrimaryDexopter.dexopt(); + + verify(mArtd).deleteProfile( + deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath))); + verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath)); + } + + @Test + public void testDexoptNeedsToBeShared() throws Exception { + when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath))) + .thenReturn(true); + when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath))) + .thenReturn(true); + + // The ref profile is usable but shouldn't be used. + makeProfileUsable(mRefProfile); + + makeProfileNotUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + + // The existing artifacts are private. + when(mArtd.getArtifactsVisibility( + argThat(artifactsPath -> artifactsPath.dexPath == mDexPath))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + verify(mArtd).copyAndRewriteProfile( + deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath)); + + // It should re-compile anyway. + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", + ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath), + true /* isOtherReadable */, true /* generateAppImage */); + + checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed"); + checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed"); + + verifyProfileNotUsed(mRefProfile); + verifyProfileNotUsed(mPrebuiltProfile); + } + + @Test + public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception { + // Same setup as above, but the existing artifacts are public. + when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath))) + .thenReturn(true); + when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath))) + .thenReturn(true); + + makeProfileUsable(mRefProfile); + makeProfileNotUsable(mPrebuiltProfile); + makeProfileUsable(mDmProfile); + when(mArtd.getArtifactsVisibility( + argThat(artifactsPath -> artifactsPath.dexPath == mDexPath))) + .thenReturn(FileVisibility.OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + // It should use the default dexopt trigger. + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + verify(mArtd).getDexoptNeeded( + eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger)); + } + + @Test + public void testDexoptUsesProfileForSplit() throws Exception { + makeProfileUsable(mSplit0RefProfile); + when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + mPrimaryDexopter.dexopt(); + + verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"), + eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm64", mSplit0RefProfile, + false /* isOtherReadable */, false /* generateAppImage */); + + verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"), + eq(mDefaultDexoptTrigger)); + checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm", mSplit0RefProfile, + false /* isOtherReadable */, false /* generateAppImage */); + } + + @Test + public void testDexoptCancelledBeforeDexopt() throws Exception { + mCancellationSignal.cancel(); + + var artdCancellationSignal = mock(IArtdCancellationSignal.class); + when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal); + + doAnswer(invocation -> { + verify(artdCancellationSignal).cancel(); + return createArtdDexoptResult(true /* cancelled */); + }) + .when(mArtd) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + same(artdCancellationSignal)); + + // The result should only contain one element: the result of the first file with + // DEXOPT_CANCELLED. + assertThat(mPrimaryDexopter.dexopt() + .stream() + .map(DexContainerFileDexoptResult::getStatus) + .collect(Collectors.toList())) + .containsExactly(DexoptResult.DEXOPT_CANCELLED); + + // It shouldn't continue after being cancelled on the first file. + verify(mArtd, times(1)).createCancellationSignal(); + verify(mArtd, times(1)) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + any()); + } + + @Test + public void testDexoptCancelledDuringDexopt() throws Exception { + Semaphore dexoptStarted = new Semaphore(0); + Semaphore dexoptCancelled = new Semaphore(0); + final long TIMEOUT_SEC = 10; + + var artdCancellationSignal = mock(IArtdCancellationSignal.class); + when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal); + + doAnswer(invocation -> { + dexoptStarted.release(); + assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + return createArtdDexoptResult(true /* cancelled */); + }) + .when(mArtd) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + same(artdCancellationSignal)); + doAnswer(invocation -> { + dexoptCancelled.release(); + return null; + }) + .when(artdCancellationSignal) + .cancel(); + + Future<List<DexContainerFileDexoptResult>> results = + ForkJoinPool.commonPool().submit(() -> { return mPrimaryDexopter.dexopt(); }); + + assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue(); + + mCancellationSignal.cancel(); + + assertThat(results.get() + .stream() + .map(DexContainerFileDexoptResult::getStatus) + .collect(Collectors.toList())) + .containsExactly(DexoptResult.DEXOPT_CANCELLED); + + // It shouldn't continue after being cancelled on the first file. + verify(mArtd, times(1)).createCancellationSignal(); + verify(mArtd, times(1)) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + any()); + } + + @Test + public void testDexoptBaseApk() throws Exception { + mDexoptParams = + new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName(null) + .build(); + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + mPrimaryDexopter.dexopt(); + + verify(mArtd, times(2)) + .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(), + any(), any()); + verify(mArtd, never()) + .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(), + anyInt(), any(), any()); + } + + @Test + public void testDexoptSplitApk() throws Exception { + mDexoptParams = + new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + mPrimaryDexopter.dexopt(); + + verify(mArtd, never()) + .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(), + any(), any()); + verify(mArtd, times(2)) + .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(), + anyInt(), any(), any()); + } + + @Test + public void testDexoptStorageLow() throws Exception { + when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l, 0l, 0l, 1l); + + mDexoptParams = + new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) + .build(); + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt(); + assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED); + assertThat(results.get(0).isSkippedDueToStorageLow()).isFalse(); + assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED); + assertThat(results.get(1).isSkippedDueToStorageLow()).isTrue(); + assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED); + assertThat(results.get(2).isSkippedDueToStorageLow()).isTrue(); + assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED); + assertThat(results.get(3).isSkippedDueToStorageLow()).isFalse(); + + verify(mArtd, times(2)) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + any()); + } + + private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile, + boolean isOtherReadable, boolean generateAppImage) throws Exception { + artd.dexopt(argThat(artifacts + -> artifacts.permissionSettings.fileFsPermission.isOtherReadable + == isOtherReadable), + eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(), + anyInt(), + argThat(dexoptOptions -> dexoptOptions.generateAppImage == generateAppImage), + any()); + } + + private void checkDexoptWithNoProfile( + IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception { + artd.dexopt( + argThat(artifacts + -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true), + eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(), + argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any()); + } + + private void verifyProfileNotUsed(ProfilePath profile) throws Exception { + assertThat(mUsedProfiles) + .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality()) + .doesNotContain(profile); + } + + private void makeProfileUsable(ProfilePath profile) throws Exception { + lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> { + mUsedProfiles.add(invocation.<ProfilePath>getArgument(0)); + return true; + }); + lenient() + .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any())) + .thenAnswer(invocation -> { + mUsedProfiles.add(invocation.<ProfilePath>getArgument(0)); + return true; + }); + } + + private void makeProfileNotUsable(ProfilePath profile) throws Exception { + lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenReturn(false); + lenient() + .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any())) + .thenReturn(false); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java new file mode 100644 index 0000000000..94e0b489f7 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.same; + +import android.os.CancellationSignal; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; + +import com.android.modules.utils.pm.PackageStateModulesUtils; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageUserState; + +import dalvik.system.PathClassLoader; + +import org.junit.Before; +import org.junit.Rule; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; + +public class PrimaryDexopterTestBase { + protected static final String PKG_NAME = "com.example.foo"; + protected static final int UID = 12345; + protected static final int SHARED_GID = UserHandle.getSharedAppGid(UID); + protected static final long ART_VERSION = 331413030l; + protected static final String APP_VERSION_NAME = "12.34.56"; + protected static final long APP_VERSION_CODE = 1536036288l; + + @Rule + public StaticMockitoRule mockitoRule = new StaticMockitoRule( + SystemProperties.class, Constants.class, PackageStateModulesUtils.class); + + @Mock protected PrimaryDexopter.Injector mInjector; + @Mock protected IArtd mArtd; + @Mock protected UserManager mUserManager; + @Mock protected DexUseManagerLocal mDexUseManager; + @Mock protected StorageManager mStorageManager; + protected PackageState mPkgState; + protected AndroidPackage mPkg; + protected PackageUserState mPkgUserStateNotInstalled; + protected PackageUserState mPkgUserStateInstalled; + protected CancellationSignal mCancellationSignal; + + @Before + public void setUp() throws Exception { + lenient().when(mInjector.getArtd()).thenReturn(mArtd); + lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false); + lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false); + lenient().when(mInjector.getUserManager()).thenReturn(mUserManager); + lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager); + lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager); + lenient().when(mInjector.getArtVersion()).thenReturn(ART_VERSION); + + lenient() + .when(SystemProperties.get("dalvik.vm.systemuicompilerfilter")) + .thenReturn("speed"); + lenient() + .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean())) + .thenReturn(false); + lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4"); + lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed"); + + // No ISA translation. + lenient() + .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa.")))) + .thenReturn(""); + + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + + lenient() + .when(mUserManager.getUserHandles(anyBoolean())) + .thenReturn(List.of(UserHandle.of(0), UserHandle.of(1), UserHandle.of(2))); + + lenient().when(mDexUseManager.isPrimaryDexUsedByOtherApps(any(), any())).thenReturn(false); + + lenient().when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l); + + mPkgUserStateNotInstalled = createPackageUserState(false /* installed */); + mPkgUserStateInstalled = createPackageUserState(true /* installed */); + mPkgState = createPackageState(); + mPkg = mPkgState.getAndroidPackage(); + mCancellationSignal = new CancellationSignal(); + } + + private AndroidPackage createPackage() { + // This package has the base APK and one split APK that has code. + AndroidPackage pkg = mock(AndroidPackage.class); + var baseSplit = mock(AndroidPackageSplit.class); + lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk"); + lenient().when(baseSplit.isHasCode()).thenReturn(true); + lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName()); + + var split0 = mock(AndroidPackageSplit.class); + lenient().when(split0.getName()).thenReturn("split_0"); + lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk"); + lenient().when(split0.isHasCode()).thenReturn(true); + + var split1 = mock(AndroidPackageSplit.class); + lenient().when(split1.getName()).thenReturn("split_1"); + lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk"); + lenient().when(split1.isHasCode()).thenReturn(false); + + var splits = List.of(baseSplit, split0, split1); + lenient().when(pkg.getSplits()).thenReturn(splits); + + lenient().when(pkg.isVmSafeMode()).thenReturn(false); + lenient().when(pkg.isDebuggable()).thenReturn(false); + lenient().when(pkg.getTargetSdkVersion()).thenReturn(123); + lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false); + lenient().when(pkg.isNonSdkApiRequested()).thenReturn(false); + lenient().when(pkg.getVersionName()).thenReturn(APP_VERSION_NAME); + lenient().when(pkg.getLongVersionCode()).thenReturn(APP_VERSION_CODE); + return pkg; + } + + private PackageState createPackageState() { + PackageState pkgState = mock(PackageState.class); + lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME); + lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a"); + lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a"); + lenient().when(pkgState.isSystem()).thenReturn(false); + lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false); + lenient().when(pkgState.getAppId()).thenReturn(UID); + lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(new ArrayList<>()); + lenient().when(pkgState.getStateForUser(any())).thenReturn(mPkgUserStateNotInstalled); + AndroidPackage pkg = createPackage(); + lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg); + lenient() + .when(PackageStateModulesUtils.isLoadableInOtherProcesses( + same(pkgState), anyBoolean())) + .thenReturn(false); + return pkgState; + } + + private PackageUserState createPackageUserState(boolean isInstalled) { + PackageUserState pkgUserState = mock(PackageUserState.class); + lenient().when(pkgUserState.isInstalled()).thenReturn(isInstalled); + return pkgUserState; + } + + protected GetDexoptNeededResult dexoptIsNotNeeded() { + var result = new GetDexoptNeededResult(); + result.isDexoptNeeded = false; + return result; + } + + protected GetDexoptNeededResult dexoptIsNeeded() { + return dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR); + } + + protected GetDexoptNeededResult dexoptIsNeeded(@ArtifactsLocation byte location) { + var result = new GetDexoptNeededResult(); + result.isDexoptNeeded = true; + result.artifactsLocation = location; + if (location != ArtifactsLocation.NONE_OR_ERROR) { + result.isVdexUsable = true; + } + return result; + } + + protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled, long wallTimeMs, + long cpuTimeMs, long sizeBytes, long sizeBeforeBytes) { + var result = new ArtdDexoptResult(); + result.cancelled = cancelled; + result.wallTimeMs = wallTimeMs; + result.cpuTimeMs = cpuTimeMs; + result.sizeBytes = sizeBytes; + result.sizeBeforeBytes = sizeBeforeBytes; + return result; + } + + protected ArtdDexoptResult createArtdDexoptResult(boolean cancelled) { + return createArtdDexoptResult(cancelled, 0 /* wallTimeMs */, 0 /* cpuTimeMs */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java new file mode 100644 index 0000000000..55fd0b43ce --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; + +import android.os.SystemProperties; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.model.ArtFlags; +import com.android.server.art.testing.StaticMockitoRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ReasonMappingTest { + @Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(SystemProperties.class); + + @Test + public void testGetCompilerFilterForReason() { + when(SystemProperties.get("pm.dexopt.foo")).thenReturn("speed"); + assertThat(ReasonMapping.getCompilerFilterForReason("foo")).isEqualTo("speed"); + } + + @Test(expected = IllegalStateException.class) + public void testGetCompilerFilterForReasonInvalidFilter() throws Exception { + when(SystemProperties.get("pm.dexopt.foo")).thenReturn("invalid-filter"); + ReasonMapping.getCompilerFilterForReason("foo"); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetCompilerFilterForReasonInvalidReason() throws Exception { + ReasonMapping.getCompilerFilterForReason("foo"); + } + + @Test + public void testGetCompilerFilterForShared() { + when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed"); + assertThat(ReasonMapping.getCompilerFilterForShared()).isEqualTo("speed"); + } + + @Test(expected = IllegalStateException.class) + public void testGetCompilerFilterForSharedProfileGuidedFilter() throws Exception { + when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed-profile"); + ReasonMapping.getCompilerFilterForShared(); + } + + @Test + public void testGetPriorityClassForReason() throws Exception { + assertThat(ReasonMapping.getPriorityClassForReason("install")) + .isEqualTo(ArtFlags.PRIORITY_INTERACTIVE); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPriorityClassForReasonInvalidReason() throws Exception { + ReasonMapping.getPriorityClassForReason("foo"); + } + + @Test + public void testGetConcurrencyForReason() { + when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt())) + .thenReturn(3); + assertThat(ReasonMapping.getConcurrencyForReason("bg-dexopt")).isEqualTo(3); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java new file mode 100644 index 0000000000..5d8661fa05 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; +import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation; +import static com.android.server.art.OutputArtifacts.PermissionSettings; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.testing.TestingUtils.deepEq; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.CancellationSignal; +import android.os.SystemProperties; +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.model.ArtFlags; +import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.art.testing.TestingUtils; +import com.android.server.pm.PackageSetting; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageStateUnserialized; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SecondaryDexopterTest { + private static final String PKG_NAME = "com.example.foo"; + private static final int APP_ID = 12345; + private static final UserHandle USER_HANDLE = UserHandle.of(2); + private static final int UID = USER_HANDLE.getUid(APP_ID); + private static final String APP_DATA_DIR = "/data/user/2/" + PKG_NAME; + private static final String DEX_1 = APP_DATA_DIR + "/1.apk"; + private static final String DEX_2 = APP_DATA_DIR + "/2.apk"; + private static final String DEX_3 = APP_DATA_DIR + "/3.apk"; + + private final DexoptParams mDexoptParams = + new DexoptParams.Builder("bg-dexopt") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX) + .build(); + + private final ProfilePath mDex1RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_1); + private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1); + private final ProfilePath mDex2RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_2); + private final ProfilePath mDex3RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_3); + private final OutputProfile mDex1PrivateOutputProfile = + AidlUtils.buildOutputProfileForSecondary(DEX_1, UID, UID, false /* isOtherReadable */); + + private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER + | DexoptTrigger.COMPILER_FILTER_IS_SAME + | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; + + private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions(); + + @Rule + public StaticMockitoRule mockitoRule = + new StaticMockitoRule(SystemProperties.class, Constants.class); + + @Mock private SecondaryDexopter.Injector mInjector; + @Mock private IArtd mArtd; + @Mock private DexUseManagerLocal mDexUseManager; + private PackageState mPkgState; + private AndroidPackage mPkg; + private CancellationSignal mCancellationSignal; + + private SecondaryDexopter mSecondaryDexopter; + + @Before + public void setUp() throws Exception { + lenient() + .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean())) + .thenReturn(false); + lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4"); + lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed"); + + // No ISA translation. + lenient() + .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa.")))) + .thenReturn(""); + + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + + lenient().when(mInjector.getArtd()).thenReturn(mArtd); + lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false); + lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false); + lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager); + + List<DetailedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo(); + lenient() + .when(mDexUseManager.getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME))) + .thenReturn(secondaryDexInfo); + + mPkgState = createPackageState(); + mPkg = mPkgState.getAndroidPackage(); + mCancellationSignal = new CancellationSignal(); + + prepareProfiles(); + + // Dexopt is always needed and successful. + lenient() + .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt())) + .thenReturn(dexoptIsNeeded()); + lenient() + .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), + any(), any())) + .thenReturn(createArtdDexoptResult()); + + lenient() + .when(mArtd.createCancellationSignal()) + .thenReturn(mock(IArtdCancellationSignal.class)); + + mSecondaryDexopter = new SecondaryDexopter( + mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + } + + @Test + public void testDexopt() throws Exception { + assertThat(mSecondaryDexopter.dexopt()) + .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality()) + .containsExactly( + DexContainerFileDexoptResult.create(DEX_1, true /* isPrimaryAbi */, + "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create(DEX_2, true /* isPrimaryAbi */, + "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create(DEX_2, false /* isPrimaryAbi */, + "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */), + DexContainerFileDexoptResult.create(DEX_3, true /* isPrimaryAbi */, + "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED, + 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, + 0 /* sizeBytes */, 0 /* sizeBeforeBytes */, + false /* isSkippedDueToStorageLow */)); + + // It should use profile for dex 1. + + verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile), + deepEq(mDex1PrivateOutputProfile), deepEq(List.of(DEX_1)), + deepEq(mMergeProfileOptions)); + + verify(mArtd).getDexoptNeeded( + eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger)); + checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64", + ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1"); + + verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath)); + + verify(mArtd).deleteProfile(deepEq(mDex1CurProfile)); + + // It should use "speed" for dex 2 for both ISAs and make the artifacts public. + + verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any()); + verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any(), any()); + + verify(mArtd).getDexoptNeeded( + eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger)); + checkDexoptWithNoProfile( + verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */); + + verify(mArtd).getDexoptNeeded( + eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger)); + checkDexoptWithNoProfile( + verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */); + + // It should use "verify" for dex 3 and make the artifacts private. + + verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any()); + verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any(), any()); + + verify(mArtd).getDexoptNeeded( + eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger)); + checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify", + null /* classLoaderContext */, false /* isPublic */); + } + + private AndroidPackage createPackage() { + var pkg = mock(AndroidPackage.class); + lenient().when(pkg.isVmSafeMode()).thenReturn(false); + lenient().when(pkg.isDebuggable()).thenReturn(false); + lenient().when(pkg.getTargetSdkVersion()).thenReturn(123); + lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false); + lenient().when(pkg.isNonSdkApiRequested()).thenReturn(false); + return pkg; + } + + private PackageState createPackageState() { + var pkgState = mock(PackageState.class); + lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME); + lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a"); + lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a"); + lenient().when(pkgState.getAppId()).thenReturn(APP_ID); + lenient().when(pkgState.getSeInfo()).thenReturn("se-info"); + AndroidPackage pkg = createPackage(); + lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg); + return pkgState; + } + + private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception { + // This should be compiled with profile. + var dex1Info = mock(DetailedSecondaryDexInfo.class); + lenient().when(dex1Info.dexPath()).thenReturn(DEX_1); + lenient().when(dex1Info.userHandle()).thenReturn(USER_HANDLE); + lenient().when(dex1Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_1"); + lenient().when(dex1Info.abiNames()).thenReturn(Set.of("arm64-v8a")); + lenient().when(dex1Info.isUsedByOtherApps()).thenReturn(false); + lenient().when(dex1Info.isDexFilePublic()).thenReturn(true); + + // This should be compiled without profile because it's used by other apps. + var dex2Info = mock(DetailedSecondaryDexInfo.class); + lenient().when(dex2Info.dexPath()).thenReturn(DEX_2); + lenient().when(dex2Info.userHandle()).thenReturn(USER_HANDLE); + lenient().when(dex2Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_2"); + lenient().when(dex2Info.abiNames()).thenReturn(Set.of("arm64-v8a", "armeabi-v7a")); + lenient().when(dex2Info.isUsedByOtherApps()).thenReturn(true); + lenient().when(dex2Info.isDexFilePublic()).thenReturn(true); + + // This should be compiled with verify because the class loader context is invalid. + var dex3Info = mock(DetailedSecondaryDexInfo.class); + lenient().when(dex3Info.dexPath()).thenReturn(DEX_3); + lenient().when(dex3Info.userHandle()).thenReturn(USER_HANDLE); + lenient().when(dex3Info.classLoaderContext()).thenReturn(null); + lenient().when(dex3Info.abiNames()).thenReturn(Set.of("arm64-v8a")); + lenient().when(dex3Info.isUsedByOtherApps()).thenReturn(false); + lenient().when(dex3Info.isDexFilePublic()).thenReturn(false); + + return List.of(dex1Info, dex2Info, dex3Info); + } + + private void prepareProfiles() throws Exception { + // Profile for dex file 1 is usable. + lenient().when(mArtd.isProfileUsable(deepEq(mDex1RefProfile), any())).thenReturn(true); + lenient() + .when(mArtd.getProfileVisibility(deepEq(mDex1RefProfile))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + // Profiles for dex file 2 and 3 are also usable, but shouldn't be used. + lenient().when(mArtd.isProfileUsable(deepEq(mDex2RefProfile), any())).thenReturn(true); + lenient() + .when(mArtd.getProfileVisibility(deepEq(mDex2RefProfile))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + lenient().when(mArtd.isProfileUsable(deepEq(mDex3RefProfile), any())).thenReturn(true); + lenient() + .when(mArtd.getProfileVisibility(deepEq(mDex3RefProfile))) + .thenReturn(FileVisibility.NOT_OTHER_READABLE); + + lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true); + } + + private GetDexoptNeededResult dexoptIsNeeded() { + var result = new GetDexoptNeededResult(); + result.isDexoptNeeded = true; + result.artifactsLocation = ArtifactsLocation.NONE_OR_ERROR; + result.isVdexUsable = false; + return result; + } + + private ArtdDexoptResult createArtdDexoptResult() { + var result = new ArtdDexoptResult(); + result.cancelled = false; + result.wallTimeMs = 0; + result.cpuTimeMs = 0; + result.sizeBytes = 0; + result.sizeBeforeBytes = 0; + return result; + } + + private void checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa, + ProfilePath profile, String classLoaderContext) throws Exception { + PermissionSettings permissionSettings = buildPermissionSettings(false /* isPublic */); + OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts( + dexPath, isa, false /* isInDalvikCache */, permissionSettings); + artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext), + eq("speed-profile"), deepEq(profile), any(), isNull() /* dmFile */, anyInt(), + argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any()); + } + + private void checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa, + String compilerFilter, String classLoaderContext, boolean isPublic) throws Exception { + PermissionSettings permissionSettings = buildPermissionSettings(isPublic); + OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts( + dexPath, isa, false /* isInDalvikCache */, permissionSettings); + artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext), + eq(compilerFilter), isNull(), any(), isNull() /* dmFile */, anyInt(), + argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any()); + } + + private PermissionSettings buildPermissionSettings(boolean isPublic) { + FsPermission dirFsPermission = AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */, + false /* isOtherReadable */, true /* isOtherExecutable */); + FsPermission fileFsPermission = + AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */, isPublic); + return AidlUtils.buildPermissionSettings( + dirFsPermission, fileFsPermission, AidlUtils.buildSeContext("se-info", UID)); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java new file mode 100644 index 0000000000..bc6ed1611b --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.os.SystemProperties; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.art.Utils; +import com.android.server.art.testing.StaticMockitoRule; +import com.android.server.pm.pkg.PackageState; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UtilsTest { + @Rule + public StaticMockitoRule mockitoRule = + new StaticMockitoRule(SystemProperties.class, Constants.class); + + @Before + public void setUp() throws Exception { + lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("arm64"); + lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86"))).thenReturn("arm"); + + lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a"); + lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a"); + } + + @Test + public void testCollectionIsEmptyTrue() { + assertThat(Utils.isEmpty(List.of())).isTrue(); + } + + @Test + public void testCollectionIsEmptyFalse() { + assertThat(Utils.isEmpty(List.of(1))).isFalse(); + } + + @Test + public void testSparseArrayIsEmptyTrue() { + assertThat(Utils.isEmpty(new SparseArray<Integer>())).isTrue(); + } + + @Test + public void testSparseArrayIsEmptyFalse() { + SparseArray<Integer> array = new SparseArray<>(); + array.put(1, 1); + assertThat(Utils.isEmpty(array)).isFalse(); + } + + @Test + public void testArrayIsEmptyTrue() { + assertThat(Utils.isEmpty(new int[0])).isTrue(); + } + + @Test + public void testArrayIsEmptyFalse() { + assertThat(Utils.isEmpty(new int[] {1})).isFalse(); + } + + @Test + public void testGetAllAbis() { + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a"); + when(pkgState.getSecondaryCpuAbi()).thenReturn("arm64-v8a"); + + assertThat(Utils.getAllAbis(pkgState)) + .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */), + Utils.Abi.create("arm64-v8a", "arm64", false /* isPrimaryAbi */)); + } + + @Test + public void testGetAllAbisTranslated() { + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64"); + when(pkgState.getSecondaryCpuAbi()).thenReturn("x86"); + + assertThat(Utils.getAllAbis(pkgState)) + .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */), + Utils.Abi.create("armeabi-v7a", "arm", false /* isPrimaryAbi */)); + } + + @Test + public void testGetAllAbisPrimaryOnly() { + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn("armeabi-v7a"); + when(pkgState.getSecondaryCpuAbi()).thenReturn(null); + + assertThat(Utils.getAllAbis(pkgState)) + .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */)); + } + + @Test + public void testGetAllAbisNone() { + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn(null); + when(pkgState.getSecondaryCpuAbi()).thenReturn(null); + + assertThat(Utils.getAllAbis(pkgState)) + .containsExactly(Utils.Abi.create("arm64-v8a", "arm64", true /* isPrimaryAbi */)); + + // Make sure the result does come from `Constants.getPreferredAbi()` rather than somewhere + // else. + when(Constants.getPreferredAbi()).thenReturn("armeabi-v7a"); + assertThat(Utils.getAllAbis(pkgState)) + .containsExactly(Utils.Abi.create("armeabi-v7a", "arm", true /* isPrimaryAbi */)); + } + + @Test(expected = IllegalStateException.class) + public void testGetAllAbisInvalidNativeIsa() { + lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn("x86"); + + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64"); + when(pkgState.getSecondaryCpuAbi()).thenReturn(null); + + Utils.getAllAbis(pkgState); + } + + @Test(expected = IllegalStateException.class) + public void testGetAllAbisUnsupportedTranslation() { + lenient().when(SystemProperties.get(eq("ro.dalvik.vm.isa.x86_64"))).thenReturn(""); + + var pkgState = mock(PackageState.class); + when(pkgState.getPrimaryCpuAbi()).thenReturn("x86_64"); + when(pkgState.getSecondaryCpuAbi()).thenReturn(null); + + Utils.getAllAbis(pkgState); + } + + @Test + public void testImplies() { + assertThat(Utils.implies(false, false)).isTrue(); + assertThat(Utils.implies(false, true)).isTrue(); + assertThat(Utils.implies(true, false)).isFalse(); + assertThat(Utils.implies(true, true)).isTrue(); + } + + @Test + public void testCheck() { + Utils.check(true); + } + + @Test(expected = IllegalStateException.class) + public void testCheckFailed() throws Exception { + Utils.check(false); + } + + @Test + public void testExecuteAndWait() { + Executor executor = ForkJoinPool.commonPool(); + List<String> results = new ArrayList<>(); + Utils.executeAndWait(executor, () -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + results.add("foo"); + }); + assertThat(results).containsExactly("foo"); + } + + @Test(expected = IllegalArgumentException.class) + public void testExecuteAndWaitPropagatesException() { + Executor executor = ForkJoinPool.commonPool(); + Utils.executeAndWait(executor, () -> { throw new IllegalArgumentException(); }); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java new file mode 100644 index 0000000000..60986414fa --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/model/DexoptParamsTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art.model; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DexoptParamsTest { + @Test + public void testBuild() { + new DexoptParams.Builder("install").build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildEmptyReason() { + new DexoptParams.Builder("").build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildReasonVdex() { + new DexoptParams.Builder("vdex").setCompilerFilter("speed").setPriorityClass(90).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildInvalidCompilerFilter() { + new DexoptParams.Builder("install").setCompilerFilter("invalid").build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildInvalidPriorityClass() { + new DexoptParams.Builder("install").setPriorityClass(101).build(); + } + + @Test + public void testBuildCustomReason() { + new DexoptParams.Builder("custom").setCompilerFilter("speed").setPriorityClass(90).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildCustomReasonEmptyCompilerFilter() { + new DexoptParams.Builder("custom").setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildCustomReasonEmptyPriorityClass() { + new DexoptParams.Builder("custom").setCompilerFilter("speed").build(); + } + + @Test + public void testSingleSplit() { + new DexoptParams.Builder("install") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testSingleSplitNoPrimaryFlag() { + new DexoptParams.Builder("install") + .setFlags(ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testSingleSplitSecondaryFlag() { + new DexoptParams.Builder("install") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX + | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testSingleSplitDependenciesFlag() { + new DexoptParams.Builder("install") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES + | ArtFlags.FLAG_FOR_SINGLE_SPLIT) + .setSplitName("split_0") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testSplitNameNoSingleSplitFlag() { + new DexoptParams.Builder("install") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX) + .setSplitName("split_0") + .build(); + } +} diff --git a/libartservice/service/javatests/com/android/server/art/testing/MockClock.java b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java new file mode 100644 index 0000000000..7b4b23b3b5 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/testing/MockClock.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.testing; + +import android.annotation.NonNull; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.concurrent.RunnableScheduledFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class MockClock { + private long mCurrentTimeMs = 0; + @NonNull private List<ScheduledExecutor> mExecutors = new ArrayList<>(); + + @NonNull + public ScheduledExecutor createScheduledExecutor() { + var executor = new ScheduledExecutor(); + mExecutors.add(executor); + return executor; + } + + public long getCurrentTimeMs() { + return mCurrentTimeMs; + } + + public void advanceTime(long timeMs) { + mCurrentTimeMs += timeMs; + for (ScheduledExecutor executor : mExecutors) { + executor.notifyUpdate(); + } + } + + public class ScheduledExecutor extends ScheduledThreadPoolExecutor { + // The second element of the pair is the scheduled time. + @NonNull + private PriorityQueue<Pair<RunnableScheduledFuture<?>, Long>> tasks = new PriorityQueue<>( + 1 /* initialCapacity */, Comparator.comparingLong(pair -> pair.second)); + + public ScheduledExecutor() { + super(1 /* corePoolSize */); + } + + @NonNull + public ScheduledFuture<?> schedule( + @NonNull Runnable command, long delay, @NonNull TimeUnit unit) { + // Use `Long.MAX_VALUE` to prevent the task from being automatically run. + var task = (RunnableScheduledFuture<?>) super.schedule( + command, Long.MAX_VALUE, TimeUnit.MILLISECONDS); + tasks.add(Pair.create(task, getCurrentTimeMs() + unit.toMillis(delay))); + return task; + } + + public void notifyUpdate() { + while (!tasks.isEmpty()) { + Pair<RunnableScheduledFuture<?>, Long> pair = tasks.peek(); + RunnableScheduledFuture<?> task = pair.first; + long scheduledTimeMs = pair.second; + if (getCurrentTimeMs() >= scheduledTimeMs) { + if (!task.isDone() && !task.isCancelled()) { + task.run(); + } + tasks.poll(); + // Remove the task from the queue of the executor. Terminate the executor if + // it's shutdown and the queue is empty. + super.remove(task); + } else { + break; + } + } + } + } +} diff --git a/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java b/libartservice/service/javatests/com/android/server/art/testing/OnSuccessRule.java new file mode 100644 index 0000000000..350a8cb624 --- /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 0000000000..595370b840 --- /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 0000000000..5ee0a57bd7 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art.testing; + +import static org.mockito.Mockito.argThat; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import com.google.common.truth.Correspondence; +import com.google.common.truth.Truth; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; + +public final class TestingUtils { + private static final String TAG = "ArtServiceTesting"; + + private TestingUtils() {} + + /** + * Recursively compares two objects using reflection. Returns true if the two objects are equal. + * For simplicity, this method only supports types that every field is a primitive type, a + * string, a {@link List}, or a supported type. + */ + public static boolean deepEquals( + @Nullable Object a, @Nullable Object b, @NonNull StringBuilder errorMsg) { + try { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + errorMsg.append(String.format("Nullability mismatch: %s != %s", + a == null ? "null" : "nonnull", b == null ? "null" : "nonnull")); + return false; + } + if (a instanceof List && b instanceof List) { + return listDeepEquals((List<?>) a, (List<?>) b, errorMsg); + } + if (a.getClass() != b.getClass()) { + errorMsg.append( + String.format("Type mismatch: %s != %s", a.getClass(), b.getClass())); + return false; + } + if (a.getClass() == String.class) { + if (!a.equals(b)) { + errorMsg.append(String.format("%s != %s", a, b)); + } + return a.equals(b); + } + if (a.getClass().isArray()) { + throw new UnsupportedOperationException("Array type is not supported"); + } + for (Field field : a.getClass().getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); + if (field.getType().isPrimitive()) { + if (!field.get(a).equals(field.get(b))) { + errorMsg.append(String.format("Field %s mismatch: %s != %s", + field.getName(), field.get(a), field.get(b))); + return false; + } + } else if (!deepEquals(field.get(a), field.get(b), errorMsg)) { + errorMsg.insert(0, String.format("Field %s mismatch: ", field.getName())); + return false; + } + } + return true; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /** Same as above, but ignores any error message. */ + public static boolean deepEquals(@Nullable Object a, @Nullable Object b) { + var errorMsgIgnored = new StringBuilder(); + return deepEquals(a, b, errorMsgIgnored); + } + + /** + * A Mockito argument matcher that uses {@link #deepEquals} to compare objects and logs any + * mismatch. + */ + public static <T> T deepEq(@Nullable T expected) { + return argThat(arg -> { + var errorMsg = new StringBuilder(); + boolean result = deepEquals(arg, expected, errorMsg); + if (!result) { + Log.e(TAG, errorMsg.toString()); + } + return result; + }); + } + + /** + * A Mockito argument matcher that matches a list containing expected in any order. + */ + @SafeVarargs + public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrder( + @Nullable ItemType... expected) { + return argThat(argument -> { + try { + Truth.assertThat(argument).containsExactlyElementsIn(expected); + return true; + } catch (AssertionError error) { + return false; + } + }); + } + + /** + * {@link #inAnyOrder(Object[])} but using {@link #deepEquals(Object, Object)}} for comparisons. + * + * @see #inAnyOrder(Object[]) + */ + @SafeVarargs + public static <ListType extends List<ItemType>, ItemType> ListType inAnyOrderDeepEquals( + @Nullable ItemType... expected) { + return argThat(argument -> { + try { + Truth.assertThat(argument) + .comparingElementsUsing(deepEquality()) + .containsExactlyElementsIn(expected); + return true; + } catch (AssertionError error) { + return false; + } + }); + } + + /** + * A Truth correspondence that uses {@link #deepEquals} to compare objects and reports any + * mismatch. + */ + public static <T> Correspondence<T, T> deepEquality() { + return Correspondence.<T, T>from(TestingUtils::deepEquals, "deeply equals") + .formattingDiffsUsing((actual, expected) -> { + var errorMsg = new StringBuilder(); + deepEquals(actual, expected, errorMsg); + return errorMsg.toString(); + }); + } + + private static boolean listDeepEquals( + @NonNull List<?> a, @NonNull List<?> b, @NonNull StringBuilder errorMsg) { + if (a.size() != b.size()) { + errorMsg.append(String.format("List length mismatch: %d != %d", a.size(), b.size())); + return false; + } + for (int i = 0; i < a.size(); i++) { + if (!deepEquals(a.get(i), b.get(i), errorMsg)) { + errorMsg.insert(0, String.format("Element %d mismatch: ", i)); + return false; + } + } + return true; + }; +} diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java new file mode 100644 index 0000000000..518f0e4716 --- /dev/null +++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtilsTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.art.testing; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.NonNull; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.internal.progress.ThreadSafeMockingProgress; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.function.Consumer; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TestingUtilsTest { + @Before + @After + public void resetMockito() { + ThreadSafeMockingProgress.mockingProgress().reset(); + } + + @Test + public void testDeepEquals() { + var a = new Foo(); + var b = new Foo(); + assertThat(TestingUtils.deepEquals(a, b)).isTrue(); + } + + @Test + public void testDeepEqualsNull() { + assertThat(TestingUtils.deepEquals(null, null)).isTrue(); + } + + @Test + public void testDeepEqualsNullabilityMismatch() { + var a = new Foo(); + assertThat(TestingUtils.deepEquals(a, null)).isFalse(); + } + + @Test + public void testDeepEqualsTypeMismatch() { + var a = new Foo(); + var b = new Bar(); + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test + public void testDeepEqualsPrimitiveFieldMismatch() { + var a = new Foo(); + var b = new Foo(); + b.mA = 11111111; + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test + public void testDeepEqualsStringFieldMismatch() { + var a = new Foo(); + var b = new Foo(); + b.mB = "def"; + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test + public void deepEqualsNestedFieldMismatch() { + var a = new Foo(); + var b = new Foo(); + b.mC.setB(11111111); + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testDeepEqualsArrayNotSupported() throws Exception { + int[] a = new int[] {1}; + int[] b = new int[] {2}; + TestingUtils.deepEquals(a, b); + } + + @Test + public void testListDeepEquals() throws Exception { + var a = new ArrayList<Integer>(); + a.add(1); + a.add(2); + a.add(3); + a.add(4); + a.add(5); + var b = List.of(1, 2, 3, 4, 5); + assertThat(TestingUtils.deepEquals(a, b)).isTrue(); + } + + @Test + public void testListDeepEqualsSizeMismatch() throws Exception { + var a = new ArrayList<Integer>(); + a.add(1); + var b = new ArrayList<Integer>(); + b.add(1); + b.add(2); + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test + public void testListDeepEqualsElementMismatch() throws Exception { + var a = new ArrayList<Integer>(); + a.add(1); + var b = new ArrayList<Integer>(); + b.add(2); + assertThat(TestingUtils.deepEquals(a, b)).isFalse(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testDeepEqualsOtherContainerNotSupported() throws Exception { + var a = new HashSet<Integer>(); + a.add(1); + var b = new HashSet<Integer>(); + b.add(2); + TestingUtils.deepEquals(a, b); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test + public void testInAnyOrderDuplicates() { + testInAnyOrderInternal(List.of(1, 1), List.of(1), false, TestingUtils::inAnyOrder); + testInAnyOrderInternal(List.of(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrder); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test + public void testInAnyOrderDeepEqualsDuplicates() { + testInAnyOrderInternal( + Arrays.asList(1, 1), List.of(1), false, TestingUtils::inAnyOrderDeepEquals); + testInAnyOrderInternal( + Arrays.asList(1, 1), List.of(1, 1), true, TestingUtils::inAnyOrderDeepEquals); + } + + private void testInAnyOrderInternal(@NonNull List<Integer> first, @NonNull List<Integer> second, + boolean expected, @NonNull Consumer<Integer[]> inAnyOrderBlock) { + inAnyOrderBlock.accept(first.toArray(new Integer[0])); + var matchers = ThreadSafeMockingProgress.mockingProgress() + .getArgumentMatcherStorage() + .pullLocalizedMatchers(); + assertThat(matchers).hasSize(1); + // noinspection unchecked + var matcher = (ArgumentMatcher<List<Integer>>) matchers.get(0).getMatcher(); + assertThat(matcher.matches(second)).isEqualTo(expected); + ThreadSafeMockingProgress.mockingProgress().reset(); + } +} + +class Foo { + public int mA = 1234567; + public String mB = "abc"; + public Bar mC = new Bar(); +} + +class Bar { + public static int sA = 10000000; + private int mB = 7654321; + public void setB(int b) { + mB = b; + } +} diff --git a/libartservice/service/proguard.flags b/libartservice/service/proguard.flags new file mode 100644 index 0000000000..8ef413f3a3 --- /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 0000000000..c8bb56536d --- /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 0000000000..1dd962dbf4 --- /dev/null +++ b/libartservice/service/proto/dex_use.proto @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package com.android.server.art.proto; +option java_multiple_files = true; + +import "art/libartservice/service/proto/common.proto"; + +// The protobuf representation of `DexUseManagerLocal.DexUse`. See classes in +// java/com/android/server/art/DexUseManagerLocal.java for details. +// This proto is persisted on disk and both forward and backward compatibility are considerations. +message DexUseProto { + repeated PackageDexUseProto package_dex_use = 1; +} + +message PackageDexUseProto { + string owning_package_name = 1; + repeated PrimaryDexUseProto primary_dex_use = 2; + repeated SecondaryDexUseProto secondary_dex_use = 3; +} + +message PrimaryDexUseProto { + string dex_file = 1; + repeated PrimaryDexUseRecordProto record = 2; +} + +message PrimaryDexUseRecordProto { + string loading_package_name = 1; + bool isolated_process = 2; + int64 last_used_at_ms = 3; +} + +message SecondaryDexUseProto { + string dex_file = 1; + Int32Value user_id = 2; // Must be explicitly set. + repeated SecondaryDexUseRecordProto record = 3; +} + +message SecondaryDexUseRecordProto { + string loading_package_name = 1; + bool isolated_process = 2; + string class_loader_context = 3; + string abi_name = 4; + int64 last_used_at_ms = 5; +} diff --git a/libarttools/Android.bp b/libarttools/Android.bp index d86a02f234..5c0f1f21c4 100644 --- a/libarttools/Android.bp +++ b/libarttools/Android.bp @@ -34,6 +34,7 @@ cc_defaults { "tools/tools.cc", ], export_include_dirs: ["."], + header_libs: ["art_libartbase_headers"], shared_libs: [ "libbase", ], @@ -57,6 +58,7 @@ art_cc_defaults { name: "art_libarttools_tests_defaults", defaults: ["libarttools_defaults"], srcs: [ + "tools/art_exec_test.cc", "tools/cmdline_builder_test.cc", "tools/system_properties_test.cc", "tools/tools_test.cc", @@ -67,6 +69,11 @@ art_cc_defaults { static_libs: [ "libgmock", ], + target: { + android: { + static_libs: ["libmodules-utils-build"], + }, + }, } // Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target. @@ -88,3 +95,26 @@ art_cc_test { "art_libarttools_tests_defaults", ], } + +cc_binary { + name: "art_exec", + defaults: [ + "art_defaults", + ], + srcs: [ + "tools/art_exec.cc", + ], + shared_libs: [ + "libartbase", + "libartpalette", + "libarttools", // Contains "libc++fs". + "libbase", + ], + static_libs: [ + "libcap", + ], + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], +} diff --git a/libarttools/tools/art_exec.cc b/libarttools/tools/art_exec.cc new file mode 100644 index 0000000000..8f3365885b --- /dev/null +++ b/libarttools/tools/art_exec.cc @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <sys/capability.h> +#include <sys/resource.h> +#include <unistd.h> + +#include <filesystem> +#include <iostream> +#include <iterator> +#include <optional> +#include <string> +#include <string_view> +#include <system_error> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "android-base/logging.h" +#include "android-base/parseint.h" +#include "android-base/result.h" +#include "android-base/strings.h" +#include "base/macros.h" +#include "base/scoped_cap.h" +#include "palette/palette.h" +#include "system/thread_defs.h" + +namespace { + +using ::android::base::ConsumePrefix; +using ::android::base::Join; +using ::android::base::ParseInt; +using ::android::base::Result; +using ::android::base::Split; + +constexpr const char* kUsage = + R"(A wrapper binary that configures the process and executes a command. + +By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can +be passed to keep some more file descriptors open. + +Usage: art_exec [OPTIONS]... -- [COMMAND]... + +Supported options: + --help: Print this text. + --set-task-profile=PROFILES: Apply a set of task profiles (see + https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a + comma-separated list of task profile names. + --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of + PRIORITY is "background". + --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs + with some root capabilities but not as the root user. + --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open. + --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set + multiple environment variables. +)"; + +constexpr int kErrorUsage = 100; +constexpr int kErrorOther = 101; + +struct Options { + int command_pos = -1; + std::vector<std::string> task_profiles; + std::optional<int> priority = std::nullopt; + bool drop_capabilities = false; + std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)}; + std::unordered_map<std::string, std::string> envs; +}; + +[[noreturn]] void Usage(const std::string& error_msg) { + LOG(ERROR) << error_msg; + std::cerr << error_msg << "\n" << kUsage << "\n"; + exit(kErrorUsage); +} + +Options ParseOptions(int argc, char** argv) { + Options options; + for (int i = 1; i < argc; i++) { + std::string_view arg = argv[i]; + if (arg == "--help") { + std::cerr << kUsage << "\n"; + exit(0); + } else if (ConsumePrefix(&arg, "--set-task-profile=")) { + options.task_profiles = Split(std::string(arg), ","); + if (options.task_profiles.empty()) { + Usage("Empty task profile list"); + } + } else if (ConsumePrefix(&arg, "--set-priority=")) { + if (arg == "background") { + options.priority = ANDROID_PRIORITY_BACKGROUND; + } else { + Usage("Unknown priority " + std::string(arg)); + } + } else if (arg == "--drop-capabilities") { + options.drop_capabilities = true; + } else if (ConsumePrefix(&arg, "--keep-fds=")) { + for (const std::string& fd_str : Split(std::string(arg), ":")) { + int fd; + if (!ParseInt(fd_str, &fd)) { + Usage("Invalid fd " + fd_str); + } + options.keep_fds.insert(fd); + } + } else if (ConsumePrefix(&arg, "--env=")) { + size_t pos = arg.find('='); + if (pos == std::string_view::npos) { + Usage("Malformed environment variable. Must contain '='"); + } + options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] = + std::string(arg.substr(pos + 1)); + } else if (arg == "--") { + if (i + 1 >= argc) { + Usage("Missing command after '--'"); + } + options.command_pos = i + 1; + return options; + } else { + Usage("Unknown option " + std::string(arg)); + } + } + Usage("Missing '--'"); +} + +Result<void> DropInheritableCaps() { + art::ScopedCap cap(cap_get_proc()); + if (cap.Get() == nullptr) { + return ErrnoErrorf("Failed to call cap_get_proc"); + } + if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) { + return ErrnoErrorf("Failed to call cap_clear_flag"); + } + if (cap_set_proc(cap.Get()) != 0) { + return ErrnoErrorf("Failed to call cap_set_proc"); + } + return {}; +} + +Result<void> CloseFds(const std::unordered_set<int>& keep_fds) { + std::vector<int> open_fds; + std::error_code ec; + for (const std::filesystem::directory_entry& dir_entry : + std::filesystem::directory_iterator("/proc/self/fd", ec)) { + int fd; + if (!ParseInt(dir_entry.path().filename(), &fd)) { + return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename()); + } + open_fds.push_back(fd); + } + if (ec) { + return Errorf("Failed to list open FDs: {}", ec.message()); + } + for (int fd : open_fds) { + if (keep_fds.find(fd) == keep_fds.end()) { + if (close(fd) != 0) { + Result<void> error = ErrnoErrorf("Failed to close FD {}", fd); + if (std::filesystem::exists(ART_FORMAT("/proc/self/fd/{}", fd))) { + return error; + } + } + } + } + return {}; +} + +} // namespace + +int main(int argc, char** argv) { + android::base::InitLogging(argv); + + Options options = ParseOptions(argc, argv); + + if (auto result = CloseFds(options.keep_fds); !result.ok()) { + LOG(ERROR) << "Failed to close open FDs: " << result.error(); + return kErrorOther; + } + + if (!options.task_profiles.empty()) { + if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles); + ret != PALETTE_STATUS_OK) { + LOG(ERROR) << "Failed to set task profile: " << ret; + return kErrorOther; + } + } + + if (options.priority.has_value()) { + if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) { + PLOG(ERROR) << "Failed to setpriority"; + return kErrorOther; + } + } + + if (options.drop_capabilities) { + if (auto result = DropInheritableCaps(); !result.ok()) { + LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error(); + return kErrorOther; + } + } + + for (const auto& [key, value] : options.envs) { + setenv(key.c_str(), value.c_str(), /*overwrite=*/1); + } + + execv(argv[options.command_pos], argv + options.command_pos); + + std::vector<const char*> command_args(argv + options.command_pos, argv + argc); + PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")"; + UNREACHABLE(); +} diff --git a/libarttools/tools/art_exec_test.cc b/libarttools/tools/art_exec_test.cc new file mode 100644 index 0000000000..a5a0b01ead --- /dev/null +++ b/libarttools/tools/art_exec_test.cc @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/capability.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <csignal> +#include <filesystem> +#include <functional> +#include <string> +#include <utility> + +#include "android-base/file.h" +#include "android-base/logging.h" +#include "android-base/scopeguard.h" +#include "android-base/strings.h" +#include "base/common_art_test.h" +#include "base/file_utils.h" +#include "base/globals.h" +#include "base/macros.h" +#include "base/os.h" +#include "base/scoped_cap.h" +#include "exec_utils.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "system/thread_defs.h" + +#ifdef ART_TARGET_ANDROID +#include "android-modules-utils/sdk_level.h" +#endif + +namespace art { +namespace { + +using ::android::base::make_scope_guard; +using ::android::base::ScopeGuard; +using ::android::base::Split; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::HasSubstr; +using ::testing::Not; + +constexpr uid_t kRoot = 0; +constexpr uid_t kNobody = 9999; + +// This test executes a few Linux system commands such as "ls", which are linked against system +// libraries. In many ART gtests we set LD_LIBRARY_PATH to make the test binaries link to libraries +// from the ART module first, and if that setting is propagated to the system commands they may also +// try to link to those libraries instead of the system ones they are built against. This is +// particularly noticeable when 32-bit tests run on a 64-bit system. Hence we need to set +// LD_LIBRARY_PATH to an empty string here. +// TODO(b/247108425): Remove this when ART gtests no longer use LD_LIBRARY_PATH. +constexpr const char* kEmptyLdLibraryPath = "--env=LD_LIBRARY_PATH="; + +std::string GetArtBin(const std::string& name) { + return ART_FORMAT("{}/bin/{}", GetArtRoot(), name); +} + +std::string GetBin(const std::string& name) { + return ART_FORMAT("{}/bin/{}", GetAndroidRoot(), name); +} + +// Executes the command, waits for it to finish, and keeps it in a waitable state until the current +// scope exits. +std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait( + std::vector<std::string>& args) { + std::vector<char*> execv_args; + execv_args.reserve(args.size() + 1); + for (std::string& arg : args) { + execv_args.push_back(arg.data()); + } + execv_args.push_back(nullptr); + + pid_t pid = fork(); + if (pid == 0) { + execv(execv_args[0], execv_args.data()); + UNREACHABLE(); + } else if (pid > 0) { + siginfo_t info; + CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0); + CHECK_EQ(info.si_code, CLD_EXITED); + CHECK_EQ(info.si_status, 0); + std::function<void()> cleanup([=] { + siginfo_t info; + CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0); + }); + return std::make_pair(pid, make_scope_guard(std::move(cleanup))); + } else { + LOG(FATAL) << "Failed to call fork"; + UNREACHABLE(); + } +} + +// Grants the current process the given root capability. +void SetCap(cap_flag_t flag, cap_value_t value) { + ScopedCap cap(cap_get_proc()); + CHECK_NE(cap.Get(), nullptr); + cap_value_t caps[]{value}; + CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0); + CHECK_EQ(cap_set_proc(cap.Get()), 0); +} + +// Returns true if the given process has the given root capability. +bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) { + ScopedCap cap(cap_get_pid(pid)); + CHECK_NE(cap.Get(), nullptr); + cap_flag_value_t flag_value; + CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0); + return flag_value == CAP_SET; +} + +class ArtExecTest : public testing::Test { + protected: + void SetUp() override { + testing::Test::SetUp(); + if (!kIsTargetAndroid) { + GTEST_SKIP() << "art_exec is for device only"; + } + if (getuid() != kRoot) { + GTEST_SKIP() << "art_exec requires root"; + } + art_exec_bin_ = GetArtBin("art_exec"); + } + + std::string art_exec_bin_; +}; + +TEST_F(ArtExecTest, Command) { + std::string error_msg; + int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg); + ASSERT_EQ(ret, 123) << error_msg; +} + +TEST_F(ArtExecTest, SetTaskProfiles) { +// The condition is always true because ArtExecTest is run on device only. +#ifdef ART_TARGET_ANDROID + if (!android::modules::sdklevel::IsAtLeastU()) { + GTEST_SKIP() << "This test depends on a libartpalette API that is only available on U+"; + } +#endif + + std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; + ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); + ASSERT_GE(scratch_file.GetFd(), 0); + + std::vector<std::string> args{art_exec_bin_, + "--set-task-profile=ProcessCapacityHigh", + kEmptyLdLibraryPath, + "--", + GetBin("sh"), + "-c", + "cat /proc/self/cgroup > " + filename}; + auto [pid, scope_guard] = ScopedExecAndWait(args); + std::string cgroup; + ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup)); + EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n")); +} + +TEST_F(ArtExecTest, SetPriority) { + std::vector<std::string> args{ + art_exec_bin_, "--set-priority=background", kEmptyLdLibraryPath, "--", GetBin("true")}; + auto [pid, scope_guard] = ScopedExecAndWait(args); + EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND); +} + +TEST_F(ArtExecTest, DropCapabilities) { + // Switch to a non-root user, but still keep the CAP_FOWNER capability available and inheritable. + // The order of the following calls matters. + CHECK_EQ(cap_setuid(kNobody), 0); + SetCap(CAP_INHERITABLE, CAP_FOWNER); + SetCap(CAP_EFFECTIVE, CAP_FOWNER); + ASSERT_EQ(cap_set_ambient(CAP_FOWNER, CAP_SET), 0); + + // Make sure the test is set up correctly (i.e., the child process should normally have the + // inherited root capability: CAP_FOWNER). + { + std::vector<std::string> args{art_exec_bin_, kEmptyLdLibraryPath, "--", GetBin("true")}; + auto [pid, scope_guard] = ScopedExecAndWait(args); + ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); + } + + { + std::vector<std::string> args{ + art_exec_bin_, "--drop-capabilities", kEmptyLdLibraryPath, "--", GetBin("true")}; + auto [pid, scope_guard] = ScopedExecAndWait(args); + EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); + } +} + +TEST_F(ArtExecTest, CloseFds) { + std::unique_ptr<File> file1(OS::OpenFileForReading("/dev/zero")); + std::unique_ptr<File> file2(OS::OpenFileForReading("/dev/zero")); + std::unique_ptr<File> file3(OS::OpenFileForReading("/dev/zero")); + ASSERT_NE(file1, nullptr); + ASSERT_NE(file2, nullptr); + ASSERT_NE(file3, nullptr); + + std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; + ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); + ASSERT_GE(scratch_file.GetFd(), 0); + + std::vector<std::string> args{art_exec_bin_, + ART_FORMAT("--keep-fds={}:{}", file3->Fd(), file2->Fd()), + kEmptyLdLibraryPath, + "--", + GetBin("sh"), + "-c", + ART_FORMAT("(" + "readlink /proc/self/fd/{} || echo;" + "readlink /proc/self/fd/{} || echo;" + "readlink /proc/self/fd/{} || echo;" + ") > {}", + file1->Fd(), + file2->Fd(), + file3->Fd(), + filename)}; + + ScopedExecAndWait(args); + + std::string open_fds; + ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds)); + + // `file1` should be closed, while the other two should be open. There's a blank line at the end. + EXPECT_THAT(Split(open_fds, "\n"), ElementsAre(Not("/dev/zero"), "/dev/zero", "/dev/zero", "")); +} + +TEST_F(ArtExecTest, Env) { + std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; + ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); + ASSERT_GE(scratch_file.GetFd(), 0); + + std::vector<std::string> args{art_exec_bin_, + "--env=FOO=BAR", + kEmptyLdLibraryPath, + "--", + GetBin("sh"), + "-c", + "env > " + filename}; + + ScopedExecAndWait(args); + + std::string envs; + ASSERT_TRUE(android::base::ReadFileToString(filename, &envs)); + + EXPECT_THAT(Split(envs, "\n"), Contains("FOO=BAR")); +} + +} // namespace +} // namespace art diff --git a/libarttools/tools/tools.cc b/libarttools/tools/tools.cc index a3a91e81e2..4ec9d9a750 100644 --- a/libarttools/tools/tools.cc +++ b/libarttools/tools/tools.cc @@ -16,12 +16,127 @@ #include "tools.h" +#include <errno.h> +#include <fnmatch.h> + +#include <algorithm> +#include <filesystem> +#include <functional> +#include <string> +#include <string_view> +#include <system_error> +#include <vector> + +#include "android-base/logging.h" +#include "base/macros.h" + namespace art { namespace tools { -std::string getMsg() { - return "hello world!"; +namespace { + +using ::std::placeholders::_1; + +// Returns true if `path_prefix` matches `pattern` or can be a prefix of a path that matches +// `pattern` (i.e., `path_prefix` represents a directory that may contain a file whose path matches +// `pattern`). +bool PartialMatch(const std::filesystem::path& pattern, const std::filesystem::path& path_prefix) { + for (std::filesystem::path::const_iterator pattern_it = pattern.begin(), + path_prefix_it = path_prefix.begin(); + ; // NOLINT + pattern_it++, path_prefix_it++) { + if (path_prefix_it == path_prefix.end()) { + return true; + } + if (pattern_it == pattern.end()) { + return false; + } + if (*pattern_it == "**") { + return true; + } + if (fnmatch(pattern_it->c_str(), path_prefix_it->c_str(), /*flags=*/0) != 0) { + return false; + } + } +} + +bool FullMatchRecursive(const std::filesystem::path& pattern, + std::filesystem::path::const_iterator pattern_it, + const std::filesystem::path& path, + std::filesystem::path::const_iterator path_it, + bool double_asterisk_visited = false) { + if (pattern_it == pattern.end() && path_it == path.end()) { + return true; + } + if (pattern_it == pattern.end()) { + return false; + } + if (*pattern_it == "**") { + DCHECK(!double_asterisk_visited); + std::filesystem::path::const_iterator next_pattern_it = pattern_it; + return FullMatchRecursive( + pattern, ++next_pattern_it, path, path_it, /*double_asterisk_visited=*/true) || + (path_it != path.end() && FullMatchRecursive(pattern, pattern_it, path, ++path_it)); + } + if (path_it == path.end()) { + return false; + } + if (fnmatch(pattern_it->c_str(), path_it->c_str(), /*flags=*/0) != 0) { + return false; + } + return FullMatchRecursive(pattern, ++pattern_it, path, ++path_it); +} + +// Returns true if `path` fully matches `pattern`. +bool FullMatch(const std::filesystem::path& pattern, const std::filesystem::path& path) { + return FullMatchRecursive(pattern, pattern.begin(), path, path.begin()); } +void MatchGlobRecursive(const std::vector<std::filesystem::path>& patterns, + const std::filesystem::path& root_dir, + /*out*/ std::vector<std::string>* results) { + std::error_code ec; + for (auto it = std::filesystem::recursive_directory_iterator( + root_dir, std::filesystem::directory_options::skip_permission_denied, ec); + !ec && it != std::filesystem::end(it); + it.increment(ec)) { + const std::filesystem::directory_entry& entry = *it; + if (std::none_of(patterns.begin(), patterns.end(), std::bind(PartialMatch, _1, entry.path()))) { + // Avoid unnecessary I/O and SELinux denials. + it.disable_recursion_pending(); + continue; + } + std::error_code ec2; + if (entry.is_regular_file(ec2) && + std::any_of(patterns.begin(), patterns.end(), std::bind(FullMatch, _1, entry.path()))) { + results->push_back(entry.path()); + } + if (ec2) { + // It's expected that we don't have permission to stat some dirs/files, and we don't care + // about them. + if (ec2.value() != EACCES) { + LOG(ERROR) << ART_FORMAT("Unable to lstat '{}': {}", entry.path().string(), ec2.message()); + } + continue; + } + } + if (ec) { + LOG(ERROR) << ART_FORMAT("Unable to walk through '{}': {}", root_dir.string(), ec.message()); + } } + +} // namespace + +std::vector<std::string> Glob(const std::vector<std::string>& patterns, std::string_view root_dir) { + std::vector<std::filesystem::path> parsed_patterns; + parsed_patterns.reserve(patterns.size()); + for (std::string_view pattern : patterns) { + parsed_patterns.emplace_back(pattern); + } + std::vector<std::string> results; + MatchGlobRecursive(parsed_patterns, root_dir, &results); + return results; } + +} // namespace tools +} // namespace art diff --git a/libarttools/tools/tools.h b/libarttools/tools/tools.h index 8231f5f74a..c2bcee77a6 100644 --- a/libarttools/tools/tools.h +++ b/libarttools/tools/tools.h @@ -18,11 +18,24 @@ #define ART_LIBARTTOOLS_TOOLS_TOOLS_H_ #include <string> +#include <string_view> +#include <vector> namespace art { namespace tools { -std::string getMsg(); +// Searches in a filesystem, starting from `root_dir`. Returns all regular files (i.e., excluding +// directories, symlinks, etc.) that match at least one pattern in `patterns`. Each pattern is an +// absolute path that contains zero or more wildcards. The scan does not follow symlinks to +// directories. +// +// Supported wildcards are: +// - Those documented in glob(7) +// - '**': Matches zero or more path elements. This is only recognised by itself as a path segment. +// +// For simplicity and efficiency, at most one '**' is allowed. +std::vector<std::string> Glob(const std::vector<std::string>& patterns, + std::string_view root_dir = "/"); } // namespace tools } // namespace art diff --git a/libarttools/tools/tools_test.cc b/libarttools/tools/tools_test.cc index 6eaa8f60bb..2f61181c73 100644 --- a/libarttools/tools/tools_test.cc +++ b/libarttools/tools/tools_test.cc @@ -15,14 +15,101 @@ */ #include "tools.h" + +#include <algorithm> +#include <filesystem> +#include <iterator> + +#include "android-base/file.h" +#include "base/common_art_test.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace art { +namespace tools { +namespace { + +using ::android::base::WriteStringToFile; +using ::testing::UnorderedElementsAre; + +void CreateFile(const std::string& filename) { + std::filesystem::path path(filename); + std::filesystem::create_directories(path.parent_path()); + ASSERT_TRUE(WriteStringToFile(/*content=*/"", filename)); +} + +class ArtToolsTest : public CommonArtTest { + protected: + void SetUp() override { + CommonArtTest::SetUp(); + scratch_dir_ = std::make_unique<ScratchDir>(); + scratch_path_ = scratch_dir_->GetPath(); + // Remove the trailing '/'; + scratch_path_.resize(scratch_path_.length() - 1); + } + + void TearDown() override { + scratch_dir_.reset(); + CommonArtTest::TearDown(); + } + + std::unique_ptr<ScratchDir> scratch_dir_; + std::string scratch_path_; +}; + +TEST_F(ArtToolsTest, Glob) { + CreateFile(scratch_path_ + "/abc/def/000.txt"); + CreateFile(scratch_path_ + "/abc/def/ghi/123.txt"); + CreateFile(scratch_path_ + "/abc/def/ghi/456.txt"); + CreateFile(scratch_path_ + "/abc/def/ghi/456.pdf"); + CreateFile(scratch_path_ + "/abc/def/ghi/jkl/456.txt"); + CreateFile(scratch_path_ + "/789.txt"); + CreateFile(scratch_path_ + "/abc/789.txt"); + CreateFile(scratch_path_ + "/abc/aaa/789.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/789.txt"); + CreateFile(scratch_path_ + "/abc/mno/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/mno/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/mno/ccc/123.txt"); + CreateFile(scratch_path_ + "/pqr/123.txt"); + CreateFile(scratch_path_ + "/abc/pqr/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/pqr/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt"); + CreateFile(scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt"); + + // This symlink will cause infinite recursion. It should not be followed. + std::filesystem::create_directory_symlink(scratch_path_ + "/abc/aaa/bbb/pqr", + scratch_path_ + "/abc/aaa/bbb/pqr/lnk"); + + // This is a directory. It should not be included in the results. + std::filesystem::create_directory(scratch_path_ + "/abc/def/ghi/000.txt"); -class ArtToolsTest : public testing::Test {}; + std::vector<std::string> patterns = { + scratch_path_ + "/abc/def/000.txt", + scratch_path_ + "/abc/def/ghi/*.txt", + scratch_path_ + "/abc/**/789.txt", + scratch_path_ + "/abc/**/mno/*.txt", + scratch_path_ + "/abc/**/pqr/**", + }; -TEST_F(ArtToolsTest, Hello) { - EXPECT_EQ("hello world!", art::tools::getMsg()); + EXPECT_THAT(Glob(patterns, scratch_path_), + UnorderedElementsAre(scratch_path_ + "/abc/def/000.txt", + scratch_path_ + "/abc/def/ghi/123.txt", + scratch_path_ + "/abc/def/ghi/456.txt", + scratch_path_ + "/abc/789.txt", + scratch_path_ + "/abc/aaa/789.txt", + scratch_path_ + "/abc/aaa/bbb/789.txt", + scratch_path_ + "/abc/mno/123.txt", + scratch_path_ + "/abc/aaa/mno/123.txt", + scratch_path_ + "/abc/aaa/bbb/mno/123.txt", + scratch_path_ + "/abc/pqr/123.txt", + scratch_path_ + "/abc/aaa/pqr/123.txt", + scratch_path_ + "/abc/aaa/bbb/pqr/123.txt", + scratch_path_ + "/abc/aaa/bbb/pqr/ccc/123.txt", + scratch_path_ + "/abc/aaa/bbb/pqr/ccc/ddd/123.txt")); } +} // namespace +} // namespace tools } // namespace art diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc index af007d1962..d3bf475d0d 100644 --- a/libprofile/profile/profile_compilation_info.cc +++ b/libprofile/profile/profile_compilation_info.cc @@ -2477,8 +2477,8 @@ bool ProfileCompilationInfo::IsProfileFile(int fd) { } bool ProfileCompilationInfo::UpdateProfileKeys( - const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* updated) { - *updated = false; + const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* matched) { + *matched = false; for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { for (const std::unique_ptr<DexFileData>& dex_data : info_) { if (dex_data->checksum == dex_file->GetLocationChecksum() && @@ -2498,8 +2498,8 @@ bool ProfileCompilationInfo::UpdateProfileKeys( // 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 57e80a5fbd..68177629b0 100644 --- a/libprofile/profile/profile_compilation_info.h +++ b/libprofile/profile/profile_compilation_info.h @@ -655,9 +655,9 @@ class ProfileCompilationInfo { // 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 dc784189f9..81680041b5 100644 --- a/libprofile/profile/profile_compilation_info_test.cc +++ b/libprofile/profile/profile_compilation_info_test.cc @@ -956,9 +956,9 @@ TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOk) { 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 @@ TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkWithAnnotation) { 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, UpdateProfileKeyOkWithAnnotation) { } } -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 @@ TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoUpdate) { 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 @@ TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyFail) { // This will cause the rename to fail because an existing entry would already have that name. AddMethod(&info, dex1_renamed, /*method_idx=*/ 0); - bool updated = false; - ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &updated)); - ASSERT_FALSE(updated); + bool matched = false; + ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &matched)); + ASSERT_FALSE(matched); // Release the ownership as this is held by the test class; for (std::unique_ptr<const DexFile>& dex : dex_files) { diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc index 84ea28d19d..ae2cb3f6a9 100644 --- a/odrefresh/odrefresh_test.cc +++ b/odrefresh/odrefresh_test.cc @@ -33,6 +33,7 @@ #include "arch/instruction_set.h" #include "base/common_art_test.h" #include "base/file_utils.h" +#include "base/macros.h" #include "base/stl_util.h" #include "exec_utils.h" #include "gmock/gmock.h" diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h index 4d2b7336bb..9c9aca9e05 100644 --- a/profman/include/profman/profman_result.h +++ b/profman/include/profman/profman_result.h @@ -57,7 +57,7 @@ class ProfmanResult { // 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 07f9ad9f26..d28ec6513d 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -2065,8 +2065,8 @@ TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKeyNoUpdate) { 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. ProfileCompilationInfo result; diff --git a/profman/profman.cc b/profman/profman.cc index 25f03feacf..a7e4931dd6 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -1905,8 +1905,8 @@ class ProfMan final { // 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 @@ -1915,7 +1915,7 @@ class ProfMan final { if (!result) { return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile; } - return updated ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoUpdate; + return matched ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoMatch; } else { return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile; } diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index ccfcc895dc..fb66377060 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1863,7 +1863,7 @@ class OatFileBackedByVdex final : public OatFileBase { InstructionSetFeatures::FromCppDefines(); SafeMap<std::string, std::string> store; store.Put(OatHeader::kCompilerFilter, CompilerFilter::NameOfFilter(CompilerFilter::kVerify)); - store.Put(OatHeader::kCompilationReasonKey, "vdex"); + store.Put(OatHeader::kCompilationReasonKey, kReasonVdex); store.Put(OatHeader::kConcurrentCopying, gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue); if (context != nullptr) { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 0fa740c1c2..e6a549ce63 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -60,6 +60,10 @@ class FakeOatFile; } // namespace collector } // namespace gc +// A special compilation reason to indicate that only the VDEX file is usable. Keep in sync with +// `ArtConstants::REASON_VDEX` in artd/binder/com/android/server/art/ArtConstants.aidl. +static constexpr const char* kReasonVdex = "vdex"; + // OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can // save even one OatMethodOffsets struct, the more complicated encoding // using a bitmap pays for itself since few classes will have 160 diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files index 821044462a..9d86fb6dd7 100755 --- a/test/utils/regen-test-files +++ b/test/utils/regen-test-files @@ -228,12 +228,10 @@ art_gtest_user_module_names = [ "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", |