Revert "Disable test 426 on redefinition configurations." am: 404e8881c2 am: 6d80c2dce9 am: 1eaabbc72d am: c976852283 am: 754cf11db9
Original change: https://android-review.googlesource.com/c/platform/art/+/2139994
Change-Id: I2af680cea64026272de7c5f45107b338602ef2af
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/artd/Android.bp b/artd/Android.bp
index b645ec6..292f09b 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -27,6 +27,7 @@
defaults: ["art_defaults"],
srcs: [
"artd.cc",
+ "path_utils.cc",
],
shared_libs: [
"libarttools",
@@ -35,6 +36,7 @@
],
static_libs: [
"artd-aidl-ndk",
+ "libc++fs",
],
}
@@ -45,6 +47,7 @@
"artd_main.cc",
],
shared_libs: [
+ "libart",
"libartbase",
],
apex_available: [
@@ -63,8 +66,13 @@
art_cc_defaults {
name: "art_artd_tests_defaults",
defaults: ["artd_defaults"],
+ static_libs: [
+ "libcap",
+ "libgmock",
+ ],
srcs: [
"artd_test.cc",
+ "path_utils_test.cc",
],
}
diff --git a/artd/artd.cc b/artd/artd.cc
index 27a609d..392f587 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -16,16 +16,32 @@
#include "artd.h"
+#include <stdlib.h>
#include <unistd.h>
+#include <cstdint>
+#include <filesystem>
+#include <memory>
#include <string>
+#include <system_error>
+#include <utility>
+#include <vector>
#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/errors.h"
#include "android-base/logging.h"
+#include "android-base/properties.h"
#include "android-base/result.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
#include "android/binder_auto_utils.h"
#include "android/binder_manager.h"
#include "android/binder_process.h"
+#include "base/array_ref.h"
+#include "base/file_utils.h"
+#include "oat_file_assistant.h"
+#include "path_utils.h"
+#include "runtime.h"
#include "tools/tools.h"
namespace art {
@@ -33,12 +49,77 @@
namespace {
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::GetOptimizationStatusResult;
using ::android::base::Error;
+using ::android::base::GetBoolProperty;
using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringPrintf;
using ::ndk::ScopedAStatus;
constexpr const char* kServiceName = "artd";
+constexpr const char* kPhenotypeFlagPrefix = "persist.device_config.runtime_native_boot.";
+constexpr const char* kDalvikVmFlagPrefix = "dalvik.vm.";
+
+Result<std::vector<std::string>> GetBootClassPath() {
+ const char* env_value = getenv("BOOTCLASSPATH");
+ if (env_value == nullptr || strlen(env_value) == 0) {
+ return Errorf("Failed to get environment variable 'BOOTCLASSPATH'");
+ }
+ return Split(env_value, ":");
+}
+
+Result<std::vector<std::string>> GetBootImageLocations(bool deny_art_apex_data_files) {
+ std::string error_msg;
+ std::string android_root = GetAndroidRootSafe(&error_msg);
+ if (!error_msg.empty()) {
+ return Errorf("Failed to get ANDROID_ROOT: {}", error_msg);
+ }
+
+ std::string location_str = GetDefaultBootImageLocation(android_root, deny_art_apex_data_files);
+ return Split(location_str, ":");
+}
+
+bool UseJitZygote() {
+ bool profile_boot_class_path_phenotype =
+ GetBoolProperty(std::string(kPhenotypeFlagPrefix) + "profilebootclasspath",
+ /*default_value=*/false);
+
+ bool profile_boot_class_path =
+ GetBoolProperty(std::string(kDalvikVmFlagPrefix) + "profilebootclasspath",
+ /*default_value=*/profile_boot_class_path_phenotype);
+
+ return profile_boot_class_path;
+}
+
+bool DenyArtApexDataFiles() {
+ return !GetBoolProperty("odsign.verification.success", /*default_value=*/false);
+}
+
+// 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::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) << StringPrintf(
+ "Failed to get the file size of '%s': %s", path.c_str(), ec.message().c_str());
+ }
+ return 0;
+ }
+
+ if (!std::filesystem::remove(path, ec)) {
+ LOG(ERROR) << StringPrintf("Failed to remove '%s': %s", path.c_str(), ec.message().c_str());
+ return 0;
+ }
+
+ return size;
+}
+
} // namespace
ScopedAStatus Artd::isAlive(bool* _aidl_return) {
@@ -46,6 +127,62 @@
return ScopedAStatus::ok();
}
+ScopedAStatus Artd::deleteArtifacts(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) {
+ Result<std::string> oat_path = BuildOatPath(in_artifactsPath);
+ if (!oat_path.ok()) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
+ oat_path.error().message().c_str());
+ }
+
+ *_aidl_return = 0;
+ *_aidl_return += GetSizeAndDeleteFile(*oat_path);
+ *_aidl_return += GetSizeAndDeleteFile(OatPathToVdexPath(*oat_path));
+ *_aidl_return += GetSizeAndDeleteFile(OatPathToArtPath(*oat_path));
+
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getOptimizationStatus(const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::string& in_classLoaderContext,
+ GetOptimizationStatusResult* _aidl_return) {
+ Result<OatFileAssistant::RuntimeOptions> runtime_options = GetRuntimeOptions();
+ if (!runtime_options.ok()) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_STATE,
+ ("Failed to get runtime options: " + runtime_options.error().message()).c_str());
+ }
+
+ std::string error_msg;
+ auto oat_file_assistant = OatFileAssistant::Create(
+ in_dexFile.c_str(),
+ in_instructionSet.c_str(),
+ in_classLoaderContext.c_str(),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ std::make_unique<OatFileAssistant::RuntimeOptions>(std::move(*runtime_options)),
+ &error_msg);
+ if (oat_file_assistant == nullptr) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_STATE, ("Failed to create OatFileAssistant: " + error_msg).c_str());
+ }
+
+ std::string ignored_odex_status;
+ oat_file_assistant->GetOptimizationStatus(&_aidl_return->compilerFilter,
+ &_aidl_return->compilationReason,
+ &_aidl_return->locationDebugString,
+ &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();
+}
+
Result<void> Artd::Start() {
ScopedAStatus status = ScopedAStatus::fromStatus(
AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
@@ -58,5 +195,43 @@
return {};
}
+Result<OatFileAssistant::RuntimeOptions> Artd::GetRuntimeOptions() {
+ // We don't cache this system property because it can change.
+ bool use_jit_zygote = UseJitZygote();
+
+ if (!HasRuntimeOptionsCache()) {
+ OR_RETURN(BuildRuntimeOptionsCache());
+ }
+
+ return OatFileAssistant::RuntimeOptions{
+ .image_locations = cached_boot_image_locations_,
+ .boot_class_path = cached_boot_class_path_,
+ .boot_class_path_locations = cached_boot_class_path_,
+ .use_jit_zygote = use_jit_zygote,
+ .deny_art_apex_data_files = cached_deny_art_apex_data_files_,
+ .apex_versions = cached_apex_versions_,
+ };
+}
+
+Result<void> Artd::BuildRuntimeOptionsCache() {
+ // This system property can only be set by odsign on boot, so it won't change.
+ bool deny_art_apex_data_files = DenyArtApexDataFiles();
+
+ std::vector<std::string> image_locations =
+ OR_RETURN(GetBootImageLocations(deny_art_apex_data_files));
+ std::vector<std::string> boot_class_path = OR_RETURN(GetBootClassPath());
+ std::string apex_versions =
+ Runtime::GetApexVersions(ArrayRef<const std::string>(boot_class_path));
+
+ cached_boot_image_locations_ = std::move(image_locations);
+ cached_boot_class_path_ = std::move(boot_class_path);
+ cached_apex_versions_ = std::move(apex_versions);
+ cached_deny_art_apex_data_files_ = deny_art_apex_data_files;
+
+ return {};
+}
+
+bool Artd::HasRuntimeOptionsCache() const { return !cached_boot_image_locations_.empty(); }
+
} // namespace artd
} // namespace art
diff --git a/artd/artd.h b/artd/artd.h
index f01d9a8..2a05267 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -17,9 +17,13 @@
#ifndef ART_ARTD_ARTD_H_
#define ART_ARTD_ARTD_H_
+#include <string>
+#include <vector>
+
#include "aidl/com/android/server/art/BnArtd.h"
#include "android-base/result.h"
#include "android/binder_auto_utils.h"
+#include "oat_file_assistant.h"
namespace art {
namespace artd {
@@ -28,7 +32,29 @@
public:
ndk::ScopedAStatus isAlive(bool* _aidl_return) override;
+ ndk::ScopedAStatus deleteArtifacts(
+ const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+ int64_t* _aidl_return) override;
+
+ ndk::ScopedAStatus getOptimizationStatus(
+ const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::string& in_classLoaderContext,
+ aidl::com::android::server::art::GetOptimizationStatusResult* _aidl_return) override;
+
android::base::Result<void> Start();
+
+ private:
+ android::base::Result<OatFileAssistant::RuntimeOptions> GetRuntimeOptions();
+
+ android::base::Result<void> BuildRuntimeOptionsCache();
+
+ bool HasRuntimeOptionsCache() const;
+
+ std::vector<std::string> cached_boot_image_locations_;
+ std::vector<std::string> cached_boot_class_path_;
+ std::string cached_apex_versions_;
+ bool cached_deny_art_apex_data_files_;
};
} // namespace artd
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 14bccc2..f16a58e 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -16,26 +16,94 @@
#include "artd.h"
+#include <sys/capability.h>
+
+#include <filesystem>
+#include <functional>
#include <memory>
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/scopeguard.h"
#include "android/binder_interface_utils.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::ArtifactsPath;
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+using ::testing::_;
+using ::testing::ContainsRegex;
+using ::testing::HasSubstr;
+using ::testing::MockFunction;
+
+// A wrapper of `cap_t` that automatically calls `cap_free`.
+class ScopedCap {
+ public:
+ explicit ScopedCap(cap_t cap) : cap_(cap) { CHECK_NE(cap, nullptr); }
+
+ ScopedCap(ScopedCap&& other) : cap_(std::exchange(other.cap_, nullptr)) {}
+
+ ~ScopedCap() {
+ if (cap_ != nullptr) {
+ CHECK_EQ(cap_free(cap_), 0);
+ }
+ }
+
+ cap_t Get() const { return cap_; }
+
+ private:
+ cap_t cap_;
+};
+
+// Temporarily drops all root capabilities when the test is run as root. This is a noop otherwise.
+ScopeGuard<std::function<void()>> ScopedUnroot() {
+ ScopedCap old_cap(cap_get_proc());
+ ScopedCap new_cap(cap_dup(old_cap.Get()));
+ CHECK_EQ(cap_clear_flag(new_cap.Get(), CAP_EFFECTIVE), 0);
+ CHECK_EQ(cap_set_proc(new_cap.Get()), 0);
+ // `old_cap` is actually not shared with anyone else, but we have to wrap it with a `shared_ptr`
+ // because `std::function` requires captures to be copyable.
+ return make_scope_guard([old_cap = std::make_shared<ScopedCap>(std::move(old_cap))]() {
+ CHECK_EQ(cap_set_proc(old_cap->Get()), 0);
+ });
+}
+
+// Temporarily drops all permission on a file/directory.
+ScopeGuard<std::function<void()>> ScopedInaccessible(const std::string& path) {
+ std::filesystem::perms old_perms = std::filesystem::status(path).permissions();
+ std::filesystem::permissions(path, std::filesystem::perms::none);
+ return make_scope_guard([=]() { std::filesystem::permissions(path, old_perms); });
+}
+
+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));
+ });
+}
+
class ArtdTest : public CommonArtTest {
protected:
void SetUp() override {
CommonArtTest::SetUp();
artd_ = ndk::SharedRefBase::make<Artd>();
+ scratch_dir_ = std::make_unique<ScratchDir>();
}
- void TearDown() override { CommonArtTest::TearDown(); }
+ void TearDown() override {
+ scratch_dir_.reset();
+ CommonArtTest::TearDown();
+ }
std::shared_ptr<Artd> artd_;
+ std::unique_ptr<ScratchDir> scratch_dir_;
+ MockFunction<android::base::LogFunction> mock_logger_;
};
TEST_F(ArtdTest, isAlive) {
@@ -44,6 +112,130 @@
EXPECT_TRUE(result);
}
+TEST_F(ArtdTest, deleteArtifacts) {
+ std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64";
+ std::filesystem::create_directories(oat_dir);
+ android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes.
+ android::base::WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes.
+ android::base::WriteStringToFile("a", oat_dir + "/b.art"); // 1 byte.
+
+ int64_t result = -1;
+ EXPECT_TRUE(artd_
+ ->deleteArtifacts(
+ ArtifactsPath{
+ .dexPath = scratch_dir_->GetPath() + "/a/b.apk",
+ .isa = "arm64",
+ .isInDalvikCache = false,
+ },
+ &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 = dalvik_cache_ + "/arm64";
+ std::filesystem::create_directories(oat_dir);
+ android::base::WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex"); // 4 bytes.
+ android::base::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(
+ ArtifactsPath{
+ .dexPath = android_data_ + "/a/b.apk",
+ .isa = "arm64",
+ .isInDalvikCache = false,
+ },
+ &result)
+ .isOk());
+ EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsPermissionDenied) {
+ std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64";
+ std::filesystem::create_directories(oat_dir);
+ android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes.
+ android::base::WriteStringToFile("ab", oat_dir + "/b.vdex"); // 2 bytes.
+ android::base::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(
+ ArtifactsPath{
+ .dexPath = scratch_dir_->GetPath() + "/a/b.apk",
+ .isa = "arm64",
+ .isInDalvikCache = false,
+ },
+ &result)
+ .isOk());
+ EXPECT_EQ(result, 0);
+}
+
+TEST_F(ArtdTest, deleteArtifactsFileIsDir) {
+ // VDEX file is a directory.
+ std::string oat_dir = scratch_dir_->GetPath() + "/a/oat/arm64";
+ std::filesystem::create_directories(oat_dir);
+ std::filesystem::create_directories(oat_dir + "/b.vdex");
+ android::base::WriteStringToFile("abcd", oat_dir + "/b.odex"); // 4 bytes.
+ android::base::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(
+ ArtifactsPath{
+ .dexPath = scratch_dir_->GetPath() + "/a/b.apk",
+ .isa = "arm64",
+ .isInDalvikCache = false,
+ },
+ &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"));
+}
+
} // namespace
} // namespace artd
} // namespace art
diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp
index ad8474f..b6fd5b8 100644
--- a/artd/binder/Android.bp
+++ b/artd/binder/Android.bp
@@ -31,6 +31,10 @@
backend: {
java: {
enabled: true,
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
},
cpp: {
enabled: false,
@@ -40,9 +44,7 @@
apex_available: [
"com.android.art",
"com.android.art.debug",
- "com.android.compos",
],
- min_sdk_version: "31",
},
},
unstable: true,
@@ -50,4 +52,5 @@
"//system/tools/aidl/build",
"//art:__subpackages__",
],
+ min_sdk_version: "31",
}
diff --git a/artd/binder/com/android/server/art/ArtifactsPath.aidl b/artd/binder/com/android/server/art/ArtifactsPath.aidl
new file mode 100644
index 0000000..f69b439
--- /dev/null
+++ b/artd/binder/com/android/server/art/ArtifactsPath.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * Represents the path to the optimized artifacts of a dex file (i.e., ART, OAT, and VDEX files).
+ *
+ * @hide
+ */
+parcelable ArtifactsPath {
+ /** The absolute path starting with '/' to the dex file (i.e., APK or JAR file). */
+ @utf8InCpp String dexPath;
+ /** The instruction set of the optimized artifacts. */
+ @utf8InCpp String isa;
+ /** Whether the optimized artifacts are in the dalvik-cache folder. */
+ boolean isInDalvikCache;
+}
diff --git a/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl b/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl
new file mode 100644
index 0000000..99a2e37
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.getOptimizationStatus}. Each field corresponds to a field in
+ * {@code com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus}.
+ *
+ * @hide
+ */
+parcelable GetOptimizationStatusResult {
+ @utf8InCpp String compilerFilter;
+ @utf8InCpp String compilationReason;
+ @utf8InCpp String locationDebugString;
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 58b2aae..a1df266 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -16,8 +16,16 @@
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. */
+ long deleteArtifacts(in com.android.server.art.ArtifactsPath artifactsPath);
+
+ /** Returns the optimization status of a dex file. */
+ com.android.server.art.GetOptimizationStatusResult getOptimizationStatus(
+ @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+ @utf8InCpp String classLoaderContext);
}
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
new file mode 100644
index 0000000..c4d9031
--- /dev/null
+++ b/artd/path_utils.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "path_utils.h"
+
+#include <filesystem>
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/errors.h"
+#include "android-base/result.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
+#include "base/file_utils.h"
+#include "oat_file_assistant.h"
+
+namespace art {
+namespace artd {
+
+namespace {
+
+using ::aidl::com::android::server::art::ArtifactsPath;
+using ::android::base::EndsWith;
+using ::android::base::Error;
+using ::android::base::Result;
+
+Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
+ if (path_str.empty()) {
+ return Errorf("Path is empty");
+ }
+ 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> ValidateDexPath(const std::string& dex_path) {
+ OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+ if (!EndsWith(dex_path, ".apk") && !EndsWith(dex_path, ".jar")) {
+ return Errorf("Dex path '{}' has an invalid extension", dex_path);
+ }
+ return {};
+}
+
+} // namespace
+
+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.c_str());
+ }
+
+ 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;
+ }
+}
+
+std::string OatPathToVdexPath(const std::string& oat_path) {
+ return ReplaceFileExtension(oat_path, "vdex");
+}
+
+std::string OatPathToArtPath(const std::string& oat_path) {
+ return ReplaceFileExtension(oat_path, "art");
+}
+
+} // namespace artd
+} // namespace art
diff --git a/artd/path_utils.h b/artd/path_utils.h
new file mode 100644
index 0000000..970143a
--- /dev/null
+++ b/artd/path_utils.h
@@ -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.
+ */
+
+#ifndef ART_ARTD_PATH_UTILS_H_
+#define ART_ARTD_PATH_UTILS_H_
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+
+namespace art {
+namespace artd {
+
+// 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.
+std::string OatPathToVdexPath(const std::string& oat_path);
+
+// Returns the path to the ART file that corresponds to the OAT file.
+std::string OatPathToArtPath(const std::string& oat_path);
+
+} // namespace artd
+} // namespace art
+
+#endif // ART_ARTD_PATH_UTILS_H_
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
new file mode 100644
index 0000000..9ce40c5
--- /dev/null
+++ b/artd/path_utils_test.cc
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::WithMessage;
+
+class PathUtilsTest : public CommonArtTest {};
+
+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, BuildOatPathInvalidDexExtension) {
+ EXPECT_THAT(BuildOatPath(ArtifactsPath{
+ .dexPath = "/a/b.invalid", .isa = "arm64", .isInDalvikCache = false}),
+ HasError(WithMessage("Dex path '/a/b.invalid' has an invalid extension")));
+}
+
+TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) {
+ EXPECT_THAT(BuildOatPath(
+ ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}),
+ HasError(WithMessage("Instruction set 'invalid' is invalid")));
+}
+
+TEST_F(PathUtilsTest, OatPathToVdexPath) {
+ EXPECT_EQ(OatPathToVdexPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.vdex");
+}
+
+TEST_F(PathUtilsTest, OatPathToArtPath) {
+ EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
+}
+
+} // namespace
+} // namespace artd
+} // namespace art
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 0080ae4..d383f64 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -133,11 +133,9 @@
art_sigchain_tests \
ART_TEST_MODULES_TARGET := $(ART_TEST_MODULES_COMMON) \
+ art_artd_tests \
art_odrefresh_tests \
-# TODO(b/235464166): art_artd_tests doesn't work on master-art because of the dependency on
-# libbinder_ndk.
-
ART_TEST_MODULES_HOST := $(ART_TEST_MODULES_COMMON)
ART_TARGET_GTEST_NAMES := $(foreach tm,$(ART_TEST_MODULES_TARGET),\
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 07fd103..9ad5eee 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -400,9 +400,7 @@
// ART gtests with dependencies on internal ART APEX libraries.
art_gtests = [
- // TODO(b/235464166): art_artd_tests doesn't work on master-art because of
- // the dependency on libbinder_ndk.
- // "art_artd_tests",
+ "art_artd_tests",
"art_cmdline_tests",
"art_compiler_tests",
"art_dex2oat_tests",
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index e180464..1813913 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -670,9 +670,7 @@
def run(self):
# Check ART test binaries.
- # TODO(b/235464166): art_artd_tests doesn't work on master-art because of
- # the dependency on libbinder_ndk.
- # self._checker.check_art_test_executable('art_artd_tests')
+ self._checker.check_art_test_executable('art_artd_tests')
self._checker.check_art_test_executable('art_cmdline_tests')
self._checker.check_art_test_executable('art_compiler_tests')
self._checker.check_art_test_executable('art_dex2oat_tests')
diff --git a/build/apex/manifest-art.json b/build/apex/manifest-art.json
index bf45076..c41469b 100644
--- a/build/apex/manifest-art.json
+++ b/build/apex/manifest-art.json
@@ -1,6 +1,6 @@
{
"name": "com.android.art",
- "version": 339990000,
+ "version": 990090000,
"provideNativeLibs": [
"libjdwp.so"
],
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 8805430..7bd4c34 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -82,6 +82,8 @@
"java/**/*.java",
],
static_libs: [
+ "artd-aidl-java",
+ "modules-utils-shell-command-handler",
],
plugins: ["java_api_finder"],
jarjar_rules: "jarjar-rules.txt",
@@ -127,11 +129,15 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.runner",
+ "artd-aidl-java",
"mockito-target-minus-junit4",
"service-art.impl",
+ // Statically link against system server to allow us to mock system
+ // server APIs. This won't work on master-art, but it's fine because we
+ // don't run this test on master-art.
+ "services.core",
],
- sdk_version: "system_server_current",
min_sdk_version: "31",
test_suites: ["general-tests"],
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index c7844e0..d35f8c7 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -3,6 +3,38 @@
public final class ArtManagerLocal {
ctor public ArtManagerLocal();
+ method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.snapshot.PackageDataSnapshot, @NonNull String);
+ method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.snapshot.PackageDataSnapshot, @NonNull String, int);
+ method @NonNull public com.android.server.art.model.OptimizationStatus getOptimizationStatus(@NonNull com.android.server.pm.snapshot.PackageDataSnapshot, @NonNull String);
+ method @NonNull public com.android.server.art.model.OptimizationStatus getOptimizationStatus(@NonNull com.android.server.pm.snapshot.PackageDataSnapshot, @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[]);
+ }
+
+}
+
+package com.android.server.art.model {
+
+ public class ArtFlags {
+ method public static int defaultDeleteFlags();
+ method public static int defaultGetStatusFlags();
+ field public static final int FLAG_FOR_PRIMARY_DEX = 1; // 0x1
+ field public static final int FLAG_FOR_SECONDARY_DEX = 2; // 0x2
+ }
+
+ public class DeleteResult {
+ method public long getFreedBytes();
+ }
+
+ public class OptimizationStatus {
+ method @NonNull public java.util.List<com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus> getDexFileOptimizationStatuses();
+ }
+
+ public static class OptimizationStatus.DexFileOptimizationStatus {
+ method @NonNull public String getCompilationReason();
+ method @NonNull public String getCompilerFilter();
+ method @NonNull public String getDexFile();
+ method @NonNull public String getInstructionSet();
+ method @NonNull public String getLocationDebugString();
}
}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 64aec7b..3a6bdc9 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,16 +16,251 @@
package com.android.server.art;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
+import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.model.ArtFlags.DeleteFlags;
+import static com.android.server.art.model.ArtFlags.GetStatusFlags;
+import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
+
+import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.IArtd;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.OptimizationStatus;
+import com.android.server.art.wrapper.AndroidPackageApi;
+import com.android.server.art.wrapper.PackageManagerLocal;
+import com.android.server.art.wrapper.PackageState;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* 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";
- public ArtManagerLocal() {}
+ @NonNull private final Injector mInjector;
+
+ public ArtManagerLocal() {
+ this(new Injector());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public ArtManagerLocal(Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
+ * Handles `cmd package art` sub-command.
+ *
+ * For debugging purposes only. Intentionally enforces root access to limit the usage.
+ *
+ * Note: This method is not an override of {@link Binder#handleShellCommand} because ART
+ * services does not publish a binder. Instead, it handles the `art` sub-command forwarded by
+ * the `package` service. The semantics of the parameters are the same as {@link
+ * Binder#handleShellCommand}.
+ *
+ * @return zero on success, non-zero on internal error (e.g., I/O error)
+ * @throws SecurityException if the caller is not root
+ * @throws IllegalArgumentException if the arguments are illegal
+ * @see ArtShellCommand#onHelp()
+ */
+ public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return new ArtShellCommand(this, mInjector.getPackageManagerLocal())
+ .exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
+ err.getFileDescriptor(), args);
+ }
+
+ /**
+ * Deletes optimized artifacts of a package.
+ *
+ * @throws IllegalArgumentException if the package is not found or the flags are illegal
+ * @throws IllegalStateException if an internal error occurs
+ */
+ @NonNull
+ public DeleteResult deleteOptimizedArtifacts(
+ @NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
+ return deleteOptimizedArtifacts(snapshot, packageName, ArtFlags.defaultDeleteFlags());
+ }
+
+ /**
+ * Same as above, but allows to specify flags.
+ *
+ * @see #deleteOptimizedArtifacts(PackageDataSnapshot, String)
+ */
+ @NonNull
+ public DeleteResult deleteOptimizedArtifacts(@NonNull PackageDataSnapshot snapshot,
+ @NonNull String packageName, @DeleteFlags int flags) {
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+ && (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
+ throw new IllegalArgumentException("Nothing to delete");
+ }
+
+ PackageState pkgState = getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackageApi pkg = getPackageOrThrow(pkgState);
+
+ try {
+ long freedBytes = 0;
+
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
+ for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
+ if (!dexInfo.hasCode()) {
+ continue;
+ }
+ for (String isa : Utils.getAllIsas(pkgState)) {
+ freedBytes += mInjector.getArtd().deleteArtifacts(
+ Utils.buildArtifactsPath(dexInfo.dexPath(), isa, isInDalvikCache));
+ }
+ }
+ }
+
+ if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ // TODO(jiakaiz): Implement this.
+ throw new UnsupportedOperationException(
+ "Deleting artifacts of secondary dex'es is not implemented yet");
+ }
+
+ return new DeleteResult(freedBytes);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ /**
+ * Returns the optimization status of a package.
+ *
+ * @throws IllegalArgumentException if the package is not found or the flags are illegal
+ * @throws IllegalStateException if an internal error occurs
+ */
+ @NonNull
+ public OptimizationStatus getOptimizationStatus(
+ @NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
+ return getOptimizationStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
+ }
+
+ /**
+ * Same as above, but allows to specify flags.
+ *
+ * @see #getOptimizationStatus(PackageDataSnapshot, String)
+ */
+ @NonNull
+ public OptimizationStatus getOptimizationStatus(@NonNull PackageDataSnapshot 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 = getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackageApi pkg = getPackageOrThrow(pkgState);
+
+ try {
+ List<DexFileOptimizationStatus> statuses = new ArrayList<>();
+
+ if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ for (DetailedPrimaryDexInfo dexInfo :
+ PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+ if (!dexInfo.hasCode()) {
+ continue;
+ }
+ for (String isa : Utils.getAllIsas(pkgState)) {
+ GetOptimizationStatusResult result =
+ mInjector.getArtd().getOptimizationStatus(
+ dexInfo.dexPath(), isa, dexInfo.classLoaderContext());
+ statuses.add(new DexFileOptimizationStatus(dexInfo.dexPath(), isa,
+ result.compilerFilter, result.compilationReason,
+ result.locationDebugString));
+ }
+ }
+ }
+
+ if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ // TODO(jiakaiz): Implement this.
+ throw new UnsupportedOperationException(
+ "Getting optimization status of secondary dex'es is not implemented yet");
+ }
+
+ return new OptimizationStatus(statuses);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ private PackageState getPackageStateOrThrow(
+ @NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
+ PackageState pkgState = mInjector.getPackageManagerLocal().getPackageState(
+ snapshot, Binder.getCallingUid(), packageName);
+ if (pkgState == null) {
+ throw new IllegalArgumentException("Package not found: " + packageName);
+ }
+ return pkgState;
+ }
+
+ private AndroidPackageApi getPackageOrThrow(@NonNull PackageState pkgState) {
+ AndroidPackageApi pkg = pkgState.getAndroidPackage();
+ if (pkg == null) {
+ throw new IllegalStateException("Unable to get package " + pkgState.getPackageName());
+ }
+ return pkg;
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ private final PackageManagerLocal mPackageManagerLocal;
+
+ Injector() {
+ PackageManagerLocal packageManagerLocal = null;
+ try {
+ packageManagerLocal = PackageManagerLocal.getInstance();
+ } catch (Exception e) {
+ // This is not a serious error. The reflection-based approach can be broken in some
+ // cases. This is fine because ART services is under development and no one depends
+ // on it.
+ // TODO(b/177273468): Make this a serious error when we switch to using the real
+ // APIs.
+ Log.w(TAG, "Unable to get fake PackageManagerLocal", e);
+ }
+ mPackageManagerLocal = packageManagerLocal;
+ }
+
+ public PackageManagerLocal getPackageManagerLocal() {
+ return mPackageManagerLocal;
+ }
+
+ public IArtd getArtd() {
+ IArtd artd = IArtd.Stub.asInterface(ServiceManager.waitForService("artd"));
+ if (artd == null) {
+ throw new IllegalStateException("Unable to connect to artd");
+ }
+ return artd;
+ }
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
new file mode 100644
index 0000000..9a49aae
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.OptimizationStatus.DexFileOptimizationStatus;
+
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.OptimizationStatus;
+import com.android.server.art.wrapper.PackageManagerLocal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+
+import java.io.PrintWriter;
+
+/**
+ * This class handles ART shell commands.
+ *
+ * @hide
+ */
+public final class ArtShellCommand extends BasicShellCommandHandler {
+ private static final String TAG = "ArtShellCommand";
+
+ private final ArtManagerLocal mArtManagerLocal;
+ private final PackageManagerLocal mPackageManagerLocal;
+
+ public ArtShellCommand(
+ ArtManagerLocal artManagerLocal, PackageManagerLocal packageManagerLocal) {
+ mArtManagerLocal = artManagerLocal;
+ mPackageManagerLocal = packageManagerLocal;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ enforceRoot();
+ PrintWriter pw = getOutPrintWriter();
+ PackageDataSnapshot snapshot = mPackageManagerLocal.snapshot();
+ switch (cmd) {
+ case "delete-optimized-artifacts":
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(
+ snapshot, getNextArgRequired(), ArtFlags.defaultDeleteFlags());
+ pw.printf("Freed %d bytes\n", result.getFreedBytes());
+ return 0;
+ case "get-optimization-status":
+ OptimizationStatus optimizationStatus = mArtManagerLocal.getOptimizationStatus(
+ snapshot, getNextArgRequired(), ArtFlags.defaultGetStatusFlags());
+ for (DexFileOptimizationStatus status :
+ optimizationStatus.getDexFileOptimizationStatuses()) {
+ pw.printf("dexFile = %s, instructionSet = %s, compilerFilter = %s, "
+ + "compilationReason = %s, locationDebugString = %s\n",
+ status.getDexFile(), status.getInstructionSet(),
+ status.getCompilerFilter(), status.getCompilationReason(),
+ status.getLocationDebugString());
+ }
+ return 0;
+ default:
+ // Handles empty, help, and invalid commands.
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("ART service commands.");
+ pw.println("Note: The commands are used for internal debugging purposes only. There are no "
+ + "stability guarantees for them.");
+ pw.println("");
+ pw.println("Usage: cmd package art [ARGS]...");
+ pw.println("");
+ pw.println("Supported commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ // TODO(jiakaiz): Also do operations for secondary dex'es by default.
+ pw.println(" delete-optimized-artifacts <package-name>");
+ pw.println(" Delete the optimized artifacts of a package.");
+ pw.println(" By default, the command only deletes the optimized artifacts of primary "
+ + "dex'es.");
+ pw.println(" get-optimization-status <package-name>");
+ pw.println(" Print the optimization status of a package.");
+ pw.println(" By default, the command only prints the optimization status of primary "
+ + "dex'es.");
+ }
+
+ private void enforceRoot() {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.ROOT_UID) {
+ throw new SecurityException("ART service shell commands need root access");
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/LoggingArtd.java b/libartservice/service/java/com/android/server/art/LoggingArtd.java
new file mode 100644
index 0000000..811cb6f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/LoggingArtd.java
@@ -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.
+ */
+
+package com.android.server.art;
+
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * An implementation of artd that logs the artd calls for debugging purposes.
+ *
+ * @hide
+ */
+public class LoggingArtd implements IArtd {
+ private static final String TAG = "LoggingArtd";
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public long deleteArtifacts(ArtifactsPath artifactsPath) {
+ Log.i(TAG, "deleteArtifacts " + artifactsPathToString(artifactsPath));
+ return 0;
+ }
+
+ @Override
+ public GetOptimizationStatusResult getOptimizationStatus(
+ String dexFile, String instructionSet, String classLoaderContext) {
+ Log.i(TAG,
+ "getOptimizationStatus " + dexFile + ", " + instructionSet + ", "
+ + classLoaderContext);
+ return new GetOptimizationStatusResult();
+ }
+
+ private String artifactsPathToString(ArtifactsPath artifactsPath) {
+ return String.format("ArtifactsPath{dexPath = \"%s\", isa = \"%s\", isInDalvikCache = %s}",
+ artifactsPath.dexPath, artifactsPath.isa,
+ String.valueOf(artifactsPath.isInDalvikCache));
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
new file mode 100644
index 0000000..d6b8a59
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.Immutable;
+import com.android.server.art.wrapper.AndroidPackageApi;
+import com.android.server.art.wrapper.PackageState;
+import com.android.server.art.wrapper.SharedLibraryInfo;
+
+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.stream.Collectors;
+
+/** @hide */
+public class PrimaryDexUtils {
+ private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName();
+
+ /**
+ * Returns the basic information about all primary dex files belonging to the package. The
+ * return value is a list where the entry at index 0 is the information about the base APK, and
+ * the entry at index i is the information about the (i-1)-th split APK.
+ */
+ @NonNull
+ public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackageApi 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 AndroidPackageApi pkg) {
+ return getDetailedDexInfoImpl(pkgState, pkg)
+ .stream()
+ .map(builder -> builder.buildDetailed())
+ .collect(Collectors.toList());
+ }
+
+ @NonNull
+ private static List<PrimaryDexInfoBuilder> getDexInfoImpl(@NonNull AndroidPackageApi pkg) {
+ List<PrimaryDexInfoBuilder> dexInfos = new ArrayList<>();
+
+ PrimaryDexInfoBuilder baseInfo = new PrimaryDexInfoBuilder(pkg.getBaseApkPath());
+ baseInfo.mHasCode = pkg.isHasCode();
+ baseInfo.mIsBaseApk = true;
+ dexInfos.add(baseInfo);
+
+ String[] splitNames = pkg.getSplitNames();
+ String[] splitCodePaths = pkg.getSplitCodePaths();
+ int[] splitFlags = pkg.getSplitFlags();
+
+ for (int i = 0; i < splitNames.length; i++) {
+ PrimaryDexInfoBuilder splitInfo = new PrimaryDexInfoBuilder(splitCodePaths[i]);
+ splitInfo.mHasCode =
+ splitFlags != null && (splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
+ splitInfo.mSplitIndex = i;
+ splitInfo.mSplitName = splitNames[i];
+ dexInfos.add(splitInfo);
+ }
+
+ return dexInfos;
+ }
+
+ @NonNull
+ private static List<PrimaryDexInfoBuilder> getDetailedDexInfoImpl(
+ @NonNull PackageState pkgState, @NonNull AndroidPackageApi pkg) {
+ List<PrimaryDexInfoBuilder> dexInfos = getDexInfoImpl(pkg);
+
+ PrimaryDexInfoBuilder baseApk = dexInfos.get(0);
+ assert baseApk.mIsBaseApk;
+ baseApk.mClassLoaderName = pkg.getClassLoaderName();
+ File baseDexFile = new File(baseApk.mDexPath);
+ baseApk.mRelativeDexPath = baseDexFile.getName();
+
+ // Shared libraries are the dependencies of the base APK.
+ baseApk.mSharedLibrariesContext = encodeSharedLibraries(pkgState.getUsesLibraryInfos());
+
+ String[] splitClassLoaderNames = pkg.getSplitClassLoaderNames();
+ SparseArray<int[]> splitDependencies = pkg.getSplitDependencies();
+ boolean isIsolatedSplitLoading =
+ pkg.isIsolatedSplitLoading() && !Utils.isEmpty(splitDependencies);
+
+ for (int i = 1; i < dexInfos.size(); i++) {
+ assert dexInfos.get(i).mSplitIndex == i - 1;
+ File splitDexFile = new File(dexInfos.get(i).mDexPath);
+ if (!splitDexFile.getParent().equals(baseDexFile.getParent())) {
+ throw new IllegalStateException(
+ "Split APK and base APK are in different directories: "
+ + splitDexFile.getParent() + " != " + baseDexFile.getParent());
+ }
+ dexInfos.get(i).mRelativeDexPath = splitDexFile.getName();
+ if (isIsolatedSplitLoading && dexInfos.get(i).mHasCode) {
+ dexInfos.get(i).mClassLoaderName =
+ splitClassLoaderNames[dexInfos.get(i).mSplitIndex];
+
+ // Keys and values of `splitDependencies` are `split index + 1` for split APK or 0
+ // for base APK, so they can be regarded as indices to `dexInfos`.
+ int[] dependencies = splitDependencies.get(i);
+ 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.
+ dexInfos.get(i).mSplitDependency = dexInfos.get(dependencies[0]);
+ }
+ }
+ }
+
+ 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.mHasCode) {
+ 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.mHasCode) {
+ 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<SharedLibraryInfo> sharedLibraries) {
+ if (Utils.isEmpty(sharedLibraries)) {
+ return null;
+ }
+ return sharedLibraries.stream()
+ .map(library
+ -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(),
+ null /* parentContext */,
+ encodeSharedLibraries(library.getDependencies())))
+ .collect(Collectors.joining("#", "{", "}"));
+ }
+
+ /** Basic information about a primary dex file (either the base APK or a split APK). */
+ @Immutable
+ public static class PrimaryDexInfo {
+ private final @NonNull String mDexPath;
+ private final boolean mHasCode;
+ private final boolean mIsBaseApk;
+ private final int mSplitIndex;
+ private final @Nullable String mSplitName;
+
+ PrimaryDexInfo(@NonNull String dexPath, boolean hasCode, boolean isBaseApk, int splitIndex,
+ @Nullable String splitName) {
+ mDexPath = dexPath;
+ mHasCode = hasCode;
+ mIsBaseApk = isBaseApk;
+ mSplitIndex = splitIndex;
+ mSplitName = splitName;
+ }
+
+ /** The path to the dex file. */
+ public @NonNull String dexPath() {
+ return mDexPath;
+ }
+
+ /** True if the dex file has code. */
+ public boolean hasCode() {
+ return mHasCode;
+ }
+
+ /** True if the dex file is the base APK. */
+ public boolean isBaseApk() {
+ return mIsBaseApk;
+ }
+
+ /** The index to {@link AndroidPackageApi#getSplitNames()}, or -1 for base APK. */
+ public int splitIndex() {
+ return mSplitIndex;
+ }
+
+ /** The name of the split, or null for base APK. */
+ public @Nullable String splitName() {
+ return mSplitName;
+ }
+ }
+
+ /**
+ * 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 {
+ private final @Nullable String mClassLoaderContext;
+
+ DetailedPrimaryDexInfo(@NonNull String dexPath, boolean hasCode, boolean isBaseApk,
+ int splitIndex, @Nullable String splitName, @Nullable String classLoaderContext) {
+ super(dexPath, hasCode, isBaseApk, splitIndex, splitName);
+ 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 String mDexPath;
+ boolean mHasCode = false;
+ boolean mIsBaseApk = false;
+ int mSplitIndex = -1;
+ @Nullable String mSplitName = null;
+ @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 String dexPath) {
+ mDexPath = dexPath;
+ }
+
+ PrimaryDexInfo build() {
+ return new PrimaryDexInfo(mDexPath, mHasCode, mIsBaseApk, mSplitIndex, mSplitName);
+ }
+
+ DetailedPrimaryDexInfo buildDetailed() {
+ return new DetailedPrimaryDexInfo(
+ mDexPath, mHasCode, mIsBaseApk, mSplitIndex, mSplitName, mClassLoaderContext);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
new file mode 100644
index 0000000..9c67a0f
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.SparseArray;
+
+import com.android.server.art.ArtifactsPath;
+import com.android.server.art.wrapper.PackageManagerLocal;
+import com.android.server.art.wrapper.PackageState;
+
+import dalvik.system.VMRuntime;
+
+import java.util.Collection;
+import java.util.List;
+
+/** @hide */
+public final class Utils {
+ 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;
+ }
+
+ @NonNull
+ public static List<String> getAllIsas(@NonNull PackageState pkgState) {
+ String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
+ String secondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+ if (primaryCpuAbi != null) {
+ if (secondaryCpuAbi != null) {
+ return List.of(VMRuntime.getInstructionSet(primaryCpuAbi),
+ VMRuntime.getInstructionSet(secondaryCpuAbi));
+ }
+ return List.of(VMRuntime.getInstructionSet(primaryCpuAbi));
+ }
+ return List.of();
+ }
+
+ @NonNull
+ public static ArtifactsPath buildArtifactsPath(
+ @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) {
+ ArtifactsPath artifactsPath = new ArtifactsPath();
+ artifactsPath.dexPath = dexPath;
+ artifactsPath.isa = isa;
+ artifactsPath.isInDalvikCache = isInDalvikCache;
+ return artifactsPath;
+ }
+
+ public static boolean isInDalvikCache(@NonNull PackageState pkg) {
+ return pkg.isSystem() && !pkg.isUpdatedSystemApp();
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/ArtFlags.java b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
new file mode 100644
index 0000000..0ecad74
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtFlags.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ArtFlags {
+ /** Whether the operation is applied for primary dex'es. */
+ public static final int FLAG_FOR_PRIMARY_DEX = 1 << 0;
+ /** Whether the operation is applied for secondary dex'es. */
+ public static final int FLAG_FOR_SECONDARY_DEX = 1 << 1;
+
+ /**
+ * Flags for {@link ArtManagerLocal#deleteOptimizedArtifacts(PackageDataSnapshot, String, int)}.
+ *
+ * @hide
+ */
+ // clang-format off
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_FOR_PRIMARY_DEX,
+ FLAG_FOR_SECONDARY_DEX,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeleteFlags {}
+
+ /**
+ * Default flags that are used when
+ * {@link ArtManagerLocal#deleteOptimizedArtifacts(PackageDataSnapshot, String)} is called.
+ * Value: {@link #FLAG_FOR_PRIMARY_DEX}.
+ */
+ public static @DeleteFlags int defaultDeleteFlags() {
+ return FLAG_FOR_PRIMARY_DEX;
+ }
+
+ /**
+ * Flags for {@link ArtManagerLocal#getOptimizationStatus(PackageDataSnapshot, String, int)}.
+ *
+ * @hide
+ */
+ // clang-format off
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_FOR_PRIMARY_DEX,
+ FLAG_FOR_SECONDARY_DEX,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GetStatusFlags {}
+
+ /**
+ * Default flags that are used when
+ * {@link ArtManagerLocal#getOptimizationStatus(PackageDataSnapshot, String)} is called.
+ * Value: {@link #FLAG_FOR_PRIMARY_DEX}.
+ */
+ public static @GetStatusFlags int defaultGetStatusFlags() {
+ return FLAG_FOR_PRIMARY_DEX;
+ }
+
+ private ArtFlags() {}
+}
diff --git a/libartservice/service/java/com/android/server/art/model/DeleteResult.java b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
new file mode 100644
index 0000000..fc40cbc
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DeleteResult.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.model;
+
+import android.annotation.SystemApi;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class DeleteResult {
+ private long mFreedBytes;
+
+ /** @hide */
+ public DeleteResult(long freedBytes) {
+ mFreedBytes = freedBytes;
+ }
+
+ /** The amount of the disk space freed by the deletion, in bytes. */
+ public long getFreedBytes() {
+ return mFreedBytes;
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
new file mode 100644
index 0000000..724b0dd
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes the optimization status of a package.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+public class OptimizationStatus {
+ private final @NonNull List<DexFileOptimizationStatus> mDexFileOptimizationStatuses;
+
+ /** @hide */
+ public OptimizationStatus(
+ @NonNull List<DexFileOptimizationStatus> dexFileOptimizationStatuses) {
+ mDexFileOptimizationStatuses = dexFileOptimizationStatuses;
+ }
+
+ /** The optimization status of each individual dex file. */
+ @NonNull
+ public List<DexFileOptimizationStatus> getDexFileOptimizationStatuses() {
+ return mDexFileOptimizationStatuses;
+ }
+
+ /** Describes the optimization status of a dex file. */
+ @Immutable
+ public static class DexFileOptimizationStatus {
+ private final @NonNull String mDexFile;
+ private final @NonNull String mInstructionSet;
+ private final @NonNull String mCompilerFilter;
+ private final @NonNull String mCompilationReason;
+ private final @NonNull String mLocationDebugString;
+
+ /** @hide */
+ public DexFileOptimizationStatus(@NonNull String dexFile, @NonNull String instructionSet,
+ @NonNull String compilerFilter, @NonNull String compilationReason,
+ @NonNull String locationDebugString) {
+ mDexFile = dexFile;
+ mInstructionSet = instructionSet;
+ mCompilerFilter = compilerFilter;
+ mCompilationReason = compilationReason;
+ mLocationDebugString = locationDebugString;
+ }
+
+ /** The absolute path to the dex file. */
+ public @NonNull String getDexFile() {
+ return mDexFile;
+ }
+
+ /** The instruction set. */
+ public @NonNull String getInstructionSet() {
+ return mInstructionSet;
+ }
+
+ /**
+ * A string that describes the compiler filter.
+ *
+ * Possible values are:
+ * <ul>
+ * <li>A valid value of the {@code --compiler-filer} option passed to {@code dex2oat}, if
+ * the optimized artifacts are valid.
+ * <li>{@code "run-from-apk"}, if the optimized artifacts do not exist.
+ * <li>{@code "run-from-apk-fallback"}, if the optimized artifacts exist but are invalid
+ * because the dex file has changed.
+ * <li>{@code "error"}, if an unexpected error occurs.
+ * </ul>
+ */
+ public @NonNull String getCompilerFilter() {
+ return mCompilerFilter;
+ }
+
+ /**
+ * A string that describes the compilation reason.
+ *
+ * Possible values are:
+ * <ul>
+ * <li>The compilation reason, in text format, passed to {@code dex2oat}.
+ * <li>{@code "unknown"}: if the reason is empty or the optimized artifacts do not exist.
+ * <li>{@code "error"}: if an unexpected error occurs.
+ * </ul>
+ */
+ public @NonNull String getCompilationReason() {
+ return mCompilationReason;
+ }
+
+ /**
+ * A human-readable string that describes the location of the optimized artifacts.
+ *
+ * Note that this string is for debugging purposes only. There is no stability guarantees
+ * for the format of the string. DO NOT use it programmatically.
+ */
+ public @NonNull String getLocationDebugString() {
+ return mLocationDebugString;
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java b/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java
new file mode 100644
index 0000000..90adb49
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.wrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+/** @hide */
+public class AndroidPackageApi {
+ private final Object mPkg;
+
+ AndroidPackageApi(@NonNull Object pkg) {
+ mPkg = pkg;
+ }
+
+ Object getRealInstance() {
+ return mPkg;
+ }
+
+ @NonNull
+ public String getBaseApkPath() {
+ try {
+ return (String) mPkg.getClass().getMethod("getBaseApkPath").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isHasCode() {
+ try {
+ return (boolean) mPkg.getClass().getMethod("isHasCode").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public String[] getSplitNames() {
+ try {
+ return (String[]) mPkg.getClass().getMethod("getSplitNames").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public String[] getSplitCodePaths() {
+ try {
+ return (String[]) mPkg.getClass().getMethod("getSplitCodePaths").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public int[] getSplitFlags() {
+ try {
+ Class<?> parsingPackageImplClass =
+ Class.forName("com.android.server.pm.pkg.parsing.ParsingPackageImpl");
+ return (int[]) parsingPackageImplClass.getMethod("getSplitFlags").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public String getClassLoaderName() {
+ try {
+ return (String) mPkg.getClass().getMethod("getClassLoaderName").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public String[] getSplitClassLoaderNames() {
+ try {
+ return (String[]) mPkg.getClass().getMethod("getSplitClassLoaderNames").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public SparseArray<int[]> getSplitDependencies() {
+ try {
+ return (SparseArray<int[]>) mPkg.getClass()
+ .getMethod("getSplitDependencies")
+ .invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isIsolatedSplitLoading() {
+ try {
+ return (boolean) mPkg.getClass().getMethod("isIsolatedSplitLoading").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/PackageManagerLocal.java b/libartservice/service/java/com/android/server/art/wrapper/PackageManagerLocal.java
new file mode 100644
index 0000000..650d29c
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/PackageManagerLocal.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art.wrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+
+/** @hide */
+public class PackageManagerLocal {
+ private final Object mPackageManagerInternal;
+
+ /**
+ * Returns an instance this class, which is a reflection-based implementation of {@link
+ * com.android.server.pm.PackageManagerLocal}.
+ * Note: This is NOT a real system API! Use {@link LocalManagerRegistry} for getting a real
+ * instance.
+ */
+ @NonNull
+ public static PackageManagerLocal getInstance() throws Exception {
+ Class<?> localServicesClass = Class.forName("com.android.server.LocalServices");
+ Class<?> packageManagerInternalClass =
+ Class.forName("android.content.pm.PackageManagerInternal");
+ Object packageManagerInternal = localServicesClass.getMethod("getService", Class.class)
+ .invoke(null, packageManagerInternalClass);
+ if (packageManagerInternal == null) {
+ throw new Exception("Failed to get PackageManagerInternal");
+ }
+ return new PackageManagerLocal(packageManagerInternal);
+ }
+
+ private PackageManagerLocal(@NonNull Object packageManagerInternal) {
+ mPackageManagerInternal = packageManagerInternal;
+ }
+
+ @NonNull
+ public PackageDataSnapshot snapshot() {
+ try {
+ return (PackageDataSnapshot) mPackageManagerInternal.getClass()
+ .getMethod("snapshot")
+ .invoke(mPackageManagerInternal);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public PackageState getPackageState(@NonNull PackageDataSnapshot snapshot,
+ @NonNull int callingUid, @NonNull String packageName) {
+ try {
+ int userId = (int) UserHandle.class.getMethod("getUserId", int.class)
+ .invoke(null, callingUid);
+ Class<?> computerClass = Class.forName("com.android.server.pm.Computer");
+ Object packageState = computerClass
+ .getMethod("getPackageStateForInstalledAndFiltered",
+ String.class, int.class, int.class)
+ .invoke(snapshot, packageName, callingUid, userId);
+ return packageState != null ? new PackageState(packageState) : null;
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/PackageState.java b/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
new file mode 100644
index 0000000..2235514
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/PackageState.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.wrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** @hide */
+public class PackageState {
+ private final Object mPkgState;
+
+ PackageState(@NonNull Object pkgState) {
+ mPkgState = pkgState;
+ }
+
+ @Nullable
+ public AndroidPackageApi getAndroidPackage() {
+ try {
+ Object pkg = mPkgState.getClass().getMethod("getAndroidPackage").invoke(mPkgState);
+ return pkg != null ? new AndroidPackageApi(pkg) : null;
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public String getPackageName() {
+ try {
+ return (String) mPkgState.getClass().getMethod("getPackageName").invoke(mPkgState);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public List<SharedLibraryInfo> getUsesLibraryInfos() {
+ try {
+ Object packageStateUnserialized =
+ mPkgState.getClass().getMethod("getTransientState").invoke(mPkgState);
+ var list = (List<?>) packageStateUnserialized.getClass()
+ .getMethod("getUsesLibraryInfos")
+ .invoke(packageStateUnserialized);
+ return list.stream()
+ .map(obj -> new SharedLibraryInfo(obj))
+ .collect(Collectors.toList());
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public String getPrimaryCpuAbi() {
+ try {
+ String abi =
+ (String) mPkgState.getClass().getMethod("getPrimaryCpuAbi").invoke(mPkgState);
+ if (!TextUtils.isEmpty(abi)) {
+ return abi;
+ }
+
+ // Default to the information in `AndroidPackageApi`. The defaulting behavior will
+ // eventually be done by `PackageState` internally.
+ AndroidPackageApi pkg = getAndroidPackage();
+ if (pkg == null) {
+ // This should never happen because we check the existence of the package at the
+ // beginning of each ART Services method.
+ throw new IllegalStateException("Unable to get package " + getPackageName()
+ + ". This should never happen.");
+ }
+
+ Class<?> androidPackageHiddenClass =
+ Class.forName("com.android.server.pm.parsing.pkg.AndroidPackageHidden");
+ return (String) androidPackageHiddenClass.getMethod("getPrimaryCpuAbi")
+ .invoke(pkg.getRealInstance());
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public String getSecondaryCpuAbi() {
+ try {
+ String abi =
+ (String) mPkgState.getClass().getMethod("getSecondaryCpuAbi").invoke(mPkgState);
+ if (!TextUtils.isEmpty(abi)) {
+ return abi;
+ }
+
+ // Default to the information in `AndroidPackageApi`. The defaulting behavior will
+ // eventually be done by `PackageState` internally.
+ AndroidPackageApi pkg = getAndroidPackage();
+ if (pkg == null) {
+ // This should never happen because we check the existence of the package at the
+ // beginning of each ART Services method.
+ throw new IllegalStateException("Unable to get package " + getPackageName()
+ + ". This should never happen.");
+ }
+
+ Class<?> androidPackageHiddenClass =
+ Class.forName("com.android.server.pm.parsing.pkg.AndroidPackageHidden");
+ return (String) androidPackageHiddenClass.getMethod("getSecondaryCpuAbi")
+ .invoke(pkg.getRealInstance());
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isSystem() {
+ try {
+ return (boolean) mPkgState.getClass().getMethod("isSystem").invoke(mPkgState);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isUpdatedSystemApp() {
+ try {
+ Object packageStateUnserialized =
+ mPkgState.getClass().getMethod("getTransientState").invoke(mPkgState);
+ return (boolean) packageStateUnserialized.getClass()
+ .getMethod("isUpdatedSystemApp")
+ .invoke(packageStateUnserialized);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/README.md b/libartservice/service/java/com/android/server/art/wrapper/README.md
new file mode 100644
index 0000000..829fc1c
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/README.md
@@ -0,0 +1,11 @@
+This folder contains temporary wrappers that access system server internal
+classes using reflection. Having the wrappers is the workaround for the current
+time being where required system APIs are not finalized. The classes and methods
+correspond to system APIs planned to be exposed.
+
+The mappings are:
+
+- `AndroidPackageApi`: `com.android.server.pm.pkg.AndroidPackageApi`
+- `PackageManagerLocal`: `com.android.server.pm.PackageManagerLocal`
+- `PackageState`: `com.android.server.pm.pkg.PackageState`
+- `SharedLibraryInfo`: `android.content.pm.SharedLibraryInfo`
diff --git a/libartservice/service/java/com/android/server/art/wrapper/SharedLibraryInfo.java b/libartservice/service/java/com/android/server/art/wrapper/SharedLibraryInfo.java
new file mode 100644
index 0000000..f2bde16
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/wrapper/SharedLibraryInfo.java
@@ -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.wrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** @hide */
+public class SharedLibraryInfo {
+ private final Object mInfo;
+
+ SharedLibraryInfo(@NonNull Object info) {
+ mInfo = info;
+ }
+
+ @NonNull
+ public List<String> getAllCodePaths() {
+ try {
+ return (List<String>) mInfo.getClass().getMethod("getAllCodePaths").invoke(mInfo);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public List<SharedLibraryInfo> getDependencies() {
+ try {
+ var list = (List<?>) mInfo.getClass().getMethod("getDependencies").invoke(mInfo);
+ if (list == null) {
+ return null;
+ }
+ return list.stream()
+ .map(obj -> new SharedLibraryInfo(obj))
+ .collect(Collectors.toList());
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index a27dfa5..0e958ba 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,29 +16,217 @@
package com.android.server.art;
+import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
+
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.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+
import androidx.test.filters.SmallTest;
-import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.OptimizationStatus;
+import com.android.server.art.wrapper.AndroidPackageApi;
+import com.android.server.art.wrapper.PackageManagerLocal;
+import com.android.server.art.wrapper.PackageState;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
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 org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+import java.util.List;
@SmallTest
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(Parameterized.class)
public class ArtManagerLocalTest {
+ private static final String PKG_NAME = "com.example.foo";
+
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+ @Mock private ArtManagerLocal.Injector mInjector;
+ @Mock private PackageManagerLocal mPackageManagerLocal;
+ @Mock private IArtd mArtd;
+ private PackageState mPkgState;
+
+ // True if the primary dex'es are in a readonly partition.
+ @Parameter(0) public boolean mIsInReadonlyPartition;
+
private ArtManagerLocal mArtManagerLocal;
+ @Parameters(name = "isInReadonlyPartition={0}")
+ public static Iterable<? extends Object> data() {
+ return List.of(false, true);
+ }
+
@Before
- public void setUp() {
- mArtManagerLocal = new ArtManagerLocal();
+ public void setUp() throws Exception {
+ lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+
+ mPkgState = createPackageState();
+ lenient()
+ .when(mPackageManagerLocal.getPackageState(any(), anyInt(), eq(PKG_NAME)))
+ .thenReturn(mPkgState);
+
+ mArtManagerLocal = new ArtManagerLocal(mInjector);
}
@Test
- public void testScaffolding() {
- assertThat(true).isTrue();
+ public void testDeleteOptimizedArtifacts() throws Exception {
+ when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(
+ mock(PackageDataSnapshot.class), PKG_NAME);
+ assertThat(result.getFreedBytes()).isEqualTo(4);
+
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("arm64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/base.apk")
+ && artifactsPath.isa.equals("arm")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("arm64")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verify(mArtd).deleteArtifacts(argThat(artifactsPath
+ -> artifactsPath.dexPath.equals("/data/app/foo/split_0.apk")
+ && artifactsPath.isa.equals("arm")
+ && artifactsPath.isInDalvikCache == mIsInReadonlyPartition));
+ verifyNoMoreInteractions(mArtd);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteOptimizedArtifactsPackageNotFound() throws Exception {
+ when(mPackageManagerLocal.getPackageState(any(), anyInt(), eq(PKG_NAME))).thenReturn(null);
+
+ mArtManagerLocal.deleteOptimizedArtifacts(mock(PackageDataSnapshot.class), PKG_NAME);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDeleteOptimizedArtifactsNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.deleteOptimizedArtifacts(mock(PackageDataSnapshot.class), PKG_NAME);
+ }
+
+ @Test
+ public void testGetOptimizationStatus() throws Exception {
+ when(mArtd.getOptimizationStatus(any(), any(), any()))
+ .thenReturn(createGetOptimizationStatusResult(
+ "speed", "compilation-reason-0", "location-debug-string-0"),
+ createGetOptimizationStatusResult(
+ "speed-profile", "compilation-reason-1", "location-debug-string-1"),
+ createGetOptimizationStatusResult(
+ "verify", "compilation-reason-2", "location-debug-string-2"),
+ createGetOptimizationStatusResult(
+ "extract", "compilation-reason-3", "location-debug-string-3"));
+
+ OptimizationStatus result =
+ mArtManagerLocal.getOptimizationStatus(mock(PackageDataSnapshot.class), PKG_NAME);
+
+ List<DexFileOptimizationStatus> statuses = result.getDexFileOptimizationStatuses();
+ assertThat(statuses.size()).isEqualTo(4);
+
+ assertThat(statuses.get(0).getDexFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(0).getInstructionSet()).isEqualTo("arm64");
+ assertThat(statuses.get(0).getCompilerFilter()).isEqualTo("speed");
+ assertThat(statuses.get(0).getCompilationReason()).isEqualTo("compilation-reason-0");
+ assertThat(statuses.get(0).getLocationDebugString()).isEqualTo("location-debug-string-0");
+
+ assertThat(statuses.get(1).getDexFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(1).getInstructionSet()).isEqualTo("arm");
+ assertThat(statuses.get(1).getCompilerFilter()).isEqualTo("speed-profile");
+ assertThat(statuses.get(1).getCompilationReason()).isEqualTo("compilation-reason-1");
+ assertThat(statuses.get(1).getLocationDebugString()).isEqualTo("location-debug-string-1");
+
+ assertThat(statuses.get(2).getDexFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(2).getInstructionSet()).isEqualTo("arm64");
+ assertThat(statuses.get(2).getCompilerFilter()).isEqualTo("verify");
+ assertThat(statuses.get(2).getCompilationReason()).isEqualTo("compilation-reason-2");
+ assertThat(statuses.get(2).getLocationDebugString()).isEqualTo("location-debug-string-2");
+
+ assertThat(statuses.get(3).getDexFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(3).getInstructionSet()).isEqualTo("arm");
+ assertThat(statuses.get(3).getCompilerFilter()).isEqualTo("extract");
+ assertThat(statuses.get(3).getCompilationReason()).isEqualTo("compilation-reason-3");
+ assertThat(statuses.get(3).getLocationDebugString()).isEqualTo("location-debug-string-3");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetOptimizationStatusPackageNotFound() throws Exception {
+ when(mPackageManagerLocal.getPackageState(any(), anyInt(), eq(PKG_NAME))).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(mock(PackageDataSnapshot.class), PKG_NAME);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetOptimizationStatusNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(mock(PackageDataSnapshot.class), PKG_NAME);
+ }
+
+ private AndroidPackageApi createPackage() {
+ AndroidPackageApi pkg = mock(AndroidPackageApi.class);
+
+ lenient().when(pkg.getBaseApkPath()).thenReturn("/data/app/foo/base.apk");
+ lenient().when(pkg.isHasCode()).thenReturn(true);
+
+ // split_0 has code while split_1 doesn't.
+ lenient().when(pkg.getSplitNames()).thenReturn(new String[] {"split_0", "split_1"});
+ lenient()
+ .when(pkg.getSplitCodePaths())
+ .thenReturn(
+ new String[] {"/data/app/foo/split_0.apk", "/data/app/foo/split_1.apk"});
+ lenient()
+ .when(pkg.getSplitFlags())
+ .thenReturn(new int[] {ApplicationInfo.FLAG_HAS_CODE, 0});
+
+ 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(mIsInReadonlyPartition);
+ lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
+ AndroidPackageApi pkg = createPackage();
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+
+ return pkgState;
+ }
+
+ private GetOptimizationStatusResult createGetOptimizationStatusResult(
+ String compilerFilter, String compilationReason, String locationDebugString) {
+ var getOptimizationStatusResult = new GetOptimizationStatusResult();
+ getOptimizationStatusResult.compilerFilter = compilerFilter;
+ getOptimizationStatusResult.compilationReason = compilationReason;
+ getOptimizationStatusResult.locationDebugString = locationDebugString;
+ return getOptimizationStatusResult;
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
new file mode 100644
index 0000000..5a81e01
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.PrimaryDexUtils;
+import com.android.server.art.wrapper.AndroidPackageApi;
+import com.android.server.art.wrapper.PackageState;
+import com.android.server.art.wrapper.SharedLibraryInfo;
+
+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).isBaseApk()).isTrue();
+ assertThat(infos.get(0).splitIndex()).isEqualTo(-1);
+ 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).isBaseApk()).isFalse();
+ assertThat(infos.get(1).splitIndex()).isEqualTo(0);
+ 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).isBaseApk()).isFalse();
+ assertThat(infos.get(2).splitIndex()).isEqualTo(1);
+ 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).isBaseApk()).isFalse();
+ assertThat(infos.get(3).splitIndex()).isEqualTo(2);
+ 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).isBaseApk()).isFalse();
+ assertThat(infos.get(4).splitIndex()).isEqualTo(3);
+ 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).isBaseApk()).isFalse();
+ assertThat(infos.get(5).splitIndex()).isEqualTo(4);
+ assertThat(infos.get(5).splitName()).isEqualTo("split_4");
+ }
+
+ private AndroidPackageApi createPackage(boolean isIsolatedSplitLoading) {
+ AndroidPackageApi pkg = mock(AndroidPackageApi.class);
+
+ when(pkg.getBaseApkPath()).thenReturn("/data/app/foo/base.apk");
+ when(pkg.isHasCode()).thenReturn(true);
+ when(pkg.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
+
+ when(pkg.getSplitNames())
+ .thenReturn(new String[] {"split_0", "split_1", "split_2", "split_3", "split_4"});
+ when(pkg.getSplitCodePaths())
+ .thenReturn(new String[] {
+ "/data/app/foo/split_0.apk",
+ "/data/app/foo/split_1.apk",
+ "/data/app/foo/split_2.apk",
+ "/data/app/foo/split_3.apk",
+ "/data/app/foo/split_4.apk",
+ });
+ when(pkg.getSplitFlags())
+ .thenReturn(new int[] {
+ ApplicationInfo.FLAG_HAS_CODE,
+ 0,
+ ApplicationInfo.FLAG_HAS_CODE,
+ ApplicationInfo.FLAG_HAS_CODE,
+ ApplicationInfo.FLAG_HAS_CODE,
+ });
+
+ if (isIsolatedSplitLoading) {
+ // split_0: PCL(PathClassLoader), depends on split_2.
+ // split_1: no code.
+ // split_2: DLC(DelegateLastClassLoader), depends on base.
+ // split_3: PCL(DexClassLoader), no dependency.
+ // split_4: PCL(null), depends on split_3.
+ when(pkg.isIsolatedSplitLoading()).thenReturn(true);
+ when(pkg.getSplitClassLoaderNames())
+ .thenReturn(new String[] {
+ PathClassLoader.class.getName(),
+ null,
+ DelegateLastClassLoader.class.getName(),
+ DexClassLoader.class.getName(),
+ null,
+ });
+ SparseArray<int[]> splitDependencies = new SparseArray<>();
+ splitDependencies.set(1, new int[] {3});
+ splitDependencies.set(3, new int[] {0});
+ splitDependencies.set(5, new int[] {4});
+ when(pkg.getSplitDependencies()).thenReturn(splitDependencies);
+ } else {
+ when(pkg.isIsolatedSplitLoading()).thenReturn(false);
+ }
+
+ return pkg;
+ }
+
+ private PackageState createPackageState() {
+ PackageState pkgState = mock(PackageState.class);
+
+ when(pkgState.getPackageName()).thenReturn("com.example.foo");
+
+ // Base depends on library 2, 3, 4.
+ // Library 2, 4 depends on library 1.
+ List<SharedLibraryInfo> usesLibraryInfos = new ArrayList<>();
+
+ SharedLibraryInfo library1 = mock(SharedLibraryInfo.class);
+ when(library1.getAllCodePaths())
+ .thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar"));
+ when(library1.getDependencies()).thenReturn(null);
+
+ SharedLibraryInfo library2 = mock(SharedLibraryInfo.class);
+ when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
+ when(library2.getDependencies()).thenReturn(List.of(library1));
+ usesLibraryInfos.add(library2);
+
+ SharedLibraryInfo library3 = mock(SharedLibraryInfo.class);
+ when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
+ when(library3.getDependencies()).thenReturn(null);
+ usesLibraryInfos.add(library3);
+
+ SharedLibraryInfo library4 = mock(SharedLibraryInfo.class);
+ when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar"));
+ when(library4.getDependencies()).thenReturn(List.of(library1));
+ usesLibraryInfos.add(library4);
+
+ when(pkgState.getUsesLibraryInfos()).thenReturn(usesLibraryInfos);
+
+ return pkgState;
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
new file mode 100644
index 0000000..da39eec
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.Utils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class UtilsTest {
+ @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();
+ }
+}
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index cf0155f..f7c9e03 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -22,6 +22,7 @@
#include <sstream>
#include "android-base/file.h"
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "arch/instruction_set.h"
@@ -234,6 +235,47 @@
}
}
+std::unique_ptr<OatFileAssistant> OatFileAssistant::Create(
+ const std::string& filename,
+ const std::string& isa_str,
+ const std::string& context_str,
+ bool load_executable,
+ bool only_load_trusted_executable,
+ std::unique_ptr<RuntimeOptions> runtime_options,
+ std::string* error_msg) {
+ InstructionSet isa = GetInstructionSetFromString(isa_str.c_str());
+ if (isa == InstructionSet::kNone) {
+ *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str());
+ return nullptr;
+ }
+
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str.c_str());
+ if (context == nullptr) {
+ *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str.c_str());
+ return nullptr;
+ }
+
+ if (!context->OpenDexFiles(android::base::Dirname(filename.c_str()),
+ /*context_fds=*/{},
+ /*only_read_checksums=*/true)) {
+ *error_msg =
+ StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
+ filename.c_str(),
+ context_str.c_str());
+ return nullptr;
+ }
+
+ auto assistant = std::make_unique<OatFileAssistant>(filename.c_str(),
+ isa,
+ context.get(),
+ load_executable,
+ only_load_trusted_executable,
+ std::move(runtime_options));
+
+ assistant->owned_context_ = std::move(context);
+ return assistant;
+}
+
bool OatFileAssistant::UseFdToReadFiles() {
return zip_fd_ >= 0;
}
@@ -1141,57 +1183,6 @@
&out_odex_status);
}
-bool OatFileAssistant::GetOptimizationStatus(const std::string& filename,
- const std::string& isa_str,
- const std::string& context_str,
- std::unique_ptr<RuntimeOptions> runtime_options,
- /*out*/ std::string* compiler_filter,
- /*out*/ std::string* compilation_reason,
- /*out*/ std::string* odex_location,
- /*out*/ std::string* error_msg) {
- InstructionSet isa = GetInstructionSetFromString(isa_str.c_str());
- if (isa == InstructionSet::kNone) {
- *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str());
- return false;
- }
-
- std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str.c_str());
- if (context == nullptr) {
- *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str.c_str());
- return false;
- }
-
- std::vector<int> context_fds;
- if (!context->OpenDexFiles(android::base::Dirname(filename.c_str()),
- context_fds,
- /*only_read_checksums=*/true)) {
- *error_msg =
- StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
- filename.c_str(),
- context_str.c_str());
- return false;
- }
-
- OatFileAssistant oat_file_assistant(filename.c_str(),
- isa,
- context.get(),
- /*load_executable=*/false,
- /*only_load_trusted_executable=*/true,
- std::move(runtime_options));
-
- // We ignore the odex_status because it is not meaningful. It can never be
- // "boot-image-more-recent" or "context-mismatch". In the case where the boot image has changed or
- // there is a context mismatch, the value is "up-to-date" because the vdex file is still usable.
- // I.e., it can only be either "up-to-date" or "apk-more-recent", which means it doesn't give us
- // information in addition to what we can learn from compiler_filter.
- std::string ignored_odex_status;
-
- oat_file_assistant.GetOptimizationStatus(
- odex_location, compiler_filter, compilation_reason, &ignored_odex_status);
-
- return true;
-}
-
void OatFileAssistant::GetOptimizationStatus(
std::string* out_odex_location,
std::string* out_compilation_filter,
@@ -1218,28 +1209,25 @@
OatStatus status = oat_file_info.Status();
const char* reason = oat_file->GetCompilationReason();
*out_compilation_reason = reason == nullptr ? "unknown" : reason;
+
+ // If the oat file is invalid, the vdex file will be picked, so the status is `kOatUpToDate`. If
+ // the vdex file is also invalid, then either `oat_file` is nullptr, or `status` is
+ // `kOatDexOutOfDate`.
+ DCHECK(status == kOatUpToDate || status == kOatDexOutOfDate);
+
switch (status) {
case kOatUpToDate:
*out_compilation_filter = CompilerFilter::NameOfFilter(oat_file->GetCompilerFilter());
*out_odex_status = "up-to-date";
return;
- case kOatCannotOpen: // This should never happen, but be robust.
- *out_compilation_filter = "error";
- *out_compilation_reason = "error";
- // This mostly happens when we cannot open the vdex file,
- // or the file is corrupt.
- *out_odex_status = "io-error-or-corruption";
- return;
-
+ case kOatCannotOpen:
case kOatBootImageOutOfDate:
- *out_compilation_filter = "run-from-apk-fallback";
- *out_odex_status = "boot-image-more-recent";
- return;
-
case kOatContextOutOfDate:
- *out_compilation_filter = "run-from-apk-fallback";
- *out_odex_status = "context-mismatch";
+ // These should never happen, but be robust.
+ *out_compilation_filter = "unexpected";
+ *out_compilation_reason = "unexpected";
+ *out_odex_status = "unexpected";
return;
case kOatDexOutOfDate:
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 527f473..1198832 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -156,6 +156,17 @@
int oat_fd,
int zip_fd);
+ // A convenient factory function that accepts ISA, class loader context, and compiler filter in
+ // strings. Returns the created instance on success, or returns nullptr and outputs an error
+ // message if it fails to parse the input strings.
+ static std::unique_ptr<OatFileAssistant> Create(const std::string& filename,
+ const std::string& isa_str,
+ const std::string& context_str,
+ bool load_executable,
+ bool only_load_trusted_executable,
+ std::unique_ptr<RuntimeOptions> runtime_options,
+ std::string* error_msg);
+
// Returns true if the dex location refers to an element of the boot class
// path.
bool IsInBootClassPath();
@@ -204,7 +215,7 @@
// - out_compilation_reason: the optimization reason. The reason might
// be "unknown" if the compiler artifacts were not annotated during optimizations.
// - out_odex_status: a human readable refined status of the validity of the odex file.
- // E.g. up-to-date, apk-more-recent.
+ // Possible values are: "up-to-date", "apk-more-recent", and "io-error-no-oat".
//
// This method will try to mimic the runtime effect of loading the dex file.
// For example, if there is no usable oat file, the compiler filter will be set
@@ -220,18 +231,6 @@
std::string* out_compilation_reason,
std::unique_ptr<RuntimeOptions> runtime_options = nullptr);
- // A convenient version of `GetOptimizationStatus` that accepts ISA and class loader context in
- // strings. Returns true on success, or returns false and outputs an error message if it fails to
- // parse the input strings.
- static bool GetOptimizationStatus(const std::string& filename,
- const std::string& isa_str,
- const std::string& context_str,
- std::unique_ptr<RuntimeOptions> runtime_options,
- /*out*/ std::string* compiler_filter,
- /*out*/ std::string* compilation_reason,
- /*out*/ std::string* odex_location,
- /*out*/ std::string* error_msg);
-
// Open and returns an image space associated with the oat file.
static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file);
@@ -472,6 +471,7 @@
std::string dex_location_;
ClassLoaderContext* context_;
+ std::unique_ptr<ClassLoaderContext> owned_context_;
// Whether or not the parent directory of the dex file is writable.
bool dex_parent_writable_ = false;
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index c0662aa..e414e4e 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -88,26 +88,7 @@
ASSERT_EQ(expected_reason, compilation_reason1);
}
- // Verify the static method (called from artd).
- std::string compilation_filter2;
- std::string compilation_reason2;
- std::string odex_location2; // ignored
- std::string error_msg; // ignored
-
- ASSERT_TRUE(
- OatFileAssistant::GetOptimizationStatus(file,
- GetInstructionSetString(kRuntimeISA),
- context->EncodeContextForDex2oat(/*base_dir=*/""),
- MaybeCreateRuntimeOptions(),
- &compilation_filter2,
- &compilation_reason2,
- &odex_location2,
- &error_msg));
-
- ASSERT_EQ(expected_filter_name, compilation_filter2);
- ASSERT_EQ(expected_reason, compilation_reason2);
-
- // Verify the instance methods (called at runtime).
+ // Verify the instance methods (called at runtime and from artd).
OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
std::string odex_location3; // ignored
@@ -1796,6 +1777,29 @@
}
}
+TEST_P(OatFileAssistantTest, Create) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::string error_msg;
+ std::unique_ptr<OatFileAssistant> oat_file_assistant =
+ OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeCreateRuntimeOptions(),
+ &error_msg);
+ ASSERT_NE(oat_file_assistant, nullptr);
+
+ // Verify that the created instance is usable.
+ VerifyOptimizationStatus(dex_location, default_context_.get(), "speed", "install", "up-to-date");
+}
+
TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) {
std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
@@ -1804,19 +1808,15 @@
auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- std::string ignored_compilation_filter;
- std::string ignored_compilation_reason;
- std::string ignored_odex_location;
std::string error_msg;
- EXPECT_FALSE(OatFileAssistant::GetOptimizationStatus(
- dex_location,
- /*isa_str=*/"foo",
- default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
- MaybeCreateRuntimeOptions(),
- &ignored_compilation_filter,
- &ignored_compilation_reason,
- &ignored_odex_location,
- &error_msg));
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ /*isa_str=*/"foo",
+ default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeCreateRuntimeOptions(),
+ &error_msg),
+ nullptr);
EXPECT_EQ(error_msg, "Instruction set 'foo' is invalid");
}
@@ -1828,18 +1828,15 @@
auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- std::string ignored_compilation_filter;
- std::string ignored_compilation_reason;
- std::string ignored_odex_location;
std::string error_msg;
- EXPECT_FALSE(OatFileAssistant::GetOptimizationStatus(dex_location,
- GetInstructionSetString(kRuntimeISA),
- /*context_str=*/"foo",
- MaybeCreateRuntimeOptions(),
- &ignored_compilation_filter,
- &ignored_compilation_reason,
- &ignored_odex_location,
- &error_msg));
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ /*context_str=*/"foo",
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeCreateRuntimeOptions(),
+ &error_msg),
+ nullptr);
EXPECT_EQ(error_msg, "Class loader context 'foo' is invalid");
}
@@ -1856,19 +1853,15 @@
auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- std::string ignored_compilation_filter;
- std::string ignored_compilation_reason;
- std::string ignored_odex_location;
std::string error_msg;
- EXPECT_FALSE(
- OatFileAssistant::GetOptimizationStatus(dex_location,
- GetInstructionSetString(kRuntimeISA),
- /*context_str=*/"PCL[" + context_location + "]",
- MaybeCreateRuntimeOptions(),
- &ignored_compilation_filter,
- &ignored_compilation_reason,
- &ignored_odex_location,
- &error_msg));
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ /*context_str=*/"PCL[" + context_location + "]",
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeCreateRuntimeOptions(),
+ &error_msg),
+ nullptr);
EXPECT_EQ(error_msg,
"Failed to load class loader context files for '" + dex_location +
"' with context 'PCL[" + context_location + "]'");
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index fc7c360..481b9ba 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -217,9 +217,7 @@
# ART gtests that do not need root access to the device.
art_gtest_user_module_names = [
"art_libnativebridge_cts_tests",
- # TODO(b/235464166): art_artd_tests doesn't work on master-art because of
- # the dependency on libbinder_ndk.
- # "art_standalone_artd_tests",
+ "art_standalone_artd_tests",
"art_standalone_cmdline_tests",
"art_standalone_compiler_tests",
"art_standalone_dex2oat_tests",