Merge changes I0539656f,I4c9f29da,Iac679828 into sc-dev

* changes:
  OverlayManager Fabricated RROs
  Add fabricated RRO generation to libidmap2
  Query overlays using package and name
This commit is contained in:
Ryan Mitchell
2021-02-10 18:06:54 +00:00
committed by Android (Google) Code Review
89 changed files with 4804 additions and 2629 deletions

View File

@@ -369,6 +369,7 @@ filegroup {
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
":idmap2_core_aidl",
":incidentcompanion_aidl",
":inputconstants_aidl",
":installd_aidl",

View File

@@ -51,13 +51,18 @@ cc_library {
static: {
enabled: false,
},
static_libs: [
"libidmap2_protos",
],
shared_libs: [
"libandroidfw",
"libbase",
"libcutils",
"libutils",
"libziparchive",
"libidmap2_policies",
"libprotobuf-cpp-lite",
"libutils",
"libz",
"libziparchive",
],
},
host: {
@@ -68,14 +73,29 @@ cc_library {
"libandroidfw",
"libbase",
"libcutils",
"libutils",
"libziparchive",
"libidmap2_policies",
"libidmap2_protos",
"libprotobuf-cpp-lite",
"libutils",
"libz",
"libziparchive",
],
},
},
}
cc_library {
name: "libidmap2_protos",
srcs: [
"libidmap2/proto/*.proto",
],
host_supported: true,
proto: {
type: "lite",
export_proto_headers: true,
},
}
cc_library {
name: "libidmap2_policies",
defaults: [
@@ -88,9 +108,6 @@ cc_library {
enabled: true,
},
android: {
static: {
enabled: false,
},
shared_libs: [
"libandroidfw",
],
@@ -119,6 +136,7 @@ cc_test {
srcs: [
"tests/BinaryStreamVisitorTests.cpp",
"tests/CommandLineOptionsTests.cpp",
"tests/FabricatedOverlayTests.cpp",
"tests/FileUtilsTests.cpp",
"tests/Idmap2BinaryTests.cpp",
"tests/IdmapTests.cpp",
@@ -130,20 +148,27 @@ cc_test {
"tests/ResourceUtilsTests.cpp",
"tests/ResultTests.cpp",
"tests/XmlParserTests.cpp",
"tests/ZipFileTests.cpp",
],
static_libs: ["libgmock"],
required: [
"idmap2",
],
static_libs: [
"libgmock",
"libidmap2_protos",
],
target: {
android: {
shared_libs: [
"libandroidfw",
"libbase",
"libidmap2",
"libidmap2_policies",
"liblog",
"libprotobuf-cpp-lite",
"libutils",
"libz",
"libz",
"libziparchive",
"libidmap2_policies",
],
},
host: {
@@ -152,10 +177,11 @@ cc_test {
"libbase",
"libcutils",
"libidmap2",
"libidmap2_policies",
"liblog",
"libprotobuf-cpp-lite",
"libutils",
"libziparchive",
"libidmap2_policies",
],
shared_libs: [
"libz",
@@ -189,6 +215,9 @@ cc_binary {
"idmap2/Lookup.cpp",
"idmap2/Main.cpp",
],
static_libs: [
"libidmap2_protos",
],
target: {
android: {
shared_libs: [
@@ -196,9 +225,11 @@ cc_binary {
"libbase",
"libcutils",
"libidmap2",
"libutils",
"libziparchive",
"libidmap2_policies",
"libprotobuf-cpp-lite",
"libutils",
"libz",
"libziparchive",
],
},
host: {
@@ -207,10 +238,11 @@ cc_binary {
"libbase",
"libcutils",
"libidmap2",
"libidmap2_policies",
"liblog",
"libprotobuf-cpp-lite",
"libutils",
"libziparchive",
"libidmap2_policies",
],
shared_libs: [
"libz",
@@ -236,11 +268,14 @@ cc_binary {
"libbinder",
"libcutils",
"libidmap2",
"libidmap2_policies",
"libprotobuf-cpp-lite",
"libutils",
"libziparchive",
"libidmap2_policies",
],
static_libs: [
"libc++fs",
"libidmap2_protos",
"libidmap2daidl",
],
init_rc: ["idmap2d/idmap2d.rc"],
@@ -248,28 +283,41 @@ cc_binary {
cc_library_static {
name: "libidmap2daidl",
defaults: [
"idmap2_defaults",
],
tidy: false,
host_supported: false,
srcs: [
":idmap2_aidl",
":idmap2_core_aidl",
],
header_libs: [
"libbinder_headers",
],
shared_libs: [
"libbase",
],
aidl: {
export_aidl_headers: true,
local_include_dirs: [
"idmap2d/aidl/core",
"idmap2d/aidl/services/",
],
},
}
filegroup {
name: "idmap2_core_aidl",
srcs: [
"idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl",
"idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl",
"idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl",
],
path: "idmap2d/aidl/core/",
}
filegroup {
name: "idmap2_aidl",
srcs: [
"idmap2d/aidl/android/os/IIdmap2.aidl",
"idmap2d/aidl/services/android/os/IIdmap2.aidl",
],
path: "idmap2d/aidl",
path: "idmap2d/aidl/services/",
}
aidl_interface {
@@ -281,7 +329,7 @@ aidl_interface {
filegroup {
name: "overlayable_policy_aidl_files",
srcs: [
"idmap2d/aidl/android/os/OverlayablePolicy.aidl",
"idmap2d/aidl/services/android/os/OverlayablePolicy.aidl",
],
path: "idmap2d/aidl",
path: "idmap2d/aidl/services/",
}

View File

@@ -25,7 +25,9 @@
using android::idmap2::Error;
using android::idmap2::IdmapHeader;
using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
@@ -39,11 +41,20 @@ Result<Unit> Verify(const std::string& idmap_path, const std::string& target_pat
return Error("failed to parse idmap header");
}
const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name,
fulfilled_policies, enforce_overlayable);
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return Error("failed to load target '%s'", target_path.c_str());
}
auto overlay = OverlayResourceContainer::FromPath(overlay_path);
if (!overlay) {
return Error("failed to load overlay '%s'", overlay_path.c_str());
}
const auto header_ok = header->IsUpToDate(**target, **overlay, overlay_name, fulfilled_policies,
enforce_overlayable);
if (!header_ok) {
return Error(header_ok.GetError(), "idmap not up to date");
}
return Unit{};
}

View File

@@ -20,7 +20,6 @@
#include <fstream>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include "androidfw/ResourceTypes.h"
@@ -31,12 +30,13 @@
#include "idmap2/PolicyUtils.h"
#include "idmap2/SysTrace.h"
using android::ApkAssets;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::Idmap;
using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
using android::idmap2::utils::kIdmapFilePermissionMask;
using android::idmap2::utils::PoliciesToBitmaskResult;
@@ -93,18 +93,18 @@ Result<Unit> Create(const std::vector<std::string>& args) {
fulfilled_policies |= PolicyFlags::PUBLIC;
}
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return Error("failed to load apk %s", target_apk_path.c_str());
const auto target = TargetResourceContainer::FromPath(target_apk_path);
if (!target) {
return Error("failed to load target '%s'", target_apk_path.c_str());
}
const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
return Error("failed to load apk %s", overlay_apk_path.c_str());
const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
if (!overlay) {
return Error("failed to load apk overlay '%s'", overlay_apk_path.c_str());
}
const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name,
fulfilled_policies, !ignore_overlayable);
const auto idmap = Idmap::FromContainers(**target, **overlay, overlay_name, fulfilled_policies,
!ignore_overlayable);
if (!idmap) {
return Error(idmap.GetError(), "failed to create idmap");
}
@@ -112,13 +112,14 @@ Result<Unit> Create(const std::vector<std::string>& args) {
umask(kIdmapFilePermissionMask);
std::ofstream fout(idmap_path);
if (fout.fail()) {
return Error("failed to open idmap path %s", idmap_path.c_str());
return Error("failed to open idmap path '%s'", idmap_path.c_str());
}
BinaryStreamVisitor visitor(fout);
(*idmap)->accept(&visitor);
fout.close();
if (fout.fail()) {
return Error("failed to write to idmap path %s", idmap_path.c_str());
return Error("failed to write to idmap path '%s'", idmap_path.c_str());
}
return Unit{};

View File

@@ -34,13 +34,14 @@
#include "idmap2/PolicyUtils.h"
#include "idmap2/SysTrace.h"
using android::ApkAssets;
using android::base::StringPrintf;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::Idmap;
using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
@@ -91,9 +92,9 @@ Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
fulfilled_policies |= PolicyFlags::PUBLIC;
}
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return Error("failed to load apk %s", target_apk_path.c_str());
const auto target = TargetResourceContainer::FromPath(target_apk_path);
if (!target) {
return Error("failed to load target '%s'", target_apk_path.c_str());
}
std::vector<std::string> idmap_paths;
@@ -108,14 +109,14 @@ Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
// TODO(b/175014391): Support multiple overlay tags in OverlayConfig
if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies,
!ignore_overlayable)) {
const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
if (!overlay) {
LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
continue;
}
const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies,
!ignore_overlayable);
const auto idmap =
Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable);
if (!idmap) {
LOG(WARNING) << "failed to create idmap";
continue;

View File

@@ -37,7 +37,6 @@
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
#include "utils/String16.h"
#include "utils/String8.h"
@@ -52,10 +51,10 @@ using android::base::StringPrintf;
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::IdmapHeader;
using android::idmap2::OverlayResourceContainer;
using android::idmap2::ResourceId;
using android::idmap2::Result;
using android::idmap2::Unit;
using android::idmap2::utils::ExtractOverlayManifestInfo;
namespace {
@@ -195,12 +194,17 @@ Result<Unit> Lookup(const std::vector<std::string>& args) {
}
apk_assets.push_back(std::move(target_apk));
auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(),
idmap_header->GetOverlayName());
auto overlay = OverlayResourceContainer::FromPath(idmap_header->GetOverlayPath());
if (!overlay) {
return overlay.GetError();
}
auto manifest_info = (*overlay)->FindOverlayInfo(idmap_header->GetOverlayName());
if (!manifest_info) {
return manifest_info.GetError();
}
target_package_name = manifest_info->target_package;
target_package_name = (*manifest_info).target_package;
} else if (target_path != idmap_header->GetTargetPath()) {
return Error("different target APKs (expected target APK %s but %s has target APK %s)",
target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str());

View File

@@ -18,10 +18,10 @@
#include <sys/stat.h> // umask
#include <sys/types.h> // umask
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <ostream>
@@ -35,19 +35,20 @@
#include "idmap2/Idmap.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
#include "idmap2/ZipFile.h"
#include "utils/String8.h"
using android::IPCThreadState;
using android::base::StringPrintf;
using android::binder::Status;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::GetPackageCrc;
using android::idmap2::FabricatedOverlay;
using android::idmap2::FabricatedOverlayContainer;
using android::idmap2::Idmap;
using android::idmap2::IdmapHeader;
using android::idmap2::ZipFile;
using android::idmap2::OverlayResourceContainer;
using android::idmap2::TargetResourceContainer;
using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
using android::idmap2::utils::RandomStringForPath;
using android::idmap2::utils::UidHasWriteAccessToPath;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
@@ -69,39 +70,24 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) {
return static_cast<PolicyBitmask>(arg);
}
Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
const auto zip = ZipFile::Open(apk_path);
if (!zip) {
return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
}
const auto crc = GetPackageCrc(*zip);
if (!crc) {
return error(crc.GetErrorMessage());
}
*out_crc = *crc;
return ok();
}
} // namespace
namespace android::os {
Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
assert(_aidl_return);
SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_apk_path;
*_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_path;
*_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
return ok();
}
Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
Status Idmap2Service::removeIdmap(const std::string& overlay_path, int32_t user_id ATTRIBUTE_UNUSED,
bool* _aidl_return) {
assert(_aidl_return);
SYSTRACE << "Idmap2Service::removeIdmap " << overlay_apk_path;
SYSTRACE << "Idmap2Service::removeIdmap " << overlay_path;
const uid_t uid = IPCThreadState::self()->getCallingUid();
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
if (!UidHasWriteAccessToPath(uid, idmap_path)) {
*_aidl_return = false;
return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
@@ -115,85 +101,78 @@ Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
return ok();
}
Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
const std::string& overlay_apk_path, int32_t fulfilled_policies,
Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
bool* _aidl_return) {
SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path;
assert(_aidl_return);
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
std::ifstream fin(idmap_path);
const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
fin.close();
if (!header) {
*_aidl_return = false;
return error("failed to parse idmap header");
LOG(WARNING) << "failed to parse idmap header of '" << idmap_path << "'";
return ok();
}
uint32_t target_crc;
if (target_apk_path == kFrameworkPath && android_crc_) {
target_crc = *android_crc_;
} else {
auto target_crc_status = GetCrc(target_apk_path, &target_crc);
if (!target_crc_status.isOk()) {
*_aidl_return = false;
return target_crc_status;
}
// Loading the framework zip can take several milliseconds. Cache the crc of the framework
// resource APK to reduce repeated work during boot.
if (target_apk_path == kFrameworkPath) {
android_crc_ = target_crc;
}
}
uint32_t overlay_crc;
auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
if (!overlay_crc_status.isOk()) {
const auto target = GetTargetContainer(target_path);
if (!target) {
*_aidl_return = false;
return overlay_crc_status;
LOG(WARNING) << "failed to load target '" << target_path << "'";
return ok();
}
const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
if (!overlay) {
*_aidl_return = false;
LOG(WARNING) << "failed to load overlay '" << overlay_path << "'";
return ok();
}
// TODO(162841629): Support passing overlay name to idmap2d verify
auto up_to_date =
header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc,
header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name,
ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
*_aidl_return = static_cast<bool>(up_to_date);
return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
if (!up_to_date) {
LOG(WARNING) << "idmap '" << idmap_path
<< "' not up to date : " << up_to_date.GetErrorMessage();
}
return ok();
}
Status Idmap2Service::createIdmap(const std::string& target_apk_path,
const std::string& overlay_apk_path, int32_t fulfilled_policies,
Status Idmap2Service::createIdmap(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
std::optional<std::string>* _aidl_return) {
assert(_aidl_return);
SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path;
_aidl_return->reset();
const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, idmap_path)) {
return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
idmap_path.c_str(), uid));
}
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return error("failed to load apk " + target_apk_path);
const auto target = GetTargetContainer(target_path);
if (!target) {
return error("failed to load target '%s'" + target_path);
}
const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
return error("failed to load apk " + overlay_apk_path);
const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
if (!overlay) {
return error("failed to load apk overlay '%s'" + overlay_path);
}
// TODO(162841629): Support passing overlay name to idmap2d create
const auto idmap =
Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable);
const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name,
policy_bitmask, enforce_overlayable);
if (!idmap) {
return error(idmap.GetErrorMessage());
}
@@ -220,4 +199,155 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path,
return ok();
}
idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
const std::string& target_path) {
if (target_path == kFrameworkPath) {
if (framework_apk_cache_ == nullptr) {
// Initialize the framework APK cache.
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return target.GetError();
}
framework_apk_cache_ = std::move(*target);
}
return {framework_apk_cache_.get()};
}
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return target.GetError();
}
return {std::move(*target)};
}
Status Idmap2Service::createFabricatedOverlay(
const os::FabricatedOverlayInternal& overlay,
std::optional<os::FabricatedOverlayInfo>* _aidl_return) {
idmap2::FabricatedOverlay::Builder builder(overlay.packageName, overlay.overlayName,
overlay.targetPackageName);
if (!overlay.targetOverlayable.empty()) {
builder.SetOverlayable(overlay.targetOverlayable);
}
for (const auto& res : overlay.entries) {
builder.SetResourceValue(res.resourceName, res.dataType, res.data);
}
// Generate the file path of the fabricated overlay and ensure it does not collide with an
// existing path. Re-registering a fabricated overlay will always result in an updated path.
std::string path;
std::string file_name;
do {
constexpr size_t kSuffixLength = 4;
const std::string random_suffix = RandomStringForPath(kSuffixLength);
file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
overlay.overlayName.c_str(), random_suffix.c_str());
path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
// Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
// process to abort since the name exceeds the maximum file name size.
const size_t kMaxFileNameLength = 255;
if (file_name.size() > kMaxFileNameLength) {
return error(
base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters",
file_name.c_str(), kMaxFileNameLength));
}
} while (std::filesystem::exists(path));
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, path)) {
return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access",
path.c_str(), uid));
}
// Persist the fabricated overlay.
umask(kIdmapFilePermissionMask);
std::ofstream fout(path);
if (fout.fail()) {
return error("failed to open frro path " + path);
}
const auto frro = builder.Build();
if (!frro) {
return error(StringPrintf("failed to serialize '%s:%s': %s", overlay.packageName.c_str(),
overlay.overlayName.c_str(), frro.GetErrorMessage().c_str()));
}
auto result = frro->ToBinaryStream(fout);
if (!result) {
unlink(path.c_str());
return error("failed to write to frro path " + path + ": " + result.GetErrorMessage());
}
if (fout.fail()) {
unlink(path.c_str());
return error("failed to write to frro path " + path);
}
os::FabricatedOverlayInfo out_info;
out_info.packageName = overlay.packageName;
out_info.overlayName = overlay.overlayName;
out_info.targetPackageName = overlay.targetPackageName;
out_info.targetOverlayable = overlay.targetOverlayable;
out_info.path = path;
*_aidl_return = out_info;
return ok();
}
Status Idmap2Service::getFabricatedOverlayInfos(
std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
for (const auto& entry : std::filesystem::directory_iterator(kIdmapCacheDir)) {
if (!android::IsFabricatedOverlay(entry.path())) {
continue;
}
const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
if (!overlay) {
// This is a sign something went wrong.
LOG(ERROR) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
continue;
}
const auto info = (*overlay)->GetManifestInfo();
os::FabricatedOverlayInfo out_info;
out_info.packageName = info.package_name;
out_info.overlayName = info.name;
out_info.targetPackageName = info.target_package;
out_info.targetOverlayable = info.target_name;
out_info.path = entry.path();
_aidl_return->emplace_back(std::move(out_info));
}
return ok();
}
binder::Status Idmap2Service::deleteFabricatedOverlay(const std::string& overlay_path,
bool* _aidl_return) {
SYSTRACE << "Idmap2Service::deleteFabricatedOverlay " << overlay_path;
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, overlay_path)) {
*_aidl_return = false;
return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
overlay_path.c_str(), uid));
}
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
if (!UidHasWriteAccessToPath(uid, idmap_path)) {
*_aidl_return = false;
return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
idmap_path.c_str(), uid));
}
if (unlink(overlay_path.c_str()) != 0) {
*_aidl_return = false;
return error("failed to unlink " + overlay_path + ": " + strerror(errno));
}
if (unlink(idmap_path.c_str()) != 0) {
*_aidl_return = false;
return error("failed to unlink " + idmap_path + ": " + strerror(errno));
}
*_aidl_return = true;
return ok();
}
} // namespace android::os

View File

@@ -18,12 +18,14 @@
#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
#include <android-base/unique_fd.h>
#include <android/os/BnIdmap2.h>
#include <android/os/FabricatedOverlayInfo.h>
#include <binder/BinderService.h>
#include <idmap2/ResourceContainer.h>
#include <idmap2/Result.h>
#include <string>
#include "android/os/BnIdmap2.h"
namespace android::os {
class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
@@ -32,27 +34,58 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
return "idmap";
}
binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
binder::Status getIdmapPath(const std::string& overlay_path, int32_t user_id,
std::string* _aidl_return) override;
binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
binder::Status removeIdmap(const std::string& overlay_path, int32_t user_id,
bool* _aidl_return) override;
binder::Status verifyIdmap(const std::string& target_apk_path,
const std::string& overlay_apk_path, int32_t fulfilled_policies,
binder::Status verifyIdmap(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id,
bool* _aidl_return) override;
binder::Status createIdmap(const std::string& target_apk_path,
const std::string& overlay_apk_path, int32_t fulfilled_policies,
binder::Status createIdmap(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id,
std::optional<std::string>* _aidl_return) override;
binder::Status createFabricatedOverlay(
const os::FabricatedOverlayInternal& overlay,
std::optional<os::FabricatedOverlayInfo>* _aidl_return) override;
binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
bool* _aidl_return) override;
binder::Status getFabricatedOverlayInfos(
std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
private:
// Cache the crc of the android framework package since the crc cannot change without a reboot.
std::optional<uint32_t> android_crc_;
// idmap2d is killed after a period of inactivity, so any information stored on this class should
// be able to be recalculated if idmap2 dies and restarts.
std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
std::vector<os::FabricatedOverlayInfo> fabricated_overlays_;
template <typename T>
using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
template <typename T>
WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
};
template <typename T>
const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
auto u = std::get_if<T*>(&ptr);
if (u != nullptr) {
return *u;
}
return std::get<std::unique_ptr<T>>(ptr).get();
}
} // namespace android::os
#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/**
* @hide
*/
parcelable FabricatedOverlayInfo {
@utf8InCpp String path;
@utf8InCpp String packageName;
@utf8InCpp String overlayName;
@utf8InCpp String targetPackageName;
@utf8InCpp String targetOverlayable;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.os.FabricatedOverlayInternalEntry;
/**
* @hide
*/
parcelable FabricatedOverlayInternal {
@utf8InCpp String packageName;
@utf8InCpp String overlayName;
@utf8InCpp String targetPackageName;
@utf8InCpp String targetOverlayable;
List<FabricatedOverlayInternalEntry> entries;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/**
* @hide
*/
parcelable FabricatedOverlayInternalEntry {
@utf8InCpp String resourceName;
int dataType;
int data;
}

View File

@@ -16,6 +16,9 @@
package android.os;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInfo;
/**
* @hide
*/
@@ -23,13 +26,18 @@ interface IIdmap2 {
@utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
boolean verifyIdmap(@utf8InCpp String targetApkPath,
@utf8InCpp String overlayApkPath,
@utf8InCpp String overlayApkPath,
@utf8InCpp String overlayName,
int fulfilledPolicies,
boolean enforceOverlayable,
int userId);
@nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
@utf8InCpp String overlayApkPath,
@utf8InCpp String overlayName,
int fulfilledPolicies,
boolean enforceOverlayable,
int userId);
@nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
List<FabricatedOverlayInfo> getFabricatedOverlayInfos();
boolean deleteFabricatedOverlay(@utf8InCpp String path);
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
#define IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
#include <libidmap2/proto/fabricated_v1.pb.h>
#include <iostream>
#include <map>
#include <memory>
#include <unordered_map>
#include "idmap2/ResourceContainer.h"
#include "idmap2/Result.h"
namespace android::idmap2 {
struct FabricatedOverlay {
struct Builder {
Builder(const std::string& package_name, const std::string& name,
const std::string& target_package_name);
Builder& SetOverlayable(const std::string& name);
Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
uint32_t data_value);
WARN_UNUSED Result<FabricatedOverlay> Build();
private:
struct Entry {
std::string resource_name;
DataType data_type;
DataValue data_value;
};
std::string package_name_;
std::string name_;
std::string target_package_name_;
std::string target_overlayable_;
std::vector<Entry> entries_;
};
Result<Unit> ToBinaryStream(std::ostream& stream) const;
static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
private:
struct SerializedData {
std::unique_ptr<uint8_t[]> data;
size_t data_size;
uint32_t crc;
};
Result<SerializedData*> InitializeData() const;
Result<uint32_t> GetCrc() const;
FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::optional<uint32_t> crc_from_disk = {});
pb::FabricatedOverlay overlay_pb_;
std::optional<uint32_t> crc_from_disk_;
mutable std::optional<SerializedData> data_;
friend struct FabricatedOverlayContainer;
};
struct FabricatedOverlayContainer : public OverlayResourceContainer {
static Result<std::unique_ptr<FabricatedOverlayContainer>> FromPath(std::string path);
static std::unique_ptr<FabricatedOverlayContainer> FromOverlay(FabricatedOverlay&& overlay);
WARN_UNUSED OverlayManifestInfo GetManifestInfo() const;
// inherited from OverlayResourceContainer
WARN_UNUSED Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
WARN_UNUSED Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
// inherited from ResourceContainer
WARN_UNUSED Result<uint32_t> GetCrc() const override;
WARN_UNUSED const std::string& GetPath() const override;
WARN_UNUSED Result<std::string> GetResourceName(ResourceId id) const override;
~FabricatedOverlayContainer() override;
private:
FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path);
FabricatedOverlay overlay_;
std::string path_;
};
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H

View File

@@ -17,6 +17,7 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
#include <random>
#include <string>
namespace android::idmap2::utils {
@@ -26,6 +27,8 @@ constexpr const mode_t kIdmapFilePermissionMask = 0133; // u=rw,g=r,o=r
bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
std::string RandomStringForPath(size_t length);
} // namespace android::idmap2::utils
#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_

View File

@@ -23,8 +23,8 @@
* debug_info
* data := data_header target_entry* target_inline_entry* overlay_entry*
* string_pool
* data_header := target_package_id overlay_package_id padding(2) target_entry_count
* target_inline_entry_count overlay_entry_count string_pool_index
* data_header := target_entry_count target_inline_entry_count overlay_entry_count
* string_pool_index
* target_entry := target_id overlay_id
* target_inline_entry := target_id Res_value::size padding(1) Res_value::type
* Res_value::value
@@ -68,11 +68,10 @@
#include <vector>
#include "android-base/macros.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
#include "idmap2/ResourceContainer.h"
#include "idmap2/ResourceMapping.h"
#include "idmap2/ZipFile.h"
namespace android::idmap2 {
@@ -85,9 +84,6 @@ static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic;
// current version of the idmap binary format; must be incremented when the format is changed
static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
// Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
class IdmapHeader {
public:
static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
@@ -135,9 +131,9 @@ class IdmapHeader {
// Invariant: anytime the idmap data encoding is changed, the idmap version
// field *must* be incremented. Because of this, we know that if the idmap
// header is up-to-date the entire file is up-to-date.
Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, PolicyBitmask fulfilled_policies,
bool enforce_overlayable) const;
Result<Unit> IsUpToDate(const TargetResourceContainer& target,
const OverlayResourceContainer& overlay, const std::string& overlay_name,
PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, uint32_t target_crc,
@@ -169,14 +165,6 @@ class IdmapData {
public:
static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
inline PackageId GetTargetPackageId() const {
return target_package_id_;
}
inline PackageId GetOverlayPackageId() const {
return overlay_package_id_;
}
inline uint32_t GetTargetEntryCount() const {
return target_entry_count;
}
@@ -196,8 +184,6 @@ class IdmapData {
void accept(Visitor* v) const;
private:
PackageId target_package_id_;
PackageId overlay_package_id_;
uint32_t target_entry_count;
uint32_t target_entry_inline_count;
uint32_t overlay_entry_count;
@@ -275,11 +261,10 @@ class Idmap {
// file is used; change this in the next version of idmap to use a named
// package instead; also update FromApkAssets to take additional parameters:
// the target and overlay package names
static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const std::string& overlay_name,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable);
static Result<std::unique_ptr<const Idmap>> FromContainers(
const TargetResourceContainer& target, const OverlayResourceContainer& overlay,
const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable);
const std::unique_ptr<const IdmapHeader>& GetHeader() const {
return header_;

View File

@@ -41,9 +41,8 @@ class PrettyPrintVisitor : public Visitor {
private:
std::ostream& stream_;
AssetManager2 target_am_;
AssetManager2 overlay_am_;
std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
std::unique_ptr<TargetResourceContainer> target_;
std::unique_ptr<OverlayResourceContainer> overlay_;
};
} // namespace idmap2

View File

@@ -49,10 +49,9 @@ class RawPrintVisitor : public Visitor {
void pad(size_t padding);
std::ostream& stream_;
std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
AssetManager2 target_am_;
AssetManager2 overlay_am_;
size_t offset_;
std::unique_ptr<TargetResourceContainer> target_;
std::unique_ptr<OverlayResourceContainer> overlay_;
};
} // namespace idmap2

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
#define IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
#include <string>
#include <variant>
#include <vector>
#include "idmap2/Policies.h"
#include "idmap2/ResourceUtils.h"
namespace android::idmap2 {
struct ResourceContainer {
WARN_UNUSED virtual Result<uint32_t> GetCrc() const = 0;
WARN_UNUSED virtual const std::string& GetPath() const = 0;
WARN_UNUSED virtual Result<std::string> GetResourceName(ResourceId id) const = 0;
virtual ~ResourceContainer() = default;
};
struct TargetResourceContainer : public ResourceContainer {
static Result<std::unique_ptr<TargetResourceContainer>> FromPath(std::string path);
WARN_UNUSED virtual Result<bool> DefinesOverlayable() const = 0;
WARN_UNUSED virtual Result<const android::OverlayableInfo*> GetOverlayableInfo(
ResourceId id) const = 0;
WARN_UNUSED virtual Result<ResourceId> GetResourceId(const std::string& name) const = 0;
~TargetResourceContainer() override = default;
};
struct OverlayManifestInfo {
std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
};
struct OverlayData {
struct ResourceIdValue {
// The overlay resource id.
ResourceId overlay_id;
// Whether or not references to the overlay resource id should be rewritten to its corresponding
// target id during resource resolution.
bool rewrite_id;
};
struct Value {
std::string resource_name;
std::variant<ResourceIdValue, TargetValue> value;
};
struct InlineStringPoolData {
// The binary data of the android::ResStringPool string pool.
std::unique_ptr<uint8_t[]> data;
// The length of the binary data.
uint32_t data_length;
// The offset added to TargetValue#data_value (the index of the string in the inline string
// pool) in order to prevent the indices of the overlay resource table string pool from
// colliding with the inline string pool indices.
uint32_t string_pool_offset;
};
// The overlay's mapping of target resource name to overlaid value. Use a vector to enforce that
// the overlay pairs are inserted into the ResourceMapping in the specified ordered.
std::vector<Value> pairs;
// If the overlay maps a target resource to a string literal (not a string resource), then the
// this field contains information about the string pool in which the string literal resides so it
// can be inlined into an idmap.
std::optional<InlineStringPoolData> string_pool_data;
};
struct OverlayResourceContainer : public ResourceContainer {
static Result<std::unique_ptr<OverlayResourceContainer>> FromPath(std::string path);
WARN_UNUSED virtual Result<OverlayManifestInfo> FindOverlayInfo(
const std::string& name) const = 0;
WARN_UNUSED virtual Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const = 0;
~OverlayResourceContainer() override = default;
};
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H

View File

@@ -17,30 +17,22 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
#define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
#include <androidfw/ApkAssets.h>
#include <map>
#include <memory>
#include <utility>
#include "androidfw/ApkAssets.h"
#include "idmap2/FabricatedOverlay.h"
#include "idmap2/LogInfo.h"
#include "idmap2/Policies.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/XmlParser.h"
using android::idmap2::utils::OverlayManifestInfo;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
namespace android::idmap2 {
struct TargetValue {
typedef uint8_t DataType;
typedef uint32_t DataValue;
DataType data_type;
DataValue data_value;
};
using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>;
using OverlayResourceMap = std::map<ResourceId, ResourceId>;
@@ -49,94 +41,60 @@ class ResourceMapping {
// Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
// `false` disables all overlayable and policy enforcement: this is intended for backwards
// compatibility pre-Q and unit tests.
static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable, LogInfo& log_info);
static Result<ResourceMapping> FromContainers(const TargetResourceContainer& target,
const OverlayResourceContainer& overlay,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable, LogInfo& log_info);
// Retrieves the mapping of target resource id to overlay value.
inline const TargetResourceMap& GetTargetToOverlayMap() const {
return target_map_;
}
WARN_UNUSED const TargetResourceMap& GetTargetToOverlayMap() const;
// Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
// an overlay resource to appear as a reference to its corresponding target resource at runtime.
OverlayResourceMap GetOverlayToTargetMap() const;
// Retrieves the build-time package id of the target package.
inline uint32_t GetTargetPackageId() const {
return target_package_id_;
}
// Retrieves the build-time package id of the overlay package.
inline uint32_t GetOverlayPackageId() const {
return overlay_package_id_;
}
WARN_UNUSED const OverlayResourceMap& GetOverlayToTargetMap() const;
// Retrieves the offset that was added to the index of inline string overlay values so the indices
// do not collide with the indices of the overlay resource table string pool.
inline uint32_t GetStringPoolOffset() const {
return string_pool_offset_;
}
WARN_UNUSED uint32_t GetStringPoolOffset() const;
// Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
inline const StringPiece GetStringPoolData() const {
return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
string_pool_data_length_);
}
WARN_UNUSED StringPiece GetStringPoolData() const;
private:
ResourceMapping() = default;
// Maps a target resource id to an overlay resource id.
// If rewrite_overlay_reference is `true` then references to the overlay
// resource should appear as a reference to its corresponding target resource at runtime.
Result<Unit> AddMapping(ResourceId target_resource, ResourceId overlay_resource,
bool rewrite_overlay_reference);
// Maps a target resource id to a data type and value combination.
// The `data_type` is the runtime format of the data value (see Res_value::dataType).
Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
TargetValue::DataValue data_value);
// Removes the overlay value mapping for the target resource.
void RemoveMapping(ResourceId target_resource);
// Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
size_t string_pool_offset,
const XmlParser& overlay_parser,
LogInfo& log_info);
// Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
// a target resource, a resource must exist in the overlay with the same type and entry name as
// the target resource.
static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
const AssetManager2* overlay_am,
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
LogInfo& log_info);
// Removes resources that do not pass policy or overlayable checks of the target package.
void FilterOverlayableResources(const AssetManager2* target_am,
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
// Maps a target resource id to an overlay resource id or a android::Res_value value.
//
// If `allow_rewriting_` is true, then the overlay-to-target map will be populated if the target
// resource id is mapped to an overlay resource id.
Result<Unit> AddMapping(ResourceId target_resource,
const std::variant<OverlayData::ResourceIdValue, TargetValue>& value);
TargetResourceMap target_map_;
std::multimap<ResourceId, ResourceId> overlay_map_;
uint32_t target_package_id_ = 0;
uint32_t overlay_package_id_ = 0;
OverlayResourceMap overlay_map_;
uint32_t string_pool_offset_ = 0;
uint32_t string_pool_data_length_ = 0;
std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
};
inline const TargetResourceMap& ResourceMapping::GetTargetToOverlayMap() const {
return target_map_;
}
inline const OverlayResourceMap& ResourceMapping::GetOverlayToTargetMap() const {
return overlay_map_;
}
inline uint32_t ResourceMapping::GetStringPoolOffset() const {
return string_pool_offset_;
}
inline StringPiece ResourceMapping::GetStringPoolData() const {
return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
string_pool_data_length_);
}
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_

View File

@@ -22,19 +22,26 @@
#include "androidfw/AssetManager2.h"
#include "idmap2/Result.h"
#include "idmap2/ZipFile.h"
namespace android::idmap2 {
// use typedefs to let the compiler warn us about implicit casts
typedef uint32_t ResourceId; // 0xpptteeee
typedef uint8_t PackageId; // pp in 0xpptteeee
typedef uint8_t TypeId; // tt in 0xpptteeee
typedef uint16_t EntryId; // eeee in 0xpptteeee
#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
// use typedefs to let the compiler warn us about implicit casts
using ResourceId = uint32_t; // 0xpptteeee
using PackageId = uint8_t; // pp in 0xpptteeee
using TypeId = uint8_t; // tt in 0xpptteeee
using EntryId = uint16_t; // eeee in 0xpptteeee
using DataType = uint8_t; // Res_value::dataType
using DataValue = uint32_t; // Res_value::data
struct TargetValue {
DataType data_type;
DataValue data_value;
};
namespace utils {
// Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
@@ -43,20 +50,10 @@ bool IsReference(uint8_t data_type);
// Converts the Res_value::data_type to a human-readable string representation.
StringPiece DataTypeToString(uint8_t data_type);
struct OverlayManifestInfo {
std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
};
Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
const std::string& name);
// Retrieves the type and entry name of the resource in the AssetManager in the form type/entry.
Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
} // namespace utils
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_

View File

@@ -30,8 +30,7 @@
namespace android::idmap2 {
class XmlParser {
public:
struct XmlParser {
using Event = ResXMLParser::event_code_t;
class iterator;
@@ -127,23 +126,19 @@ class XmlParser {
};
// Creates a new xml parser beginning at the first tag.
static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
bool copy_data = false);
~XmlParser();
static Result<XmlParser> Create(const void* data, size_t size, bool copy_data = false);
inline iterator tree_iterator() const {
return iterator(tree_);
return iterator(*tree_);
}
inline const ResStringPool& get_strings() const {
return tree_.getStrings();
return tree_->getStrings();
}
private:
XmlParser() = default;
mutable ResXMLTree tree_;
DISALLOW_COPY_AND_ASSIGN(XmlParser);
explicit XmlParser(std::unique_ptr<ResXMLTree> tree);
mutable std::unique_ptr<ResXMLTree> tree_;
};
} // namespace android::idmap2

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
#include <memory>
#include <string>
#include "android-base/macros.h"
#include "idmap2/Result.h"
#include "ziparchive/zip_archive.h"
namespace android::idmap2 {
struct MemoryChunk {
size_t size;
uint8_t buf[0];
static std::unique_ptr<MemoryChunk> Allocate(size_t size);
private:
MemoryChunk() {
}
};
class ZipFile {
public:
static std::unique_ptr<const ZipFile> Open(const std::string& path);
std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
Result<uint32_t> Crc(const std::string& entryPath) const;
~ZipFile();
private:
explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
}
const ::ZipArchiveHandle handle_;
DISALLOW_COPY_AND_ASSIGN(ZipFile);
};
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_

View File

@@ -87,10 +87,6 @@ void BinaryStreamVisitor::visit(const IdmapData& data) {
}
void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
Write8(header.GetTargetPackageId());
Write8(header.GetOverlayPackageId());
Write8(0U); // padding
Write8(0U); // padding
Write32(header.GetTargetEntryCount());
Write32(header.GetTargetInlineEntryCount());
Write32(header.GetOverlayEntryCount());

View File

@@ -0,0 +1,302 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "idmap2/FabricatedOverlay.h"
#include <androidfw/ResourceUtils.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <utils/ByteOrder.h>
#include <zlib.h>
#include <fstream>
namespace android::idmap2 {
namespace {
bool Read32(std::istream& stream, uint32_t* out) {
uint32_t value;
if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
*out = dtohl(value);
return true;
}
return false;
}
void Write32(std::ostream& stream, uint32_t value) {
uint32_t x = htodl(value);
stream.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
}
} // namespace
FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
std::optional<uint32_t> crc_from_disk)
: overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
}
FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
const std::string& target_package_name) {
package_name_ = package_name;
name_ = name;
target_package_name_ = target_package_name;
}
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetOverlayable(const std::string& name) {
target_overlayable_ = name;
return *this;
}
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
entries_.emplace_back(Entry{resource_name, data_type, data_value});
return *this;
}
Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
for (const auto& res_entry : entries_) {
StringPiece package_substr;
StringPiece type_name;
StringPiece entry_name;
if (!android::ExtractResourceName(StringPiece(res_entry.resource_name), &package_substr,
&type_name, &entry_name)) {
return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
}
std::string package_name =
package_substr.empty() ? target_package_name_ : package_substr.to_string();
if (type_name.empty()) {
return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
}
if (entry_name.empty()) {
return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
}
auto package = entries.find(package_name);
if (package == entries.end()) {
package = entries
.insert(std::make_pair<>(
package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
.first;
}
auto type = package->second.find(type_name.to_string());
if (type == package->second.end()) {
type =
package->second
.insert(std::make_pair<>(type_name.to_string(), std::map<std::string, TargetValue>()))
.first;
}
auto entry = type->second.find(entry_name.to_string());
if (entry == type->second.end()) {
entry = type->second.insert(std::make_pair<>(entry_name.to_string(), TargetValue())).first;
}
entry->second = TargetValue{res_entry.data_type, res_entry.data_value};
}
pb::FabricatedOverlay overlay_pb;
overlay_pb.set_package_name(package_name_);
overlay_pb.set_name(name_);
overlay_pb.set_target_package_name(target_package_name_);
overlay_pb.set_target_overlayable(target_overlayable_);
for (const auto& package : entries) {
auto package_pb = overlay_pb.add_packages();
package_pb->set_name(package.first);
for (const auto& type : package.second) {
auto type_pb = package_pb->add_types();
type_pb->set_name(type.first);
for (const auto& entry : type.second) {
auto entry_pb = type_pb->add_entries();
entry_pb->set_name(entry.first);
pb::ResourceValue* value = entry_pb->mutable_res_value();
value->set_data_type(entry.second.data_type);
value->set_data_value(entry.second.data_value);
}
}
}
return FabricatedOverlay(std::move(overlay_pb));
}
Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
uint32_t magic;
if (!Read32(stream, &magic)) {
return Error("Failed to read fabricated overlay magic.");
}
if (magic != kFabricatedOverlayMagic) {
return Error("Not a fabricated overlay file.");
}
uint32_t version;
if (!Read32(stream, &version)) {
return Error("Failed to read fabricated overlay version.");
}
if (version != 1) {
return Error("Invalid fabricated overlay version '%u'.", version);
}
uint32_t crc;
if (!Read32(stream, &crc)) {
return Error("Failed to read fabricated overlay version.");
}
pb::FabricatedOverlay overlay{};
if (!overlay.ParseFromIstream(&stream)) {
return Error("Failed read fabricated overlay proto.");
}
// If the proto version is the latest version, then the contents of the proto must be the same
// when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
// proto to the latest version will likely change the contents of the fabricated overlay.
return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
? std::optional<uint32_t>(crc)
: std::nullopt);
}
Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
if (!data_.has_value()) {
auto size = overlay_pb_.ByteSizeLong();
auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
// Ensure serialization is deterministic
google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
google::protobuf::io::CodedOutputStream output_stream(&array_stream);
output_stream.SetSerializationDeterministic(true);
overlay_pb_.SerializeWithCachedSizes(&output_stream);
if (output_stream.HadError() || size != output_stream.ByteCount()) {
return Error("Failed to serialize fabricated overlay.");
}
// Calculate the crc using the proto data and the version.
uint32_t crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
sizeof(uint32_t));
crc = crc32(crc, data.get(), size);
data_ = SerializedData{std::move(data), size, crc};
}
return &(*data_);
}
Result<uint32_t> FabricatedOverlay::GetCrc() const {
if (crc_from_disk_.has_value()) {
return *crc_from_disk_;
}
auto data = InitializeData();
if (!data) {
return data.GetError();
}
return (*data)->crc;
}
Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
auto data = InitializeData();
if (!data) {
return data.GetError();
}
Write32(stream, kFabricatedOverlayMagic);
Write32(stream, kFabricatedOverlayCurrentVersion);
Write32(stream, (*data)->crc);
stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
if (stream.bad()) {
return Error("Failed to write serialized fabricated overlay.");
}
return Unit{};
}
using FabContainer = FabricatedOverlayContainer;
FabContainer::FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path)
: overlay_(std::forward<FabricatedOverlay>(overlay)), path_(std::forward<std::string>(path)) {
}
FabContainer::~FabricatedOverlayContainer() = default;
Result<std::unique_ptr<FabContainer>> FabContainer::FromPath(std::string path) {
std::fstream fin(path);
auto overlay = FabricatedOverlay::FromBinaryStream(fin);
if (!overlay) {
return overlay.GetError();
}
return std::unique_ptr<FabContainer>(
new FabricatedOverlayContainer(std::move(*overlay), std::move(path)));
}
std::unique_ptr<FabricatedOverlayContainer> FabContainer::FromOverlay(FabricatedOverlay&& overlay) {
return std::unique_ptr<FabContainer>(
new FabricatedOverlayContainer(std::move(overlay), {} /* path */));
}
OverlayManifestInfo FabContainer::GetManifestInfo() const {
const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
return OverlayManifestInfo{
.package_name = overlay_pb.package_name(),
.name = overlay_pb.name(),
.target_package = overlay_pb.target_package_name(),
.target_name = overlay_pb.target_overlayable(),
};
}
Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
const OverlayManifestInfo info = GetManifestInfo();
if (name != info.name) {
return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
}
return info;
}
Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info) const {
const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
if (info.name != overlay_pb.name()) {
return Error("Failed to find name '%s' in fabricated overlay", info.name.c_str());
}
OverlayData result{};
for (const auto& package : overlay_pb.packages()) {
for (const auto& type : package.types()) {
for (const auto& entry : type.entries()) {
auto name = base::StringPrintf("%s:%s/%s", package.name().c_str(), type.name().c_str(),
entry.name().c_str());
const auto& res_value = entry.res_value();
result.pairs.emplace_back(OverlayData::Value{
name, TargetValue{.data_type = static_cast<uint8_t>(res_value.data_type()),
.data_value = res_value.data_value()}});
}
}
}
return result;
}
Result<uint32_t> FabContainer::GetCrc() const {
return overlay_.GetCrc();
}
const std::string& FabContainer::GetPath() const {
return path_;
}
Result<std::string> FabContainer::GetResourceName(ResourceId /* id */) const {
return Error("Fabricated overlay does not contain resources.");
}
} // namespace android::idmap2

View File

@@ -47,4 +47,19 @@ bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path
}
#endif
std::string RandomStringForPath(const size_t length) {
constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
std::string out_rand;
out_rand.reserve(length);
std::random_device rd;
std::uniform_int_distribution<int> dist(0, kCharLastIndex);
for (size_t i = 0; i < length; i++) {
out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
}
return out_rand;
}
} // namespace android::idmap2::utils

View File

@@ -20,23 +20,16 @@
#include <iostream>
#include <iterator>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "androidfw/AssetManager2.h"
#include "idmap2/ResourceMapping.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
#include "idmap2/ZipFile.h"
#include "utils/String16.h"
#include "utils/String8.h"
namespace android::idmap2 {
@@ -93,22 +86,13 @@ bool WARN_UNUSED ReadString(std::istream& stream, std::string* out) {
} // namespace
Result<uint32_t> GetPackageCrc(const ZipFile& zip) {
const Result<uint32_t> a = zip.Crc("resources.arsc");
const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
return a && b
? Result<uint32_t>(*a ^ *b)
: Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc");
}
std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_)) {
return nullptr;
}
if (idmap_header->magic_ != kIdmapMagic ||
idmap_header->version_ != kIdmapCurrentVersion) {
if (idmap_header->magic_ != kIdmapMagic || idmap_header->version_ != kIdmapCurrentVersion) {
// Do not continue parsing if the file is not a current version idmap.
return nullptr;
}
@@ -127,32 +111,22 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s
return std::move(idmap_header);
}
Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
const std::string& overlay_path,
Result<Unit> IdmapHeader::IsUpToDate(const TargetResourceContainer& target,
const OverlayResourceContainer& overlay,
const std::string& overlay_name,
PolicyBitmask fulfilled_policies,
bool enforce_overlayable) const {
const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
if (!target_zip) {
return Error("failed to open target %s", target_path.c_str());
}
const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
const Result<uint32_t> target_crc = target.GetCrc();
if (!target_crc) {
return Error("failed to get target crc");
}
const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
if (!overlay_zip) {
return Error("failed to overlay target %s", overlay_path.c_str());
}
const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
const Result<uint32_t> overlay_crc = overlay.GetCrc();
if (!overlay_crc) {
return Error("failed to get overlay crc");
}
return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc,
return IsUpToDate(target.GetPath(), overlay.GetPath(), overlay_name, *target_crc, *overlay_crc,
fulfilled_policies, enforce_overlayable);
}
@@ -209,11 +183,7 @@ Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
uint8_t padding;
if (!Read8(stream, &idmap_data_header->target_package_id_) ||
!Read8(stream, &idmap_data_header->overlay_package_id_) || !Read8(stream, &padding) ||
!Read8(stream, &padding) || !Read32(stream, &idmap_data_header->target_entry_count) ||
if (!Read32(stream, &idmap_data_header->target_entry_count) ||
!Read32(stream, &idmap_data_header->target_entry_inline_count) ||
!Read32(stream, &idmap_data_header->overlay_entry_count) ||
!Read32(stream, &idmap_data_header->string_pool_index_offset)) {
@@ -321,8 +291,6 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping(
}
std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId();
data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
data_header->target_entry_inline_count =
static_cast<uint32_t>(data->target_inline_entries_.size());
@@ -332,57 +300,46 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping(
return {std::move(data)};
}
Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const std::string& overlay_name,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
Result<std::unique_ptr<const Idmap>> Idmap::FromContainers(const TargetResourceContainer& target,
const OverlayResourceContainer& overlay,
const std::string& overlay_name,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
SYSTRACE << "Idmap::FromApkAssets";
const std::string& target_apk_path = target_apk_assets.GetPath();
const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
if (!target_zip) {
return Error("failed to open target as zip");
}
const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
if (!overlay_zip) {
return Error("failed to open overlay as zip");
}
std::unique_ptr<IdmapHeader> header(new IdmapHeader());
header->magic_ = kIdmapMagic;
header->version_ = kIdmapCurrentVersion;
Result<uint32_t> crc = GetPackageCrc(*target_zip);
if (!crc) {
return Error(crc.GetError(), "failed to get zip CRC for target");
const auto target_crc = target.GetCrc();
if (!target_crc) {
return Error(target_crc.GetError(), "failed to get zip CRC for '%s'", target.GetPath().data());
}
header->target_crc_ = *crc;
header->target_crc_ = *target_crc;
crc = GetPackageCrc(*overlay_zip);
if (!crc) {
return Error(crc.GetError(), "failed to get zip CRC for overlay");
const auto overlay_crc = overlay.GetCrc();
if (!overlay_crc) {
return Error(overlay_crc.GetError(), "failed to get zip CRC for '%s'",
overlay.GetPath().data());
}
header->overlay_crc_ = *crc;
header->overlay_crc_ = *overlay_crc;
header->fulfilled_policies_ = fulfilled_policies;
header->enforce_overlayable_ = enforce_overlayable;
header->target_path_ = target_apk_path;
header->overlay_path_ = overlay_apk_path;
header->target_path_ = target.GetPath();
header->overlay_path_ = overlay.GetPath();
header->overlay_name_ = overlay_name;
auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name);
auto info = overlay.FindOverlayInfo(overlay_name);
if (!info) {
return info.GetError();
return Error(info.GetError(), "failed to get overlay info for '%s'", overlay.GetPath().data());
}
LogInfo log_info;
auto resource_mapping =
ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info,
fulfilled_policies, enforce_overlayable, log_info);
auto resource_mapping = ResourceMapping::FromContainers(
target, overlay, *info, fulfilled_policies, enforce_overlayable, log_info);
if (!resource_mapping) {
return resource_mapping.GetError();
return Error(resource_mapping.GetError(), "failed to generate resource map for '%s'",
overlay.GetPath().data());
}
auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);

View File

@@ -27,8 +27,6 @@
namespace android::idmap2 {
#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
#define TAB " "
void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
@@ -36,8 +34,8 @@ void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
void PrettyPrintVisitor::visit(const IdmapHeader& header) {
stream_ << "Paths:" << std::endl
<< TAB "target apk path : " << header.GetTargetPath() << std::endl
<< TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
<< TAB "target path : " << header.GetTargetPath() << std::endl
<< TAB "overlay path : " << header.GetOverlayPath() << std::endl;
if (!header.GetOverlayName().empty()) {
stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
@@ -53,14 +51,11 @@ void PrettyPrintVisitor::visit(const IdmapHeader& header) {
}
}
if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) {
target_am_.SetApkAssets({target_apk_.get()});
apk_assets_.push_back(std::move(target_apk_));
if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
target_ = std::move(*target);
}
if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) {
overlay_am_.SetApkAssets({overlay_apk.get()});
apk_assets_.push_back(std::move(overlay_apk));
if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
overlay_ = std::move(*overlay);
}
stream_ << "Mapping:" << std::endl;
@@ -72,23 +67,20 @@ void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED)
void PrettyPrintVisitor::visit(const IdmapData& data) {
static constexpr const char* kUnknownResourceName = "???";
const bool target_package_loaded = !target_am_.GetApkAssets().empty();
const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
const ResStringPool string_pool(data.GetStringPoolData().data(), data.GetStringPoolData().size());
const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
for (const auto& target_entry : data.GetTargetEntries()) {
std::string target_name = kUnknownResourceName;
if (target_package_loaded) {
if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
if (target_ != nullptr) {
if (auto name = target_->GetResourceName(target_entry.target_id)) {
target_name = *name;
}
}
std::string overlay_name = kUnknownResourceName;
if (overlay_package_loaded) {
if (auto name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id)) {
if (overlay_ != nullptr) {
if (auto name = overlay_->GetResourceName(target_entry.overlay_id)) {
overlay_name = *name;
}
}
@@ -112,8 +104,8 @@ void PrettyPrintVisitor::visit(const IdmapData& data) {
}
std::string target_name = kUnknownResourceName;
if (target_package_loaded) {
if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
if (target_ != nullptr) {
if (auto name = target_->GetResourceName(target_entry.target_id)) {
target_name = *name;
}
}

View File

@@ -18,16 +18,13 @@
#include <algorithm>
#include <cstdarg>
#include <string>
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "androidfw/ApkAssets.h"
#include "idmap2/PolicyUtils.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
using android::ApkAssets;
using android::idmap2::policy::PoliciesToDebugString;
namespace android::idmap2 {
@@ -48,27 +45,19 @@ void RawPrintVisitor::visit(const IdmapHeader& header) {
print(header.GetOverlayName(), true /* print_value */, "overlay name");
print(header.GetDebugInfo(), false /* print_value */, "debug info");
auto target_apk_ = ApkAssets::Load(header.GetTargetPath());
if (target_apk_) {
target_am_.SetApkAssets({target_apk_.get()});
apk_assets_.push_back(std::move(target_apk_));
if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
target_ = std::move(*target);
}
auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath());
if (overlay_apk_) {
overlay_am_.SetApkAssets({overlay_apk_.get()});
apk_assets_.push_back(std::move(overlay_apk_));
if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
overlay_ = std::move(*overlay);
}
}
void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
const bool target_package_loaded = !target_am_.GetApkAssets().empty();
const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
for (auto& target_entry : data.GetTargetEntries()) {
Result<std::string> target_name(Error(""));
if (target_package_loaded) {
target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
if (target_ != nullptr) {
target_name = target_->GetResourceName(target_entry.target_id);
}
if (target_name) {
print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -77,8 +66,8 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
}
Result<std::string> overlay_name(Error(""));
if (overlay_package_loaded) {
overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id);
if (overlay_ != nullptr) {
overlay_name = overlay_->GetResourceName(target_entry.overlay_id);
}
if (overlay_name) {
print(target_entry.overlay_id, "overlay id: %s", overlay_name->c_str());
@@ -89,8 +78,8 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
for (auto& target_entry : data.GetTargetInlineEntries()) {
Result<std::string> target_name(Error(""));
if (target_package_loaded) {
target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
if (target_ != nullptr) {
target_name = target_->GetResourceName(target_entry.target_id);
}
if (target_name) {
print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -104,10 +93,10 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
utils::DataTypeToString(target_entry.value.data_type).data());
Result<std::string> overlay_name(Error(""));
if (overlay_package_loaded &&
if (overlay_ != nullptr &&
(target_entry.value.data_value == Res_value::TYPE_REFERENCE ||
target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.value.data_value);
overlay_name = overlay_->GetResourceName(target_entry.value.data_value);
}
if (overlay_name) {
@@ -119,8 +108,8 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
for (auto& overlay_entry : data.GetOverlayEntries()) {
Result<std::string> overlay_name(Error(""));
if (overlay_package_loaded) {
overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id);
if (overlay_ != nullptr) {
overlay_name = overlay_->GetResourceName(overlay_entry.overlay_id);
}
if (overlay_name) {
@@ -130,8 +119,8 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
}
Result<std::string> target_name(Error(""));
if (target_package_loaded) {
target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id);
if (target_ != nullptr) {
target_name = target_->GetResourceName(overlay_entry.target_id);
}
if (target_name) {
@@ -145,9 +134,6 @@ void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
}
void RawPrintVisitor::visit(const IdmapData::Header& header) {
print(header.GetTargetPackageId(), "target package id");
print(header.GetOverlayPackageId(), "overlay package id");
align();
print(header.GetTargetEntryCount(), "target entry count");
print(header.GetTargetInlineEntryCount(), "target inline entry count");
print(header.GetOverlayEntryCount(), "overlay entry count");

View File

@@ -0,0 +1,448 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "idmap2/ResourceContainer.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/AssetManager.h"
#include "androidfw/Util.h"
#include "idmap2/FabricatedOverlay.h"
#include "idmap2/XmlParser.h"
namespace android::idmap2 {
namespace {
#define REWRITE_PACKAGE(resid, package_id) \
(((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
constexpr ResourceId kAttrName = 0x01010003;
constexpr ResourceId kAttrResourcesMap = 0x01010609;
constexpr ResourceId kAttrTargetName = 0x0101044d;
constexpr ResourceId kAttrTargetPackage = 0x01010021;
// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
// this assumption tends to work out. That said, the correct thing to do is to scan
// resources.arsc for a package with a given name as read from the package manifest instead of
// relying on a hard-coded index. This however requires storing the package name in the idmap
// header, which in turn requires incrementing the idmap version. Because the initial version of
// idmap2 is compatible with idmap, this will have to wait for now.
const LoadedPackage* GetPackageAtIndex0(const LoadedArsc* loaded_arsc) {
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
if (packages.empty()) {
return nullptr;
}
return loaded_arsc->GetPackageById(packages[0]->GetPackageId());
}
Result<uint32_t> CalculateCrc(const ZipAssetsProvider* zip_assets) {
constexpr const char* kResourcesArsc = "resources.arsc";
std::optional<uint32_t> res_crc = zip_assets->GetCrc(kResourcesArsc);
if (!res_crc) {
return Error("failed to get CRC for '%s'", kResourcesArsc);
}
constexpr const char* kManifest = "AndroidManifest.xml";
std::optional<uint32_t> man_crc = zip_assets->GetCrc(kManifest);
if (!man_crc) {
return Error("failed to get CRC for '%s'", kManifest);
}
return *res_crc ^ *man_crc;
}
Result<XmlParser> OpenXmlParser(const std::string& entry_path, const ZipAssetsProvider* zip) {
auto manifest = zip->Open(entry_path);
if (manifest == nullptr) {
return Error("failed to find %s ", entry_path.c_str());
}
auto size = manifest->getLength();
auto buffer = manifest->getIncFsBuffer(true /* aligned */).convert<uint8_t>();
if (!buffer.verify(size)) {
return Error("failed to read entire %s", entry_path.c_str());
}
return XmlParser::Create(buffer.unsafe_ptr(), size, true /* copyData */);
}
Result<XmlParser> OpenXmlParser(ResourceId id, const ZipAssetsProvider* zip,
const AssetManager2* am) {
const auto ref_table = am->GetDynamicRefTableForCookie(0);
if (ref_table == nullptr) {
return Error("failed to find dynamic ref table for cookie 0");
}
ref_table->lookupResourceId(&id);
auto value = am->GetResource(id);
if (!value.has_value()) {
return Error("failed to find resource for id 0x%08x", id);
}
if (value->type != Res_value::TYPE_STRING) {
return Error("resource for is 0x%08x is not a file", id);
}
auto string_pool = am->GetStringPoolForCookie(value->cookie);
auto file = string_pool->string8ObjectAt(value->data);
if (!file.has_value()) {
return Error("failed to find string for index %d", value->data);
}
return OpenXmlParser(file->c_str(), zip);
}
Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const ZipAssetsProvider* zip,
const std::string& name) {
Result<XmlParser> xml = OpenXmlParser("AndroidManifest.xml", zip);
if (!xml) {
return xml.GetError();
}
auto manifest_it = xml->tree_iterator();
if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
return Error("root element tag is not <manifest> in AndroidManifest.xml");
}
std::string package_name;
if (auto result_str = manifest_it->GetAttributeStringValue("package")) {
package_name = *result_str;
} else {
return result_str.GetError();
}
for (auto&& it : manifest_it) {
if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
continue;
}
OverlayManifestInfo info{};
info.package_name = package_name;
if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
if (*result_str != name) {
// A value for android:name was found, but either a the name does not match the requested
// name, or an <overlay> tag with no name was requested.
continue;
}
info.name = *result_str;
} else if (!name.empty()) {
// This tag does not have a value for android:name, but an <overlay> tag with a specific name
// has been requested.
continue;
}
if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
info.target_package = *result_str;
} else {
return Error("android:targetPackage missing from <overlay> in AndroidManifest.xml");
}
if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
info.target_name = *result_str;
}
if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
if (utils::IsReference((*result_value).dataType)) {
info.resource_mapping = (*result_value).data;
} else {
return Error("android:resourcesMap is not a reference in AndroidManifest.xml");
}
}
return info;
}
return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml", name.c_str());
}
Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider* zip,
const AssetManager2* overlay_am,
const LoadedArsc* overlay_arsc,
const LoadedPackage* overlay_package) {
auto parser = OpenXmlParser(id, zip, overlay_am);
if (!parser) {
return parser.GetError();
}
OverlayData overlay_data{};
const uint32_t string_pool_offset = overlay_arsc->GetStringPool()->size();
const uint8_t package_id = overlay_package->GetPackageId();
auto root_it = parser->tree_iterator();
if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
return Error("root element is not <overlay> tag");
}
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
return Error("failed to parse overlay xml document");
}
if (overlay_it->event() != XmlParser::Event::START_TAG) {
continue;
}
if (overlay_it->name() != "item") {
return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
}
Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
if (!target_resource) {
return Error(R"(<item> tag missing expected attribute "target")");
}
Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
if (!overlay_resource) {
return Error(R"(<item> tag missing expected attribute "value")");
}
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
if (utils::IsReference(overlay_resource->dataType)) {
// Only rewrite resources defined within the overlay package to their corresponding target
// resource ids at runtime.
bool rewrite_id = package_id == EXTRACT_PACKAGE(overlay_resource->data);
overlay_data.pairs.emplace_back(OverlayData::Value{
*target_resource, OverlayData::ResourceIdValue{overlay_resource->data, rewrite_id}});
} else {
overlay_data.pairs.emplace_back(
OverlayData::Value{*target_resource, TargetValue{.data_type = overlay_resource->dataType,
.data_value = overlay_resource->data}});
}
}
const auto& string_pool = parser->get_strings();
const uint32_t string_pool_data_length = string_pool.bytes();
overlay_data.string_pool_data = OverlayData::InlineStringPoolData{
.data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
.data_length = string_pool_data_length,
.string_pool_offset = string_pool_offset,
};
// Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
memcpy(overlay_data.string_pool_data->data.get(), string_pool.data().unsafe_ptr(),
string_pool_data_length);
return overlay_data;
}
OverlayData CreateResourceMappingLegacy(const AssetManager2* overlay_am,
const LoadedPackage* overlay_package) {
OverlayData overlay_data{};
for (const ResourceId overlay_resid : *overlay_package) {
if (auto name = utils::ResToTypeEntryName(*overlay_am, overlay_resid)) {
// Disable rewriting. Overlays did not support internal references before
// android:resourcesMap. Do not introduce new behavior.
overlay_data.pairs.emplace_back(OverlayData::Value{
*name, OverlayData::ResourceIdValue{overlay_resid, false /* rewrite_id */}});
}
}
return overlay_data;
}
struct ResState {
std::unique_ptr<ApkAssets> apk_assets;
const LoadedArsc* arsc;
const LoadedPackage* package;
std::unique_ptr<AssetManager2> am;
ZipAssetsProvider* zip_assets;
static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
ResState state;
state.zip_assets = zip.get();
if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
return Error("failed to load apk asset");
}
if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
return Error("failed to retrieve loaded arsc");
}
if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
return Error("failed to retrieve loaded package at index 0");
}
state.am = std::make_unique<AssetManager2>();
if (!state.am->SetApkAssets({state.apk_assets.get()})) {
return Error("failed to create asset manager");
}
return state;
}
};
} // namespace
struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
// inherited from TargetResourceContainer
Result<bool> DefinesOverlayable() const override;
Result<const android::OverlayableInfo*> GetOverlayableInfo(ResourceId id) const override;
Result<ResourceId> GetResourceId(const std::string& name) const override;
// inherited from OverlayResourceContainer
Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
// inherited from ResourceContainer
Result<uint32_t> GetCrc() const override;
Result<std::string> GetResourceName(ResourceId id) const override;
const std::string& GetPath() const override;
~ApkResourceContainer() override = default;
private:
ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets, std::string path);
Result<const ResState*> GetState() const;
ZipAssetsProvider* GetZipAssets() const;
mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
std::string path_;
};
ApkResourceContainer::ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets,
std::string path)
: state_(std::move(zip_assets)), path_(std::move(path)) {
}
Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
const std::string& path) {
auto zip_assets = ZipAssetsProvider::Create(path);
if (zip_assets == nullptr) {
return Error("failed to load zip assets");
}
return std::unique_ptr<ApkResourceContainer>(
new ApkResourceContainer(std::move(zip_assets), path));
}
Result<const ResState*> ApkResourceContainer::GetState() const {
if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
return state;
}
auto state =
ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
if (!state) {
return state.GetError();
}
state_ = std::move(*state);
return &std::get<ResState>(state_);
}
ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
return zip->get();
}
return std::get<ResState>(state_).zip_assets;
}
Result<bool> ApkResourceContainer::DefinesOverlayable() const {
auto state = GetState();
if (!state) {
return state.GetError();
}
return (*state)->package->DefinesOverlayable();
}
Result<const android::OverlayableInfo*> ApkResourceContainer::GetOverlayableInfo(
ResourceId id) const {
auto state = GetState();
if (!state) {
return state.GetError();
}
return (*state)->package->GetOverlayableInfo(id);
}
Result<OverlayManifestInfo> ApkResourceContainer::FindOverlayInfo(const std::string& name) const {
return ExtractOverlayManifestInfo(GetZipAssets(), name);
}
Result<OverlayData> ApkResourceContainer::GetOverlayData(const OverlayManifestInfo& info) const {
const auto state = GetState();
if (!state) {
return state.GetError();
}
if (info.resource_mapping != 0) {
return CreateResourceMapping(info.resource_mapping, GetZipAssets(), (*state)->am.get(),
(*state)->arsc, (*state)->package);
}
return CreateResourceMappingLegacy((*state)->am.get(), (*state)->package);
}
Result<uint32_t> ApkResourceContainer::GetCrc() const {
return CalculateCrc(GetZipAssets());
}
const std::string& ApkResourceContainer::GetPath() const {
return path_;
}
Result<ResourceId> ApkResourceContainer::GetResourceId(const std::string& name) const {
auto state = GetState();
if (!state) {
return state.GetError();
}
auto id = (*state)->am->GetResourceId(name, "", (*state)->package->GetPackageName());
if (!id.has_value()) {
return Error("failed to find resource '%s'", name.c_str());
}
// Retrieve the compile-time resource id of the target resource.
return REWRITE_PACKAGE(*id, (*state)->package->GetPackageId());
}
Result<std::string> ApkResourceContainer::GetResourceName(ResourceId id) const {
auto state = GetState();
if (!state) {
return state.GetError();
}
return utils::ResToTypeEntryName(*(*state)->am, id);
}
Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
std::string path) {
auto result = ApkResourceContainer::FromPath(path);
if (!result) {
return result.GetError();
}
return std::unique_ptr<TargetResourceContainer>(result->release());
}
Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::FromPath(
std::string path) {
// Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
if (android::IsFabricatedOverlay(path)) {
auto result = FabricatedOverlayContainer::FromPath(path);
if (!result) {
return result.GetError();
}
return std::unique_ptr<OverlayResourceContainer>(result->release());
}
// Fallback to loading the container as an APK.
auto result = ApkResourceContainer::FromPath(path);
if (!result) {
return result.GetError();
}
return std::unique_ptr<OverlayResourceContainer>(result->release());
}
} // namespace android::idmap2

View File

@@ -30,19 +30,12 @@
using android::base::StringPrintf;
using android::idmap2::utils::BitmaskToPolicies;
using android::idmap2::utils::IsReference;
using android::idmap2::utils::ResToTypeEntryName;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
namespace {
#define REWRITE_PACKAGE(resid, package_id) \
(((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
std::string ConcatPolicies(const std::vector<std::string>& policies) {
std::string message;
for (const std::string& policy : policies) {
@@ -55,11 +48,11 @@ std::string ConcatPolicies(const std::vector<std::string>& policies) {
return message;
}
Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
Result<Unit> CheckOverlayable(const TargetResourceContainer& target,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
const ResourceId& target_resource) {
static constexpr const PolicyBitmask sDefaultPolicies =
constexpr const PolicyBitmask kDefaultPolicies =
PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
PolicyFlags::CONFIG_SIGNATURE;
@@ -68,8 +61,13 @@ Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
// the overlay is preinstalled, signed with the same signature as the target or signed with the
// same signature as reference package defined in SystemConfig under 'overlay-config-signature'
// tag.
if (!target_package.DefinesOverlayable()) {
return (sDefaultPolicies & fulfilled_policies) != 0
const Result<bool> defines_overlayable = target.DefinesOverlayable();
if (!defines_overlayable) {
return Error(defines_overlayable.GetError(), "unable to retrieve overlayable info");
}
if (!*defines_overlayable) {
return (kDefaultPolicies & fulfilled_policies) != 0
? Result<Unit>({})
: Error(
"overlay must be preinstalled, signed with the same signature as the target,"
@@ -77,367 +75,112 @@ Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
" <overlay-config-signature>.");
}
const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
if (overlayable_info == nullptr) {
const auto overlayable_info = target.GetOverlayableInfo(target_resource);
if (!overlayable_info) {
return overlayable_info.GetError();
}
if (*overlayable_info == nullptr) {
// Do not allow non-overlayable resources to be overlaid.
return Error("target resource has no overlayable declaration");
}
if (overlay_info.target_name != overlayable_info->name) {
if (overlay_info.target_name != (*overlayable_info)->name) {
// If the overlay supplies a target overlayable name, the resource must belong to the
// overlayable defined with the specified name to be overlaid.
return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
overlay_info.target_name.c_str(), overlayable_info->name.c_str());
overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
}
// Enforce policy restrictions if the resource is declared as overlayable.
if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
if (((*overlayable_info)->policy_flags & fulfilled_policies) == 0) {
return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
ConcatPolicies(BitmaskToPolicies((*overlayable_info)->policy_flags)).c_str());
}
return Result<Unit>({});
}
// TODO(martenkongstad): scan for package name instead of assuming package at index 0
//
// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
// this assumption tends to work out. That said, the correct thing to do is to scan
// resources.arsc for a package with a given name as read from the package manifest instead of
// relying on a hard-coded index. This however requires storing the package name in the idmap
// header, which in turn requires incrementing the idmap version. Because the initial version of
// idmap2 is compatible with idmap, this will have to wait for now.
const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
if (packages.empty()) {
return nullptr;
std::string GetDebugResourceName(const ResourceContainer& container, ResourceId resid) {
auto name = container.GetResourceName(resid);
if (name) {
return *name;
}
int id = packages[0]->GetPackageId();
return loaded_arsc.GetPackageById(id);
return StringPrintf("0x%08x", resid);
}
Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
const AssetManager2& asset_manager) {
auto value = asset_manager.GetResource(resource_id);
if (!value.has_value()) {
return Error("failed to find resource for id 0x%08x", resource_id);
}
if (value->type != Res_value::TYPE_STRING) {
return Error("resource for is 0x%08x is not a file", resource_id);
}
auto string_pool = asset_manager.GetStringPoolForCookie(value->cookie);
auto file = string_pool->string8ObjectAt(value->data);
if (!file.has_value()) {
return Error("failed to find string for index %d", value->data);
}
// Load the overlay resource mappings from the file specified using android:resourcesMap.
auto asset = asset_manager.OpenNonAsset(file->c_str(), Asset::AccessMode::ACCESS_BUFFER);
if (asset == nullptr) {
return Error("file \"%s\" not found", file->c_str());
}
return asset;
}
} // namespace
Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
size_t string_pool_offset,
const XmlParser& overlay_parser,
LogInfo& log_info) {
ResourceMapping resource_mapping;
auto root_it = overlay_parser.tree_iterator();
if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
return Error("root element is not <overlay> tag");
Result<ResourceMapping> ResourceMapping::FromContainers(const TargetResourceContainer& target,
const OverlayResourceContainer& overlay,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable,
LogInfo& log_info) {
auto overlay_data = overlay.GetOverlayData(overlay_info);
if (!overlay_data) {
return overlay_data.GetError();
}
const uint8_t target_package_id = target_package->GetPackageId();
const uint8_t overlay_package_id = overlay_package->GetPackageId();
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
return Error("failed to parse overlay xml document");
}
if (overlay_it->event() != XmlParser::Event::START_TAG) {
ResourceMapping mapping;
for (const auto& overlay_pair : overlay_data->pairs) {
const auto target_resid = target.GetResourceId(overlay_pair.resource_name);
if (!target_resid) {
log_info.Warning(LogMessage() << target_resid.GetErrorMessage());
continue;
}
if (overlay_it->name() != "item") {
return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
if (enforce_overlayable) {
// Filter out resources the overlay is not allowed to override.
auto overlayable = CheckOverlayable(target, overlay_info, fulfilled_policies, *target_resid);
if (!overlayable) {
log_info.Warning(LogMessage() << "overlay '" << overlay.GetPath()
<< "' is not allowed to overlay resource '"
<< GetDebugResourceName(target, *target_resid)
<< "' in target: " << overlayable.GetErrorMessage());
continue;
}
}
Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
if (!target_resource) {
return Error(R"(<item> tag missing expected attribute "target")");
}
Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
if (!overlay_resource) {
return Error(R"(<item> tag missing expected attribute "value")");
}
auto target_id_result =
target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
if (!target_id_result.has_value()) {
log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
<< "\" in target resources");
continue;
}
// Retrieve the compile-time resource id of the target resource.
uint32_t target_id = REWRITE_PACKAGE(*target_id_result, target_package_id);
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
if (IsReference(overlay_resource->dataType)) {
// Only rewrite resources defined within the overlay package to their corresponding target
// resource ids at runtime.
bool rewrite_reference = overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data);
resource_mapping.AddMapping(target_id, overlay_resource->data, rewrite_reference);
} else {
resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data);
if (auto result = mapping.AddMapping(*target_resid, overlay_pair.value); !result) {
return Error(result.GetError(), "failed to add mapping for '%s'",
GetDebugResourceName(target, *target_resid).c_str());
}
}
return resource_mapping;
auto& string_pool_data = overlay_data->string_pool_data;
if (string_pool_data.has_value()) {
mapping.string_pool_offset_ = string_pool_data->string_pool_offset;
mapping.string_pool_data_ = std::move(string_pool_data->data);
mapping.string_pool_data_length_ = string_pool_data->data_length;
}
return std::move(mapping);
}
Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
const AssetManager2* target_am, const AssetManager2* overlay_am,
const LoadedPackage* target_package, const LoadedPackage* overlay_package, LogInfo& log_info) {
ResourceMapping resource_mapping;
const uint8_t target_package_id = target_package->GetPackageId();
const auto end = overlay_package->end();
for (auto iter = overlay_package->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
if (!name) {
continue;
Result<Unit> ResourceMapping::AddMapping(
ResourceId target_resource,
const std::variant<OverlayData::ResourceIdValue, TargetValue>& value) {
if (target_map_.find(target_resource) != target_map_.end()) {
return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
}
// TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
// runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) {
target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id));
if (overlay_resource->rewrite_id) {
// An overlay resource can override multiple target resources at once. Rewrite the overlay
// resource as the first target resource it overrides.
overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
}
// Find the resource with the same type and entry name within the target package.
const std::string full_name =
base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
auto target_resource_result = target_am->GetResourceId(full_name);
if (!target_resource_result.has_value()) {
log_info.Warning(LogMessage()
<< "failed to find resource \"" << full_name << "\" in target resources");
continue;
}
// Retrieve the compile-time resource id of the target resource.
ResourceId target_resource = REWRITE_PACKAGE(*target_resource_result, target_package_id);
resource_mapping.AddMapping(target_resource, overlay_resid,
false /* rewrite_overlay_reference */);
}
return resource_mapping;
}
void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
LogInfo& log_info) {
std::set<ResourceId> remove_ids;
for (const auto& target_map : target_map_) {
const ResourceId target_resid = target_map.first;
Result<Unit> success =
CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
if (success) {
continue;
}
// Attempting to overlay a resource that is not allowed to be overlaid is treated as a
// warning.
Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
if (!name) {
name = StringPrintf("0x%08x", target_resid);
}
log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
<< "\" is not allowed to overlay resource \"" << *name
<< "\" in target: " << success.GetErrorMessage());
remove_ids.insert(target_resid);
}
for (const ResourceId target_resid : remove_ids) {
RemoveMapping(target_resid);
}
}
Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable,
LogInfo& log_info) {
AssetManager2 target_asset_manager;
if (!target_asset_manager.SetApkAssets({&target_apk_assets})) {
return Error("failed to create target asset manager");
}
AssetManager2 overlay_asset_manager;
if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets})) {
return Error("failed to create overlay asset manager");
}
const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
if (target_arsc == nullptr) {
return Error("failed to load target resources.arsc");
}
const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
if (overlay_arsc == nullptr) {
return Error("failed to load overlay resources.arsc");
}
const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
if (target_pkg == nullptr) {
return Error("failed to load target package from resources.arsc");
}
const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
if (overlay_pkg == nullptr) {
return Error("failed to load overlay package from resources.arsc");
}
size_t string_pool_data_length = 0U;
size_t string_pool_offset = 0U;
std::unique_ptr<uint8_t[]> string_pool_data;
Result<ResourceMapping> resource_mapping = {{}};
if (overlay_info.resource_mapping != 0U) {
// Use the dynamic reference table to find the assigned resource id of the map xml.
const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
uint32_t resource_mapping_id = overlay_info.resource_mapping;
ref_table->lookupResourceId(&resource_mapping_id);
// Load the overlay resource mappings from the file specified using android:resourcesMap.
auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
if (!asset) {
return Error("failed opening xml for android:resourcesMap: %s",
asset.GetErrorMessage().c_str());
}
auto parser =
XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
if (!parser) {
return Error("failed opening ResXMLTree");
}
// Copy the xml string pool data before the parse goes out of scope.
auto& string_pool = (*parser)->get_strings();
string_pool_data_length = string_pool.bytes();
string_pool_data.reset(new uint8_t[string_pool_data_length]);
// Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
memcpy(string_pool_data.get(), string_pool.data().unsafe_ptr(), string_pool_data_length);
// Offset string indices by the size of the overlay resource table string pool.
string_pool_offset = overlay_arsc->GetStringPool()->size();
resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
string_pool_offset, *(*parser), log_info);
} else {
// If no file is specified using android:resourcesMap, it is assumed that the overlay only
// defines resources intended to override target resources of the same type and name.
resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
target_pkg, overlay_pkg, log_info);
auto overlay_value = std::get<TargetValue>(value);
target_map_.insert(std::make_pair(target_resource, overlay_value));
}
if (!resource_mapping) {
return resource_mapping.GetError();
}
if (enforce_overlayable) {
// Filter out resources the overlay is not allowed to override.
(*resource_mapping)
.FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
fulfilled_policies, log_info);
}
resource_mapping->target_package_id_ = target_pkg->GetPackageId();
resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
resource_mapping->string_pool_offset_ = string_pool_offset;
resource_mapping->string_pool_data_ = std::move(string_pool_data);
resource_mapping->string_pool_data_length_ = string_pool_data_length;
return std::move(*resource_mapping);
}
OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
// An overlay resource can override multiple target resources at once. Rewrite the overlay
// resource as the first target resource it overrides.
OverlayResourceMap map;
for (const auto& mappings : overlay_map_) {
map.insert(std::make_pair(mappings.first, mappings.second));
}
return map;
}
Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, ResourceId overlay_resource,
bool rewrite_overlay_reference) {
if (target_map_.find(target_resource) != target_map_.end()) {
return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
}
// TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
// runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
target_map_.insert(std::make_pair(target_resource, overlay_resource));
if (rewrite_overlay_reference) {
overlay_map_.insert(std::make_pair(overlay_resource, target_resource));
}
return Unit{};
}
Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
TargetValue::DataType data_type,
TargetValue::DataValue data_value) {
if (target_map_.find(target_resource) != target_map_.end()) {
return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
}
// TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
// runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
return Unit{};
}
void ResourceMapping::RemoveMapping(ResourceId target_resource) {
auto target_iter = target_map_.find(target_resource);
if (target_iter == target_map_.end()) {
return;
}
const auto value = target_iter->second;
target_map_.erase(target_iter);
const ResourceId* overlay_resource = std::get_if<ResourceId>(&value);
if (overlay_resource == nullptr) {
return;
}
auto overlay_iter = overlay_map_.equal_range(*overlay_resource);
for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
if (i->second == target_resource) {
overlay_map_.erase(i);
return;
}
}
}
} // namespace android::idmap2

View File

@@ -17,27 +17,16 @@
#include "idmap2/ResourceUtils.h"
#include <memory>
#include <string>
#include "androidfw/StringPiece.h"
#include "androidfw/Util.h"
#include "idmap2/Result.h"
#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
using android::StringPiece16;
using android::idmap2::Result;
using android::idmap2::XmlParser;
using android::idmap2::ZipFile;
using android::util::Utf16ToUtf8;
namespace android::idmap2::utils {
namespace {
constexpr ResourceId kAttrName = 0x01010003;
constexpr ResourceId kAttrResourcesMap = 0x01010609;
constexpr ResourceId kAttrTargetName = 0x0101044d;
constexpr ResourceId kAttrTargetPackage = 0x01010021;
} // namespace
bool IsReference(uint8_t data_type) {
return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
@@ -97,71 +86,4 @@ Result<std::string> ResToTypeEntryName(const AssetManager2& am, uint32_t resid)
return out;
}
Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
const std::string& name) {
std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
if (!zip) {
return Error("failed to open %s as a zip file", path.c_str());
}
std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
if (!entry) {
return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
}
Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
if (!xml) {
return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
}
auto manifest_it = (*xml)->tree_iterator();
if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
}
for (auto&& it : manifest_it) {
if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
continue;
}
OverlayManifestInfo info{};
if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
if (*result_str != name) {
// A value for android:name was found, but either a the name does not match the requested
// name, or an <overlay> tag with no name was requested.
continue;
}
info.name = *result_str;
} else if (!name.empty()) {
// This tag does not have a value for android:name, but an <overlay> tag with a specific name
// has been requested.
continue;
}
if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
info.target_package = *result_str;
} else {
return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
result_str.GetErrorMessage().c_str());
}
if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
info.target_name = *result_str;
}
if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
if (IsReference((*result_value).dataType)) {
info.resource_mapping = (*result_value).data;
} else {
return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
path.c_str());
}
}
return info;
}
return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s",
name.c_str(), path.c_str());
}
} // namespace android::idmap2::utils

View File

@@ -151,16 +151,18 @@ Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string&
return value ? GetStringValue(parser_, *value, name) : value.GetError();
}
Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
bool copy_data) {
auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
XmlParser::XmlParser(std::unique_ptr<ResXMLTree> tree) : tree_(std::move(tree)) {
}
Result<XmlParser> XmlParser::Create(const void* data, size_t size, bool copy_data) {
auto tree = std::make_unique<ResXMLTree>();
if (tree->setTo(data, size, copy_data) != NO_ERROR) {
return Error("Malformed xml block");
}
// Find the beginning of the first tag.
XmlParser::Event event;
while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
while ((event = tree->next()) != XmlParser::Event::BAD_DOCUMENT &&
event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
}
@@ -172,11 +174,7 @@ Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, siz
return Error("Bad xml document");
}
return parser;
}
XmlParser::~XmlParser() {
tree_.uninit();
return XmlParser{std::move(tree)};
}
} // namespace android::idmap2

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 "idmap2/ZipFile.h"
#include <memory>
#include <string>
#include "idmap2/Result.h"
namespace android::idmap2 {
std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
void* ptr = ::operator new(sizeof(MemoryChunk) + size);
std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
chunk->size = size;
return chunk;
}
std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
::ZipArchiveHandle handle;
int32_t status = ::OpenArchive(path.c_str(), &handle);
if (status != 0) {
::CloseArchive(handle);
return nullptr;
}
return std::unique_ptr<ZipFile>(new ZipFile(handle));
}
ZipFile::~ZipFile() {
::CloseArchive(handle_);
}
std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
::ZipEntry entry;
int32_t status = ::FindEntry(handle_, entryPath, &entry);
if (status != 0) {
return nullptr;
}
std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
if (status != 0) {
return nullptr;
}
return chunk;
}
Result<uint32_t> ZipFile::Crc(const std::string& entryPath) const {
::ZipEntry entry;
int32_t status = ::FindEntry(handle_, entryPath, &entry);
if (status != 0) {
return Error("failed to find zip entry %s", entryPath.c_str());
}
return entry.crc32;
}
} // namespace android::idmap2

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
package android.idmap2.pb;
option optimize_for = LITE_RUNTIME;
// All changes to the proto messages in this file MUST be backwards compatible. Backwards
// incompatible changes will cause previously fabricated overlays to be considered corrupt by the
// new proto message specification.
message FabricatedOverlay {
repeated ResourcePackage packages = 1;
string name = 2;
string package_name = 3;
string target_package_name = 4;
string target_overlayable = 5;
}
message ResourcePackage {
string name = 1;
repeated ResourceType types = 2;
}
message ResourceType {
string name = 1;
repeated ResourceEntry entries = 2;
}
message ResourceEntry {
string name = 1;
oneof value {
ResourceValue res_value = 2;
}
}
message ResourceValue {
// Corresponds with android::Res_value::dataType
uint32 data_type = 1;
// Corresponds with android::Res_value::data
uint32 data_value = 2;
}

View File

@@ -33,7 +33,7 @@ using ::testing::NotNull;
namespace android::idmap2 {
TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
auto result1 = Idmap::FromBinaryStream(raw_stream);

View File

@@ -0,0 +1,138 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/file.h>
#include <gtest/gtest.h>
#include <idmap2/FabricatedOverlay.h>
#include <fstream>
namespace android::idmap2 {
TEST(FabricatedOverlayTests, OverlayInfo) {
auto overlay =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetOverlayable("TestResources")
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
auto info = container->FindOverlayInfo("SandTheme");
ASSERT_TRUE(info);
EXPECT_EQ("SandTheme", (*info).name);
EXPECT_EQ("TestResources", (*info).target_name);
info = container->FindOverlayInfo("OceanTheme");
ASSERT_FALSE(info);
}
TEST(FabricatedOverlayTests, SetResourceValue) {
auto overlay =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
.SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
auto info = container->FindOverlayInfo("SandTheme");
ASSERT_TRUE(info);
ASSERT_TRUE((*info).target_name.empty());
auto crc = (*container).GetCrc();
ASSERT_TRUE(crc) << crc.GetErrorMessage();
EXPECT_NE(0U, *crc);
auto pairs = container->GetOverlayData(*info);
ASSERT_TRUE(pairs);
EXPECT_FALSE(pairs->string_pool_data.has_value());
ASSERT_EQ(3U, pairs->pairs.size());
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
auto entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
ASSERT_EQ(1U, entry->data_value);
ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
it = pairs->pairs[1];
ASSERT_EQ("com.example.target:string/int3", it.resource_name);
entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
ASSERT_EQ(0x7f010000, entry->data_value);
ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
it = pairs->pairs[2];
ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
ASSERT_EQ(2U, entry->data_value);
ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
}
TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
{
auto builder =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U);
ASSERT_FALSE(builder.Build());
}
{
auto builder =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U);
ASSERT_FALSE(builder.Build());
}
}
TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
auto overlay =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetOverlayable("TestResources")
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
.Build();
ASSERT_TRUE(overlay);
TemporaryFile tf;
std::ofstream out(tf.path);
ASSERT_TRUE((*overlay).ToBinaryStream(out));
out.close();
auto container = OverlayResourceContainer::FromPath(tf.path);
ASSERT_TRUE(container) << container.GetErrorMessage();
EXPECT_EQ(tf.path, (*container)->GetPath());
auto crc = (*container)->GetCrc();
ASSERT_TRUE(crc) << crc.GetErrorMessage();
EXPECT_NE(0U, *crc);
auto info = (*container)->FindOverlayInfo("SandTheme");
ASSERT_TRUE(info) << info.GetErrorMessage();
EXPECT_EQ("SandTheme", (*info).name);
EXPECT_EQ("TestResources", (*info).target_name);
auto pairs = (*container)->GetOverlayData(*info);
ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
EXPECT_EQ(1U, pairs->pairs.size());
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
auto entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
EXPECT_EQ(1U, entry->data_value);
EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
}
} // namespace android::idmap2

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
#include <android-base/file.h>
#include <cstdio> // fclose
#include <fstream>
#include <memory>
@@ -61,12 +63,12 @@ TEST(IdmapTests, TestCanonicalIdmapPathFor) {
}
TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
ASSERT_THAT(header, NotNull());
ASSERT_EQ(header->GetMagic(), 0x504d4449U);
ASSERT_EQ(header->GetVersion(), 0x07U);
ASSERT_EQ(header->GetVersion(), 0x08U);
ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -81,7 +83,7 @@ TEST(IdmapTests, IdmapFailParsingDifferentVersion) {
std::stringstream stream;
stream << android::kIdmapMagic;
stream << 0xffffffffU;
stream << std::string(kJunkSize, (char) 0xffU);
stream << std::string(kJunkSize, (char)0xffU);
ASSERT_FALSE(Idmap::FromBinaryStream(stream));
}
@@ -90,14 +92,13 @@ TEST(IdmapTests, IdmapFailParsingDifferentMagic) {
std::stringstream stream;
stream << 0xffffffffU;
stream << android::kIdmapCurrentVersion;
stream << std::string(kJunkSize, (char) 0xffU);
stream << std::string(kJunkSize, (char)0xffU);
ASSERT_FALSE(Idmap::FromBinaryStream(stream));
}
TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
const size_t offset = kIdmapRawDataOffset;
std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
kIdmapRawDataLen - offset);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
std::istringstream stream(raw);
std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
@@ -108,8 +109,7 @@ TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
const size_t offset = kIdmapRawDataOffset;
std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
kIdmapRawDataLen - offset);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
std::istringstream stream(raw);
std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
@@ -134,7 +134,7 @@ TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
}
TEST(IdmapTests, CreateIdmapFromBinaryStream) {
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
auto result = Idmap::FromBinaryStream(stream);
@@ -143,7 +143,7 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) {
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -177,7 +177,7 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) {
}
TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
std::string raw(reinterpret_cast<const char*>(kIdmapRawData),
10); // data too small
std::istringstream stream(raw);
@@ -189,14 +189,14 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
auto idmap_result = Idmap::FromApkAssets(
*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
auto idmap_result = Idmap::FromContainers(
**target, **overlay, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
@@ -204,7 +204,7 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) {
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
@@ -218,15 +218,15 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
auto idmap_result = Idmap::FromContainers(
**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
ASSERT_THAT(idmap, NotNull());
@@ -255,25 +255,66 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
}
TEST(IdmapTests, FabricatedOverlay) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
.Build();
ASSERT_TRUE(frro);
TemporaryFile tf;
std::ofstream out(tf.path);
ASSERT_TRUE((*frro).ToBinaryStream(out));
out.close();
auto overlay = OverlayResourceContainer::FromPath(tf.path);
ASSERT_TRUE(overlay);
auto idmap_result = Idmap::FromContainers(**target, **overlay, "SandTheme", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
ASSERT_THAT(idmap, NotNull());
const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
ASSERT_EQ(dataBlocks.size(), 1U);
const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
ASSERT_THAT(data, NotNull());
ASSERT_EQ(data->GetTargetEntries().size(), 0U);
ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
const auto& target_inline_entries = data->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries.size(), 2U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
Res_value::TYPE_INT_DEC, 2U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
Res_value::TYPE_REFERENCE, 0x7f010000);
}
TEST(IdmapTests, FailCreateIdmapInvalidName) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
{
auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
auto idmap_result = Idmap::FromContainers(**target, **overlay, "", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_FALSE(idmap_result);
}
{
auto idmap_result =
Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
auto idmap_result = Idmap::FromContainers(**target, **overlay, "unknown", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_FALSE(idmap_result);
}
}
@@ -282,15 +323,15 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
auto idmap_result = Idmap::FromContainers(
**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
ASSERT_THAT(idmap, NotNull());
@@ -328,30 +369,29 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) {
}
Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
const std::string& local_target_apk_path, const std::string& local_overlay_apk_path,
const std::string& local_target_path, const std::string& local_overlay_path,
const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
auto overlay_info =
utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
const std::string target_path(GetTestDataPath() + local_target_path);
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return Error(R"(Failed to load target "%s")", target_path.c_str());
}
const std::string overlay_path(GetTestDataPath() + local_overlay_path);
auto overlay = OverlayResourceContainer::FromPath(overlay_path);
if (!overlay) {
return Error(R"(Failed to load overlay "%s")", overlay_path.c_str());
}
auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
if (!overlay_info) {
return overlay_info.GetError();
}
const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
}
const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
return Error(R"(Failed to find overlay name "%s")", overlay_name.c_str());
}
LogInfo log_info;
auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
fulfilled_policies, enforce_overlayable, log_info);
auto mapping = ResourceMapping::FromContainers(**target, **overlay, *overlay_info,
fulfilled_policies, enforce_overlayable, log_info);
if (!mapping) {
return mapping.GetError();
}
@@ -360,11 +400,9 @@ Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
}
TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
auto idmap_data =
TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages",
PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk",
"DifferentPackages", PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
auto& data = *idmap_data;
@@ -417,7 +455,7 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) {
const uint32_t target_crc = kIdmapRawDataTargetCrc;
const uint32_t overlay_crc = kIdmapRawOverlayCrc;
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
auto result = Idmap::FromBinaryStream(raw_stream);
@@ -468,8 +506,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) {
ASSERT_THAT(bad_target_crc_header, NotNull());
ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
ASSERT_FALSE(bad_target_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
// overlay crc: bytes (0xc, 0xf)
std::string bad_overlay_crc_string(stream.str());
@@ -483,8 +521,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) {
ASSERT_THAT(bad_overlay_crc_header, NotNull());
ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
// fulfilled policy: bytes (0x10, 0x13)
std::string bad_policy_string(stream.str());
@@ -522,8 +560,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) {
ASSERT_THAT(bad_target_path_header, NotNull());
ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
ASSERT_FALSE(bad_target_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
target_crc, overlay_crc, policies,
/* enforce_overlayable */ true));
// overlay path: bytes (0x2c, 0x37)
std::string bad_overlay_path_string(stream.str());
@@ -576,7 +614,7 @@ class TestVisitor : public Visitor {
};
TEST(IdmapTests, TestVisitor) {
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
const auto idmap = Idmap::FromBinaryStream(stream);

View File

@@ -27,35 +27,31 @@
#include "idmap2/Idmap.h"
#include "idmap2/PrettyPrintVisitor.h"
using android::ApkAssets;
using android::base::StringPrintf;
using ::testing::NotNull;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk,
TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
PrettyPrintVisitor visitor(stream);
(*idmap)->accept(&visitor);
ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
ASSERT_NE(stream.str().find("target path : "), std::string::npos);
ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n",
R::target::integer::int1, R::overlay::integer::int1)),
std::string::npos);
@@ -64,7 +60,7 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
fclose(stderr); // silence expected warnings from libandroidfw
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -74,8 +70,8 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
PrettyPrintVisitor visitor(stream);
(*idmap)->accept(&visitor);
ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
ASSERT_NE(stream.str().find("target path : "), std::string::npos);
ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000 (\?\?\? -> \?\?\?)\n"), std::string::npos);
}

View File

@@ -30,17 +30,16 @@
#include "idmap2/RawPrintVisitor.h"
using android::base::StringPrintf;
using ::testing::NotNull;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
#define ASSERT_CONTAINS_REGEX(pattern, str) \
do { \
ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \
<< "pattern '" << pattern << "' not found in\n--------\n" \
<< str << "--------"; \
#define ASSERT_CONTAINS_REGEX(pattern, str) \
do { \
ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \
<< "pattern '" << (pattern) << "' not found in\n--------\n" \
<< (str) << "--------"; \
} while (0)
#define ADDRESS "[0-9a-f]{8}: "
@@ -49,16 +48,15 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
fclose(stderr); // silence expected warnings
const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
ASSERT_TRUE(overlay);
const auto idmap =
Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT,
PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
@@ -66,7 +64,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str());
ASSERT_CONTAINS_REGEX(
StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
stream.str());
@@ -75,8 +73,6 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 fulfilled policies: public\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str());
@@ -104,7 +100,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
fclose(stderr); // silence expected warnings from libandroidfw
std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -115,7 +111,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str());
@@ -126,8 +122,6 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay path: overlayX.apk\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "0000000b overlay name size\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay name: OverlayName\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str());
@@ -140,7 +134,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 string pool size\n", stream.str());
ASSERT_CONTAINS_REGEX("000000a8: ........ string pool\n", stream.str());
ASSERT_CONTAINS_REGEX("000000a4: ........ string pool\n", stream.str());
}
} // namespace android::idmap2

View File

@@ -14,6 +14,10 @@
* limitations under the License.
*/
#include <android-base/file.h>
#include <androidfw/ResourceTypes.h>
#include <gtest/gtest.h>
#include <cstdio> // fclose
#include <fstream>
#include <memory>
@@ -22,14 +26,10 @@
#include "R.h"
#include "TestConstants.h"
#include "TestHelpers.h"
#include "androidfw/ResourceTypes.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "idmap2/LogInfo.h"
#include "idmap2/ResourceMapping.h"
using android::Res_value;
using android::idmap2::utils::ExtractOverlayManifestInfo;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
@@ -41,32 +41,36 @@ namespace android::idmap2 {
ASSERT_TRUE(result) << result.GetErrorMessage(); \
} while (0)
Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path,
const std::string& local_overlay_apk_path,
Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_path,
const std::string& local_overlay_path,
const std::string& overlay_name,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
auto overlay_info =
ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
const std::string target_path = (local_target_path[0] == '/')
? local_target_path
: (GetTestDataPath() + "/" + local_target_path);
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return Error(target.GetError(), R"(Failed to load target "%s")", target_path.c_str());
}
const std::string overlay_path = (local_overlay_path[0] == '/')
? local_overlay_path
: (GetTestDataPath() + "/" + local_overlay_path);
auto overlay = OverlayResourceContainer::FromPath(overlay_path);
if (!overlay) {
return Error(overlay.GetError(), R"(Failed to load overlay "%s")", overlay_path.c_str());
}
auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
if (!overlay_info) {
return overlay_info.GetError();
}
const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
}
const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
return Error(overlay_info.GetError(), R"(Failed to find overlay name "%s")",
overlay_name.c_str());
}
LogInfo log_info;
return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
fulfilled_policies, enforce_overlayable, log_info);
return ResourceMapping::FromContainers(**target, **overlay, *overlay_info, fulfilled_policies,
enforce_overlayable, log_info);
}
Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource,
@@ -128,7 +132,7 @@ Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& tar
}
TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
ASSERT_TRUE(resources) << resources.GetErrorMessage();
@@ -145,7 +149,7 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
}
TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "SwapNames",
PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -161,7 +165,7 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
}
TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
"DifferentPackages", PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -176,7 +180,7 @@ TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
}
TEST(ResourceMappingTests, InlineResources) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "Inline",
PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
constexpr size_t overlay_string_pool_size = 10U;
@@ -189,8 +193,32 @@ TEST(ResourceMappingTests, InlineResources) {
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U));
}
TEST(ResourceMappingTests, FabricatedOverlay) {
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
.Build();
ASSERT_TRUE(frro);
TemporaryFile tf;
std::ofstream out(tf.path);
ASSERT_TRUE((*frro).ToBinaryStream(out));
out.close();
auto resources = TestGetResourceMapping("target/target.apk", tf.path, "SandTheme",
PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
ASSERT_TRUE(resources) << resources.GetErrorMessage();
auto& res = *resources;
ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
}
TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
@@ -209,7 +237,7 @@ TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
// Resources that are not declared as overlayable and resources that a protected by policies the
// overlay does not fulfill must not map to overlay resources.
TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
@@ -229,7 +257,7 @@ TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
// overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
// off.
TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -264,7 +292,7 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnore
// Overlays that do not target an <overlayable> tag can overlay any resource if overlayable
// enforcement is disabled.
TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -284,10 +312,9 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTarget
// Overlays that are neither pre-installed nor signed with the same signature as the target cannot
// overlay packages that have not defined overlayable resources.
TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
auto resources =
TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
"NoTargetName", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
auto resources = TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
"NoTargetName", PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(resources) << resources.GetErrorMessage();
ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
@@ -297,9 +324,9 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
// signed with the same signature as the reference package can overlay packages that have not
// defined overlayable resources.
TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) {
auto resources =
TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies,
/* enforce_overlayable */ true);

View File

@@ -17,10 +17,12 @@
#include <memory>
#include <string>
#include "R.h"
#include "TestHelpers.h"
#include "androidfw/ApkAssets.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "idmap2/ResourceContainer.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
@@ -49,8 +51,8 @@ class ResourceUtilsTests : public Idmap2Tests {
};
TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000U);
ASSERT_TRUE(name);
Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), R::target::integer::int1);
ASSERT_TRUE(name) << name.GetErrorMessage();
ASSERT_EQ(*name, "integer/int1");
}
@@ -60,25 +62,34 @@ TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) {
}
TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) {
auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
"InvalidName");
auto overlay =
OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
ASSERT_TRUE(overlay);
auto info = (*overlay)->FindOverlayInfo("InvalidName");
ASSERT_FALSE(info);
}
TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) {
auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
"ValidName");
auto overlay =
OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
ASSERT_TRUE(overlay);
auto info = (*overlay)->FindOverlayInfo("ValidName");
ASSERT_FALSE(info);
}
TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) {
auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
"ValidNameAndTargetPackage");
auto overlay =
OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
ASSERT_TRUE(overlay);
auto info = (*overlay)->FindOverlayInfo("ValidNameAndTargetPackage");
ASSERT_TRUE(info);
ASSERT_EQ("ValidNameAndTargetPackage", info->name);
ASSERT_EQ("Valid", info->target_package);
ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
}
}// namespace android::idmap2
} // namespace android::idmap2

View File

@@ -24,13 +24,13 @@
namespace android::idmap2 {
const unsigned char idmap_raw_data[] = {
const unsigned char kIdmapRawData[] = {
// IDMAP HEADER
// 0x0: magic
0x49, 0x44, 0x4d, 0x50,
// 0x4: version
0x07, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00,
// 0x8: target crc
0x34, 0x12, 0x00, 0x00,
@@ -70,81 +70,72 @@ const unsigned char idmap_raw_data[] = {
0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
// DATA HEADER
// 0x54: target_package_id
0x7f,
// 0x55: overlay_package_id
0x7f,
// 0x56: padding
0x00, 0x00,
// 0x58: target_entry_count
// 0x54: target_entry_count
0x03, 0x00, 0x00, 0x00,
// 0x5c: target_inline_entry_count
// 0x58: target_inline_entry_count
0x01, 0x00, 0x00, 0x00,
// 0x60: overlay_entry_count
// 0x5c: overlay_entry_count
0x03, 0x00, 0x00, 0x00,
// 0x64: string_pool_offset
// 0x60: string_pool_offset
0x00, 0x00, 0x00, 0x00,
// TARGET ENTRIES
// 0x68: target id (0x7f020000)
// 0x64: target id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
// 0x6c: overlay_id (0x7f020000)
// 0x68: overlay_id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
// 0x70: target id (0x7f030000)
// 0x6c: target id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
// 0x74: overlay_id (0x7f030000)
// 0x70: overlay_id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
// 0x78: target id (0x7f030002)
// 0x74: target id (0x7f030002)
0x02, 0x00, 0x03, 0x7f,
// 0x7c: overlay_id (0x7f030001)
// 0x78: overlay_id (0x7f030001)
0x01, 0x00, 0x03, 0x7f,
// INLINE TARGET ENTRIES
// 0x80: target_id
// 0x7c: target_id
0x00, 0x00, 0x04, 0x7f,
// 0x84: Res_value::size (value ignored by idmap)
// 0x80: Res_value::size (value ignored by idmap)
0x08, 0x00,
// 0x87: Res_value::res0 (value ignored by idmap)
// 0x82: Res_value::res0 (value ignored by idmap)
0x00,
// 0x88: Res_value::dataType (TYPE_INT_HEX)
// 0x83: Res_value::dataType (TYPE_INT_HEX)
0x11,
// 0x8c: Res_value::data
// 0x84: Res_value::data
0x78, 0x56, 0x34, 0x12,
// OVERLAY ENTRIES
// 0x90: 0x7f020000 -> 0x7f020000
// 0x88: 0x7f020000 -> 0x7f020000
0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
// 0x98: 0x7f030000 -> 0x7f030000
// 0x90: 0x7f030000 -> 0x7f030000
0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
// 0xa0: 0x7f030001 -> 0x7f030002
// 0x98: 0x7f030001 -> 0x7f030002
0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
// 0xa4: string pool
// 0xa0: string pool
// string length,
0x04, 0x00, 0x00, 0x00,
// 0xa8 string contents "test"
// 0xa4 string contents "test"
0x74, 0x65, 0x73, 0x74};
const unsigned int kIdmapRawDataLen = 0xac;
const unsigned int kIdmapRawDataLen = 0xa8;
const unsigned int kIdmapRawDataOffset = 0x54;
const unsigned int kIdmapRawDataTargetCrc = 0x1234;
const unsigned int kIdmapRawOverlayCrc = 0x5678;

View File

@@ -19,25 +19,25 @@
#include <string>
#include "TestHelpers.h"
#include "gmock/gmock.h"
#include "androidfw/AssetsProvider.h"
#include "gtest/gtest.h"
#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
namespace android::idmap2 {
Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
Result<XmlParser> CreateTestParser(const std::string& test_file) {
auto zip = ZipAssetsProvider::Create(GetTestDataPath() + "/target/target.apk");
if (zip == nullptr) {
return Error("Failed to open zip file");
}
auto data = zip->Uncompress(test_file);
auto data = zip->Open(test_file);
if (data == nullptr) {
return Error("Failed to open xml file");
}
return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
return XmlParser::Create(data->getBuffer(true /* aligned*/), data->getLength(),
/* copy_data */ true);
}
TEST(XmlParserTests, Create) {
@@ -54,7 +54,7 @@ TEST(XmlParserTests, NextChild) {
auto xml = CreateTestParser("res/xml/test.xml");
ASSERT_TRUE(xml) << xml.GetErrorMessage();
auto root_iter = (*xml)->tree_iterator();
auto root_iter = xml->tree_iterator();
ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
ASSERT_EQ(root_iter->name(), "a");
@@ -85,7 +85,7 @@ TEST(XmlParserTests, AttributeValues) {
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
auto root_iter = (*xml)->tree_iterator();
auto root_iter = xml->tree_iterator();
// Start at the <b> tag.
auto a_iter = root_iter.begin();
@@ -111,8 +111,8 @@ TEST(XmlParserTests, IteratorEquality) {
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
auto root_iter_1 = (*xml)->tree_iterator();
auto root_iter_2 = (*xml)->tree_iterator();
auto root_iter_1 = xml->tree_iterator();
auto root_iter_2 = xml->tree_iterator();
ASSERT_EQ(root_iter_1, root_iter_2);
ASSERT_EQ(*root_iter_1, *root_iter_2);
@@ -146,7 +146,7 @@ TEST(XmlParserTests, Backtracking) {
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
auto root_iter_1 = (*xml)->tree_iterator();
auto root_iter_1 = xml->tree_iterator();
// Start at the <b> tag.
auto a_iter_1 = root_iter_1.begin();

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 <cstdio> // fclose
#include <string>
#include "TestHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "idmap2/Result.h"
#include "idmap2/ZipFile.h"
using ::testing::IsNull;
using ::testing::NotNull;
namespace android::idmap2 {
TEST(ZipFileTests, BasicOpen) {
auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
ASSERT_THAT(zip, NotNull());
fclose(stderr); // silence expected warnings from libziparchive
auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
ASSERT_THAT(fail, IsNull());
}
TEST(ZipFileTests, Crc) {
auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
ASSERT_THAT(zip, NotNull());
Result<uint32_t> crc = zip->Crc("AndroidManifest.xml");
ASSERT_TRUE(crc);
ASSERT_EQ(*crc, 0x762f3d24);
Result<uint32_t> crc2 = zip->Crc("does-not-exist");
ASSERT_FALSE(crc2);
}
TEST(ZipFileTests, Uncompress) {
auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
ASSERT_THAT(zip, NotNull());
auto data = zip->Uncompress("assets/lorem-ipsum.txt");
ASSERT_THAT(data, NotNull());
const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
ASSERT_THAT(data->size, lorem_ipsum.size());
ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
auto fail = zip->Uncompress("does-not-exist");
ASSERT_THAT(fail, IsNull());
}
} // namespace android::idmap2

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.om;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
/**
* A subset of {@link OverlayInfo} fields that when changed cause the overlay's settings to be
* completely reinitialized.
*
* @hide
*/
public interface CriticalOverlayInfo {
/**
* @return the package name of the overlay.
*/
@NonNull
String getPackageName();
/**
* @return the unique name of the overlay within its containing package.
*/
@Nullable
String getOverlayName();
/**
* @return the target package name of the overlay.
*/
@NonNull
String getTargetPackageName();
/**
* @return the name of the target overlayable declaration.
*/
@Nullable
String getTargetOverlayableName();
/**
* @return an identifier representing the current overlay.
*/
@NonNull
OverlayIdentifier getOverlayIdentifier();
/**
* Returns whether or not the overlay is a {@link FabricatedOverlay}.
*/
boolean isFabricated();
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.om;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
/**
* Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
*
* Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
* overlayable policies a fabricated overlay fulfills are the same policies the creator of the
* overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
* system partition would fulfil the {@code system} and {@code signature} policies.
*
* The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
* manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
*
* Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
* overlays and fabricated overlays.
* @hide
*/
public class FabricatedOverlay {
/** Retrieves the identifier for this fabricated overlay. */
public OverlayIdentifier getIdentifier() {
return new OverlayIdentifier(
mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
}
public static class Builder {
private final String mOwningPackage;
private final String mName;
private final String mTargetPackage;
private String mTargetOverlayable = "";
private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
/**
* Constructs a build for a fabricated overlay.
*
* @param owningPackage the name of the package that owns the fabricated overlay (must
* be a package name of this UID).
* @param name a name used to uniquely identify the fabricated overlay owned by
* {@param owningPackageName}
* @param targetPackage the name of the package to overlay
*/
public Builder(@NonNull String owningPackage, @NonNull String name,
@NonNull String targetPackage) {
Preconditions.checkStringNotEmpty(owningPackage,
"'owningPackage' must not be empty nor null");
Preconditions.checkStringNotEmpty(name,
"'name'' must not be empty nor null");
Preconditions.checkStringNotEmpty(targetPackage,
"'targetPackage' must not be empty nor null");
mOwningPackage = owningPackage;
mName = name;
mTargetPackage = targetPackage;
}
/**
* Sets the name of the overlayable resources to overlay (can be null).
*/
public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
return this;
}
/**
* Sets the value of
*
* @param resourceName name of the target resource to overlay (in the form
* [package]:type/entry)
* @param dataType the data type of the new value
* @param value the unsigned 32 bit integer representing the new value
*
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
entry.data = value;
mEntries.add(entry);
return this;
}
/** Builds an immutable fabricated overlay. */
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
overlay.packageName = mOwningPackage;
overlay.overlayName = mName;
overlay.targetPackageName = mTargetPackage;
overlay.targetOverlayable = mTargetOverlayable;
overlay.entries = new ArrayList<>();
overlay.entries.addAll(mEntries);
return new FabricatedOverlay(overlay);
}
}
final FabricatedOverlayInternal mOverlay;
private FabricatedOverlay(FabricatedOverlayInternal overlay) {
mOverlay = overlay;
}
}

View File

@@ -16,6 +16,7 @@
package android.content.om;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
@@ -65,6 +66,17 @@ interface IOverlayManager {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
OverlayInfo getOverlayInfo(in String packageName, in int userId);
/**
* Returns information about the overlay with the given package name for the
* specified user.
*
* @param packageName The name of the overlay package.
* @param userId The user to get the OverlayInfo for.
* @return The OverlayInfo for the overlay package; or null if no such
* overlay package exists.
*/
OverlayInfo getOverlayInfoByIdentifier(in OverlayIdentifier packageName, in int userId);
/**
* Request that an overlay package be enabled or disabled when possible to
* do so.
@@ -163,7 +175,7 @@ interface IOverlayManager {
* Invalidates and removes the idmap for an overlay,
* @param packageName The name of the overlay package whose idmap should be deleted.
*/
void invalidateCachesForOverlay(in String packageName, in int userIs);
void invalidateCachesForOverlay(in String packageName, in int userId);
/**
* Perform a series of requests related to overlay packages. This is an

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.om;
parcelable OverlayIdentifier;

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.om;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
import java.util.Objects;
/**
* A key used to uniquely identify a Runtime Resource Overlay (RRO).
*
* An overlay always belongs to a package and may optionally have a name associated with it.
* The name helps uniquely identify a particular overlay within a package.
* @hide
*/
/** @hide */
@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
genEqualsHashCode = true, genToString = false)
public class OverlayIdentifier implements Parcelable {
/**
* The package name containing or owning the overlay.
*/
@Nullable
private final String mPackageName;
/**
* The unique name within the package of the overlay.
*/
@Nullable
private final String mOverlayName;
/**
* Creates an identifier from a package and unique name within the package.
*
* @param packageName the package containing or owning the overlay
* @param overlayName the unique name of the overlay within the package
*/
public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
mPackageName = packageName;
mOverlayName = overlayName;
}
/**
* Creates an identifier for an overlay without a name.
*
* @param packageName the package containing or owning the overlay
*/
public OverlayIdentifier(@NonNull String packageName) {
mPackageName = packageName;
mOverlayName = null;
}
@Override
public String toString() {
return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
}
/** @hide */
public static OverlayIdentifier fromString(@NonNull String text) {
final String[] parts = text.split(":", 2);
if (parts.length == 2) {
return new OverlayIdentifier(parts[0], parts[1]);
} else {
return new OverlayIdentifier(parts[0]);
}
}
// Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayIdentifier.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* Retrieves the package name containing or owning the overlay.
*/
@DataClass.Generated.Member
public @Nullable String getPackageName() {
return mPackageName;
}
/**
* Retrieves the unique name within the package of the overlay.
*/
@DataClass.Generated.Member
public @Nullable String getOverlayName() {
return mOverlayName;
}
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(OverlayIdentifier other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
OverlayIdentifier that = (OverlayIdentifier) o;
//noinspection PointlessBooleanExpression
return true
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mOverlayName, that.mOverlayName);
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + Objects.hashCode(mOverlayName);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mPackageName != null) flg |= 0x1;
if (mOverlayName != null) flg |= 0x2;
dest.writeByte(flg);
if (mPackageName != null) dest.writeString(mPackageName);
if (mOverlayName != null) dest.writeString(mOverlayName);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected OverlayIdentifier(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
String packageName = (flg & 0x1) == 0 ? null : in.readString();
String overlayName = (flg & 0x2) == 0 ? null : in.readString();
this.mPackageName = packageName;
this.mOverlayName = overlayName;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<OverlayIdentifier> CREATOR
= new Parcelable.Creator<OverlayIdentifier>() {
@Override
public OverlayIdentifier[] newArray(int size) {
return new OverlayIdentifier[size];
}
@Override
public OverlayIdentifier createFromParcel(@NonNull Parcel in) {
return new OverlayIdentifier(in);
}
};
@DataClass.Generated(
time = 1612482438728L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -26,6 +26,8 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -37,7 +39,7 @@ import java.util.Objects;
* @hide
*/
@SystemApi
public final class OverlayInfo implements Parcelable {
public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
/** @hide */
@IntDef(prefix = "STATE_", value = {
@@ -142,6 +144,14 @@ public final class OverlayInfo implements Parcelable {
@NonNull
public final String packageName;
/**
* The unique name within the package of the overlay.
*
* @hide
*/
@Nullable
public final String overlayName;
/**
* Package name of the target package
*
@@ -201,6 +211,14 @@ public final class OverlayInfo implements Parcelable {
*/
public final boolean isMutable;
private OverlayIdentifier mIdentifierCached;
/**
*
* @hide
*/
public final boolean isFabricated;
/**
* Create a new OverlayInfo based on source with an updated state.
*
@@ -210,17 +228,28 @@ public final class OverlayInfo implements Parcelable {
* @hide
*/
public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
this(source.packageName, source.targetPackageName, source.targetOverlayableName,
source.category, source.baseCodePath, state, source.userId, source.priority,
source.isMutable);
this(source.packageName, source.overlayName, source.targetPackageName,
source.targetOverlayableName, source.category, source.baseCodePath, state,
source.userId, source.priority, source.isMutable, source.isFabricated);
}
/** @hide */
@VisibleForTesting
public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
@Nullable String targetOverlayableName, @Nullable String category,
@NonNull String baseCodePath, int state, int userId,
int priority, boolean isMutable) {
@NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
category, baseCodePath, state, userId, priority, isMutable,
false /* isFabricated */);
}
/** @hide */
public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
@NonNull String targetPackageName, @Nullable String targetOverlayableName,
@Nullable String category, @NonNull String baseCodePath, int state, int userId,
int priority, boolean isMutable, boolean isFabricated) {
this.packageName = packageName;
this.overlayName = overlayName;
this.targetPackageName = targetPackageName;
this.targetOverlayableName = targetOverlayableName;
this.category = category;
@@ -229,12 +258,14 @@ public final class OverlayInfo implements Parcelable {
this.userId = userId;
this.priority = priority;
this.isMutable = isMutable;
this.isFabricated = isFabricated;
ensureValidState();
}
/** @hide */
public OverlayInfo(Parcel source) {
packageName = source.readString();
overlayName = source.readString();
targetPackageName = source.readString();
targetOverlayableName = source.readString();
category = source.readString();
@@ -243,13 +274,15 @@ public final class OverlayInfo implements Parcelable {
userId = source.readInt();
priority = source.readInt();
isMutable = source.readBoolean();
isFabricated = source.readBoolean();
ensureValidState();
}
/**
* Returns package name of the current overlay.
* {@inheritDoc}
* @hide
*/
@Override
@SystemApi
@NonNull
public String getPackageName() {
@@ -257,9 +290,20 @@ public final class OverlayInfo implements Parcelable {
}
/**
* Returns the target package name of the current overlay.
* {@inheritDoc}
* @hide
*/
@Override
@Nullable
public String getOverlayName() {
return overlayName;
}
/**
* {@inheritDoc}
* @hide
*/
@Override
@SystemApi
@NonNull
public String getTargetPackageName() {
@@ -268,7 +312,8 @@ public final class OverlayInfo implements Parcelable {
/**
* Returns the category of the current overlay.
* @hide\
*
* @hide
*/
@SystemApi
@Nullable
@@ -278,6 +323,7 @@ public final class OverlayInfo implements Parcelable {
/**
* Returns user handle for which this overlay applies to.
*
* @hide
*/
@SystemApi
@@ -287,15 +333,47 @@ public final class OverlayInfo implements Parcelable {
}
/**
* Returns name of the target overlayable declaration.
* {@inheritDoc}
* @hide
*/
@Override
@SystemApi
@Nullable
public String getTargetOverlayableName() {
return targetOverlayableName;
}
/**
* {@inheritDoc}
* @hide
*/
@Override
public boolean isFabricated() {
return isFabricated;
}
/**
* Full path to the base APK or fabricated overlay for this overlay package.
*
* @hide
*/
public String getBaseCodePath() {
return baseCodePath;
}
/**
* {@inheritDoc}
* @hide
*/
@Override
@NonNull
public OverlayIdentifier getOverlayIdentifier() {
if (mIdentifierCached == null) {
mIdentifierCached = new OverlayIdentifier(packageName, overlayName);
}
return mIdentifierCached;
}
@SuppressWarnings("ConstantConditions")
private void ensureValidState() {
if (packageName == null) {
@@ -330,6 +408,7 @@ public final class OverlayInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
dest.writeString(overlayName);
dest.writeString(targetPackageName);
dest.writeString(targetOverlayableName);
dest.writeString(category);
@@ -338,6 +417,7 @@ public final class OverlayInfo implements Parcelable {
dest.writeInt(userId);
dest.writeInt(priority);
dest.writeBoolean(isMutable);
dest.writeBoolean(isFabricated);
}
public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
@@ -410,6 +490,7 @@ public final class OverlayInfo implements Parcelable {
result = prime * result + userId;
result = prime * result + state;
result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
result = prime * result + ((overlayName == null) ? 0 : overlayName.hashCode());
result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
result = prime * result + ((targetOverlayableName == null) ? 0
: targetOverlayableName.hashCode());
@@ -439,6 +520,9 @@ public final class OverlayInfo implements Parcelable {
if (!packageName.equals(other.packageName)) {
return false;
}
if (!Objects.equals(overlayName, other.overlayName)) {
return false;
}
if (!targetPackageName.equals(other.targetPackageName)) {
return false;
}
@@ -457,9 +541,13 @@ public final class OverlayInfo implements Parcelable {
@NonNull
@Override
public String toString() {
return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
+ ((targetOverlayableName == null) ? ""
: ", targetOverlayable=" + targetOverlayableName)
+ ", state=" + state + " (" + stateToString(state) + "), userId=" + userId + " }";
return "OverlayInfo {"
+ "packageName=" + packageName
+ ", overlayName=" + overlayName
+ ", targetPackage=" + targetPackageName
+ ", targetOverlayable=" + targetOverlayableName
+ ", state=" + state + " (" + stateToString(state) + "),"
+ ", userId=" + userId
+ " }";
}
}

View File

@@ -204,6 +204,25 @@ public class OverlayManager {
}
}
/**
* Returns information about the overlay represented by the identifier for the specified user.
*
* @param overlay the identifier representing the overlay
* @param userHandle the user of which to get overlay state info
* @return the overlay info or null if the overlay cannot be found
*
* @hide
*/
@Nullable
public OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay,
@NonNull final UserHandle userHandle) {
try {
return mService.getOverlayInfoByIdentifier(overlay, userHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns information about all overlays for the given target package for
* the specified user. The returned list is ordered according to the

View File

@@ -20,6 +20,9 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -29,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
/**
* Container for a batch of requests to the OverlayManagerService.
@@ -60,12 +64,13 @@ public class OverlayManagerTransaction
private OverlayManagerTransaction(@NonNull final Parcel source) {
final int size = source.readInt();
mRequests = new ArrayList<Request>(size);
mRequests = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final int request = source.readInt();
final String packageName = source.readString();
final OverlayIdentifier overlay = source.readParcelable(null);
final int userId = source.readInt();
mRequests.add(new Request(request, packageName, userId));
final Bundle extras = source.readBundle(null);
mRequests.add(new Request(request, overlay, userId, extras));
}
}
@@ -95,22 +100,36 @@ public class OverlayManagerTransaction
public static final int TYPE_SET_ENABLED = 0;
public static final int TYPE_SET_DISABLED = 1;
public static final int TYPE_REGISTER_FABRICATED = 2;
public static final int TYPE_UNREGISTER_FABRICATED = 3;
@RequestType public final int type;
public final String packageName;
public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
@RequestType
public final int type;
@NonNull
public final OverlayIdentifier overlay;
public final int userId;
@Nullable
public final Bundle extras;
public Request(@RequestType final int type, @NonNull final String packageName,
public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
final int userId) {
this(type, overlay, userId, null /* extras */);
}
public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
final int userId, @Nullable Bundle extras) {
this.type = type;
this.packageName = packageName;
this.overlay = overlay;
this.userId = userId;
this.extras = extras;
}
@Override
public String toString() {
return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}",
type, typeToString(), packageName, userId);
return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
type, typeToString(), overlay, userId);
}
/**
@@ -123,6 +142,8 @@ public class OverlayManagerTransaction
switch (type) {
case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
}
}
@@ -152,22 +173,56 @@ public class OverlayManagerTransaction
* longer affect the resources of the target package. If the target is
* currently running, its outdated resources will be replaced by new ones.
*
* @param packageName The name of the overlay package.
* @param overlay The name of the overlay package.
* @param enable true to enable the overlay, false to disable it.
* @return this Builder object, so you can chain additional requests
*/
public Builder setEnabled(@NonNull String packageName, boolean enable) {
return setEnabled(packageName, enable, UserHandle.myUserId());
public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
return setEnabled(overlay, enable, UserHandle.myUserId());
}
/**
* @hide
*/
public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) {
checkNotNull(packageName);
public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
checkNotNull(overlay);
@Request.RequestType final int type =
enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
mRequests.add(new Request(type, packageName, userId));
mRequests.add(new Request(type, overlay, userId));
return this;
}
/**
* Registers the fabricated overlay with the overlay manager so it can be enabled and
* disabled for any user.
*
* The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
* the existing overlay will be replaced by the newly registered overlay and the enabled
* state of the overlay will be left unchanged if the target package and target overlayable
* have not changed.
*
* @param overlay the overlay to register with the overlay manager
*
* @hide
*/
public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
final Bundle extras = new Bundle();
extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
UserHandle.USER_ALL, extras));
return this;
}
/**
* Disables and removes the overlay from the overlay manager for all users.
*
* @param overlay the overlay to disable and remove
*
* @hide
*/
public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
UserHandle.USER_ALL));
return this;
}
@@ -195,8 +250,9 @@ public class OverlayManagerTransaction
for (int i = 0; i < size; i++) {
final Request req = mRequests.get(i);
dest.writeInt(req.type);
dest.writeString(req.packageName);
dest.writeParcelable(req.overlay, flags);
dest.writeInt(req.userId);
dest.writeBundle(req.extras);
}
}

View File

@@ -2807,7 +2807,7 @@ public class ParsingPackageUtils {
* limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
* @return Success if it's valid.
*/
public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
public static String validateName(String name, boolean requireSeparator,
boolean requireFilename) {
final int N = name.length();
boolean hasSep = false;
@@ -2828,18 +2828,28 @@ public class ParsingPackageUtils {
front = true;
continue;
}
return input.error("bad character '" + c + "'");
return "bad character '" + c + "'";
}
if (requireFilename) {
if (!FileUtils.isValidExtFilename(name)) {
return input.error("Invalid filename");
return "Invalid filename";
} else if (N > MAX_FILE_NAME_SIZE) {
return input.error("the length of the name is greater than " + MAX_FILE_NAME_SIZE);
return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
}
}
return hasSep || !requireSeparator
? input.success(null)
: input.error("must have at least one '.' separator");
return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
}
/**
* @see #validateName(String, boolean, boolean)
*/
public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
boolean requireFilename) {
final String errorMessage = validateName(name, requireSeparator, requireFilename);
if (errorMessage != null) {
return input.error(errorMessage);
}
return input.success(null);
}
/**

View File

@@ -188,7 +188,7 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> apk_assets;
std::unique_ptr<ApkAssets> apk_assets;
switch (format) {
case FORMAT_APK: {
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),

View File

@@ -2,6 +2,7 @@
<resources>
<string name="str">none</string>
<string name="str2">none</string>
<integer name="overlaid">0</integer>
<integer name="matrix_100000">100</integer>
<integer name="matrix_100001">100</integer>
<integer name="matrix_100010">100</integer>

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.overlaytest;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.content.om.FabricatedOverlay;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
import android.content.res.Resources;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
@RunWith(JUnit4.class)
@MediumTest
public class FabricatedOverlaysTest {
private static final String TAG = "FabricatedOverlaysTest";
private final String TEST_RESOURCE = "integer/overlaid";
private final String TEST_OVERLAY_NAME = "Test";
private Context mContext;
private Resources mResources;
private OverlayManager mOverlayManager;
private int mUserId;
private UserHandle mUserHandle;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mResources = mContext.getResources();
mOverlayManager = mContext.getSystemService(OverlayManager.class);
mUserId = UserHandle.myUserId();
mUserHandle = UserHandle.of(mUserId);
}
@After
public void tearDown() throws Exception {
final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
info -> {
if (info.isFabricated()) {
cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
}
});
mOverlayManager.commit(cleanUp.build());
}
@Test
public void testFabricatedOverlay() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.build());
OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
assertNotNull(info);
assertFalse(info.isEnabled());
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build());
info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
assertNotNull(info);
assertTrue(info.isEnabled());
waitForResourceValue(1);
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.unregisterFabricatedOverlay(overlay.getIdentifier())
.build());
waitForResourceValue(0);
}
@Test
public void testRegisterEnableAtomic() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build());
waitForResourceValue(1);
}
@Test
public void testRegisterTwice() throws Exception {
FabricatedOverlay overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build());
waitForResourceValue(1);
overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 2)
.build();
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.build());
waitForResourceValue(2);
}
@Test
public void testInvalidOwningPackageName() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
"android", TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
assertThrows(SecurityException.class, () ->
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build()));
assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
@Test
public void testInvalidOverlayName() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), "invalid@name", mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
assertThrows(SecurityException.class, () ->
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build()));
assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
@Test
public void testOverlayIdentifierLongest() throws Exception {
final int maxLength = 255 - 11; // 11 reserved characters
final String longestName = String.join("",
Collections.nCopies(maxLength - mContext.getPackageName().length(), "a"));
{
FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
longestName, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.build());
assertNotNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
{
FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
longestName + "a", mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
assertThrows(SecurityException.class, () ->
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.build()));
assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
}
@Test
public void testInvalidResourceValues() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
"android", TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
assertThrows(SecurityException.class, () ->
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.build()));
assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
@Test
public void testTransactionFailRollback() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
assertThrows(SecurityException.class, () ->
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.setEnabled(overlay.getIdentifier(), true, mUserId)
.setEnabled(new OverlayIdentifier("not-valid"), true, mUserId)
.build()));
assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
}
void waitForResourceValue(final int expectedValue) throws TimeoutException {
final long timeOutDuration = 10000;
final long endTime = System.currentTimeMillis() + timeOutDuration;
final String resourceName = TEST_RESOURCE;
final int resourceId = mResources.getIdentifier(resourceName, "",
mContext.getPackageName());
int resourceValue = 0;
while (System.currentTimeMillis() < endTime) {
resourceValue = mResources.getInteger(resourceId);
if (resourceValue == expectedValue) {
return;
}
}
final String paths = TextUtils.join(",", mResources.getAssets().getApkPaths());
Log.w(TAG, "current paths: [" + paths + "]", new Throwable());
throw new TimeoutException("Timed out waiting for '" + resourceName + "' value to equal '"
+ expectedValue + "': current value is '" + resourceValue + "'");
}
}

View File

@@ -20,6 +20,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import android.annotation.NonNull;
import android.content.Context;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
@@ -32,14 +33,14 @@ import java.util.concurrent.FutureTask;
class LocalOverlayManager {
private static final long TIMEOUT = 30;
public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable,
@NonNull final String[] overlaysToDisable) throws Exception {
public static void toggleOverlaysAndWait(@NonNull final OverlayIdentifier[] overlaysToEnable,
@NonNull final OverlayIdentifier[] overlaysToDisable) throws Exception {
final int userId = UserHandle.myUserId();
OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
for (String pkg : overlaysToEnable) {
for (OverlayIdentifier pkg : overlaysToEnable) {
builder.setEnabled(pkg, true, userId);
}
for (String pkg : overlaysToDisable) {
for (OverlayIdentifier pkg : overlaysToDisable) {
builder.setEnabled(pkg, false, userId);
}
OverlayManagerTransaction transaction = builder.build();
@@ -48,7 +49,7 @@ class LocalOverlayManager {
FutureTask<Boolean> task = new FutureTask<>(() -> {
while (true) {
final String[] paths = ctx.getResources().getAssets().getApkPaths();
if (arrayTailContains(paths, overlaysToEnable)
if (arrayTailContainsOverlays(paths, overlaysToEnable)
&& arrayDoesNotContain(paths, overlaysToDisable)) {
return true;
}
@@ -64,15 +65,15 @@ class LocalOverlayManager {
task.get(TIMEOUT, SECONDS);
}
private static boolean arrayTailContains(@NonNull final String[] array,
@NonNull final String[] substrings) {
if (array.length < substrings.length) {
private static boolean arrayTailContainsOverlays(@NonNull final String[] array,
@NonNull final OverlayIdentifier[] overlays) {
if (array.length < overlays.length) {
return false;
}
for (int i = 0; i < substrings.length; i++) {
String a = array[array.length - substrings.length + i];
String s = substrings[i];
if (!a.contains(s)) {
for (int i = 0; i < overlays.length; i++) {
String a = array[array.length - overlays.length + i];
OverlayIdentifier s = overlays[i];
if (!a.contains(s.getPackageName())) {
return false;
}
}
@@ -80,10 +81,10 @@ class LocalOverlayManager {
}
private static boolean arrayDoesNotContain(@NonNull final String[] array,
@NonNull final String[] substrings) {
for (String s : substrings) {
@NonNull final OverlayIdentifier[] overlays) {
for (OverlayIdentifier s : overlays) {
for (String a : array) {
if (a.contains(s)) {
if (a.contains(s.getPackageName())) {
return false;
}
}

View File

@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.content.om.OverlayIdentifier;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -58,9 +59,12 @@ public abstract class OverlayBaseTest {
static final int MODE_SINGLE_OVERLAY = 1;
static final int MODE_MULTIPLE_OVERLAYS = 2;
static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
static final String FRAMEWORK_OVERLAY_PKG = "com.android.overlaytest.framework";
static final OverlayIdentifier APP_OVERLAY_ONE_PKG =
new OverlayIdentifier("com.android.overlaytest.app_overlay_one");
static final OverlayIdentifier APP_OVERLAY_TWO_PKG =
new OverlayIdentifier("com.android.overlaytest.app_overlay_two");
static final OverlayIdentifier FRAMEWORK_OVERLAY_PKG =
new OverlayIdentifier("com.android.overlaytest.framework");
protected OverlayBaseTest(int mode) {
mMode = mode;

View File

@@ -16,14 +16,19 @@
package com.android.overlaytest;
import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_ONE_PKG;
import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_TWO_PKG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.UserHandle;
@@ -40,8 +45,6 @@ import java.util.List;
@RunWith(JUnit4.class)
@MediumTest
public class TransactionTest {
static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
private Context mContext;
private Resources mResources;
@@ -58,8 +61,8 @@ public class TransactionTest {
mUserHandle = UserHandle.of(mUserId);
LocalOverlayManager.toggleOverlaysAndWait(
new String[]{},
new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
new OverlayIdentifier[]{},
new OverlayIdentifier[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
}
@Test
@@ -78,8 +81,8 @@ public class TransactionTest {
List<OverlayInfo> ois =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois.size(), 2);
assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG);
assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG);
assertEquals(ois.get(0).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
assertEquals(ois.get(1).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_TWO_PKG, true)
@@ -92,8 +95,8 @@ public class TransactionTest {
List<OverlayInfo> ois2 =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois2.size(), 2);
assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG);
assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG);
assertEquals(ois2.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
assertEquals(ois2.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_TWO_PKG, false)
@@ -105,8 +108,8 @@ public class TransactionTest {
List<OverlayInfo> ois3 =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois3.size(), 2);
assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG);
assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG);
assertEquals(ois3.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
assertEquals(ois3.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
}
@Test
@@ -116,7 +119,7 @@ public class TransactionTest {
OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_ONE_PKG, true)
.setEnabled("does-not-exist", true)
.setEnabled(new OverlayIdentifier("does-not-exist"), true)
.setEnabled(APP_OVERLAY_TWO_PKG, true)
.build();
assertThrows(SecurityException.class, () -> mOverlayManager.commit(t));
@@ -125,9 +128,10 @@ public class TransactionTest {
assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
}
private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) {
final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId));
private void assertOverlayIsEnabled(final OverlayIdentifier overlay, boolean enabled,
int userId) {
final OverlayInfo oi = mOverlayManager.getOverlayInfo(overlay, UserHandle.of(userId));
assertNotNull(oi);
assertEquals(oi.isEnabled(), enabled);
assertEquals(enabled, oi.isEnabled());
}
}

View File

@@ -16,6 +16,8 @@
package com.android.overlaytest;
import android.content.om.OverlayIdentifier;
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,9 @@ public class WithMultipleOverlaysTest extends OverlayBaseTest {
@BeforeClass
public static void enableOverlay() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG},
new String[]{});
new OverlayIdentifier[]{
FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
},
new OverlayIdentifier[]{});
}
}

View File

@@ -16,6 +16,8 @@
package com.android.overlaytest;
import android.content.om.OverlayIdentifier;
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,7 @@ public class WithOverlayTest extends OverlayBaseTest {
@BeforeClass
public static void enableOverlays() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
new String[]{APP_OVERLAY_TWO_PKG});
new OverlayIdentifier[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
new OverlayIdentifier[]{APP_OVERLAY_TWO_PKG});
}
}

View File

@@ -16,6 +16,8 @@
package com.android.overlaytest;
import android.content.om.OverlayIdentifier;
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,9 @@ public class WithoutOverlayTest extends OverlayBaseTest {
@BeforeClass
public static void disableOverlays() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
new String[]{},
new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
new OverlayIdentifier[]{},
new OverlayIdentifier[]{
FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
});
}
}

View File

@@ -25,7 +25,6 @@
<item target="integer/matrix_100100" value="@integer/matrix_100100"/>
<item target="integer/matrix_100101" value="@integer/matrix_100101"/>
<item target="integer/matrix_100110" value="@integer/matrix_100110"/>
<item target="integer/matrix_100110" value="@integer/matrix_100110"/>
<item target="integer/matrix_100111" value="@integer/matrix_100111"/>
<item target="integer/matrix_101000" value="@integer/matrix_101000"/>
<item target="integer/matrix_101001" value="@integer/matrix_101001"/>

View File

@@ -83,8 +83,16 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
return {};
}
std::unique_ptr<AssetsProvider> overlay_assets;
const std::string overlay_path(loaded_idmap->OverlayApkPath());
auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
if (IsFabricatedOverlay(overlay_path)) {
// Fabricated overlays do not contain resource definitions. All of the overlay resource values
// are defined inline in the idmap.
overlay_assets = EmptyAssetsProvider::Create();
} else {
// The overlay should be an APK.
overlay_assets = ZipAssetsProvider::Create(overlay_path);
}
if (overlay_assets == nullptr) {
return {};
}

View File

@@ -102,9 +102,8 @@ AssetManager2::AssetManager2() {
memset(&configuration_, 0, sizeof(configuration_));
}
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
bool invalidate_caches) {
apk_assets_ = apk_assets;
bool AssetManager2::SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches) {
apk_assets_ = std::move(apk_assets);
BuildDynamicRefTable();
RebuildFilterList();
if (invalidate_caches) {
@@ -137,6 +136,36 @@ void AssetManager2::BuildDynamicRefTable() {
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
for (const ApkAssets* apk_assets : sorted_apk_assets) {
std::shared_ptr<OverlayDynamicRefTable> overlay_ref_table;
if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
// The target package must precede the overlay package in the apk assets paths in order
// to take effect.
auto iter = apk_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
if (iter == apk_assets_package_ids.end()) {
LOG(INFO) << "failed to find target package for overlay "
<< loaded_idmap->OverlayApkPath();
} else {
uint8_t target_package_id = iter->second;
// Create a special dynamic reference table for the overlay to rewrite references to
// overlay resources as references to the target resources they overlay.
overlay_ref_table = std::make_shared<OverlayDynamicRefTable>(
loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
// Add the overlay resource map to the target package's set of overlays.
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay target '" << loaded_idmap->TargetApkPath()
<< "'added to apk_assets_package_ids but does not have an"
<< " assigned package group";
PackageGroup& target_package_group = package_groups_[target_idx];
target_package_group.overlays_.push_back(
ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
overlay_ref_table.get()),
apk_assets_cookies[apk_assets]});
}
}
const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
@@ -147,50 +176,25 @@ void AssetManager2::BuildDynamicRefTable() {
package_id = package->GetPackageId();
}
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
// Add the mapping for package ID to index if not present.
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
package_groups_.push_back({});
PackageGroup& new_group = package_groups_.emplace_back();
if (apk_assets->IsOverlay()) {
// The target package must precede the overlay package in the apk assets paths in order
// to take effect.
const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
auto target_package_iter = apk_assets_package_ids.find(
std::string(loaded_idmap->TargetApkPath()));
if (target_package_iter == apk_assets_package_ids.end()) {
LOG(INFO) << "failed to find target package for overlay "
<< loaded_idmap->OverlayApkPath();
} else {
const uint8_t target_package_id = target_package_iter->second;
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
<< " have an assigned package group";
PackageGroup& target_package_group = package_groups_[target_idx];
// Create a special dynamic reference table for the overlay to rewrite references to
// overlay resources as references to the target resources they overlay.
auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
package_groups_.back().dynamic_ref_table = overlay_table;
// Add the overlay resource map to the target package's set of overlays.
target_package_group.overlays_.push_back(
ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
overlay_table.get()),
apk_assets_cookies[apk_assets]});
}
if (overlay_ref_table != nullptr) {
// If this package is from an overlay, use a dynamic reference table that can rewrite
// overlay resource ids to their corresponding target resource ids.
new_group.dynamic_ref_table = overlay_ref_table;
}
DynamicRefTable* ref_table = package_groups_.back().dynamic_ref_table.get();
DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
ref_table->mAssignedPackageId = package_id;
ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
PackageGroup* package_group = &package_groups_[idx];
package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
@@ -578,7 +582,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
const PackageGroup& package_group = package_groups_[package_idx];
auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
stop_at_first_match, ignore_configuration);
stop_at_first_match, ignore_configuration);
if (UNLIKELY(!result.has_value())) {
return base::unexpected(result.error());
}

View File

@@ -84,7 +84,7 @@ const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
return value_;
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
time_t last_mod_time)
: zip_handle_(handle, ::CloseArchive),
name_(std::forward<PathOrDebugName>(path)),
@@ -93,7 +93,7 @@ ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
ZipArchiveHandle handle;
if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
@@ -253,6 +253,14 @@ bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
return result == -1;
}
std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
::ZipEntry entry;
if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
return {};
}
return entry.crc32;
}
const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}

View File

@@ -54,12 +54,6 @@ struct Idmap_header {
};
struct Idmap_data_header {
uint8_t target_package_id;
uint8_t overlay_package_id;
// Padding to ensure 4 byte alignment for target_entry_count
uint16_t p0;
uint32_t target_entry_count;
uint32_t target_inline_entry_count;
uint32_t overlay_entry_count;
@@ -158,19 +152,19 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const {
return {};
}
// The resource ids encoded within the idmap are build-time resource ids.
target_res_id = (0x00FFFFFFU & target_res_id)
| (((uint32_t) data_header_->target_package_id) << 24U);
// The resource ids encoded within the idmap are build-time resource ids so do not consider the
// package id when determining if the resource in the target package is overlaid.
target_res_id &= 0x00FFFFFFU;
// Check if the target resource is mapped to an overlay resource.
auto first_entry = entries_;
auto end_entry = entries_ + dtohl(data_header_->target_entry_count);
auto entry = std::lower_bound(first_entry, end_entry, target_res_id,
[](const Idmap_target_entry &e, const uint32_t target_id) {
return dtohl(e.target_id) < target_id;
[](const Idmap_target_entry& e, const uint32_t target_id) {
return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
});
if (entry != end_entry && dtohl(entry->target_id) == target_res_id) {
if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) {
uint32_t overlay_resource_id = dtohl(entry->overlay_id);
// Lookup the resource without rewriting the overlay resource id back to the target resource id
// being looked up.
@@ -182,12 +176,13 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const {
auto first_inline_entry = inline_entries_;
auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count);
auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id,
[](const Idmap_target_entry_inline &e,
[](const Idmap_target_entry_inline& e,
const uint32_t target_id) {
return dtohl(e.target_id) < target_id;
return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
});
if (inline_entry != end_inline_entry && dtohl(inline_entry->target_id) == target_res_id) {
if (inline_entry != end_inline_entry &&
(0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
return Result(inline_entry->value);
}
return {};
@@ -235,7 +230,7 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size
}
return std::string_view(data, *len);
}
}
} // namespace
LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
const Idmap_header* header,

View File

@@ -25,6 +25,7 @@
#include <string.h>
#include <algorithm>
#include <fstream>
#include <limits>
#include <map>
#include <memory>
@@ -44,6 +45,7 @@
#ifdef __ANDROID__
#include <binder/TextOutput.h>
#endif
#ifndef INT32_MAX
@@ -233,6 +235,15 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs
fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
}
bool IsFabricatedOverlay(const std::string& path) {
std::ifstream fin(path);
uint32_t magic;
if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
return magic == kFabricatedOverlayMagic;
}
return false;
}
static bool assertIdmapHeader(const void* idmap, size_t size) {
if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
ALOGE("idmap: header is not word aligned");

View File

@@ -101,7 +101,7 @@ class AssetManager2 {
// Only pass invalidate_caches=false when it is known that the structure
// change in ApkAssets is due to a safe addition of resources with completely
// new resource IDs.
bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
inline const std::vector<const ApkAssets*> GetApkAssets() const {
return apk_assets_;

View File

@@ -88,6 +88,8 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED const std::string& GetDebugName() const override;
WARN_UNUSED bool IsUpToDate() const override;
WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
~ZipAssetsProvider() override = default;
protected:
std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,

View File

@@ -168,15 +168,14 @@ class LoadedIdmap {
}
// Returns a mapping from target resource ids to overlay values.
const IdmapResMap GetTargetResourcesMap(
uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const {
const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table) const {
return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
target_assigned_package_id, overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
const OverlayDynamicRefTable GetOverlayDynamicRefTable(
uint8_t target_assigned_package_id) const {
const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
}

View File

@@ -43,8 +43,19 @@
namespace android {
constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u;
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
// This must never change.
constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of

View File

@@ -82,6 +82,7 @@ java_library_static {
":framework_native_aidl",
":gsiservice_aidl",
":idmap2_aidl",
":idmap2_core_aidl",
":inputconstants_aidl",
":installd_aidl",
":storaged_aidl",

View File

@@ -18,6 +18,7 @@ package com.android.server.om;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.om.OverlayIdentifier;
import android.os.UserHandle;
/**
@@ -26,6 +27,7 @@ import android.os.UserHandle;
public final class DumpState {
@UserIdInt private int mUserId = UserHandle.USER_ALL;
@Nullable private String mPackageName;
@Nullable private String mOverlayName;
@Nullable private String mField;
private boolean mVerbose;
@@ -38,14 +40,19 @@ public final class DumpState {
}
/** Sets the name of the package to dump the state for */
public void setPackageName(String packageName) {
mPackageName = packageName;
public void setOverlyIdentifier(String overlayIdentifier) {
final OverlayIdentifier overlay = OverlayIdentifier.fromString(overlayIdentifier);
mPackageName = overlay.getPackageName();
mOverlayName = overlay.getOverlayName();
}
@Nullable public String getPackageName() {
return mPackageName;
}
@Nullable public String getOverlayName() {
return mOverlayName;
}
/** Sets the name of the field to dump the state of */
/** Sets the name of the field to dump the state for */
public void setField(String field) {
mField = field;
}

View File

@@ -20,17 +20,23 @@ import static android.content.Context.IDMAP_SERVICE;
import static com.android.server.om.OverlayManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.IBinder;
import android.os.IIdmap2;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemService;
import android.text.TextUtils;
import android.util.Slog;
import com.android.server.FgThread;
import java.io.File;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -104,10 +110,12 @@ class IdmapDaemon {
return sInstance;
}
String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId) throws TimeoutException, RemoteException {
String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
@Nullable String overlayName, int policies, boolean enforce, int userId)
throws TimeoutException, RemoteException {
try (Connection c = connect()) {
return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
policies, enforce, userId);
}
}
@@ -117,11 +125,12 @@ class IdmapDaemon {
}
}
boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId)
boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
@Nullable String overlayName, int policies, boolean enforce, int userId)
throws Exception {
try (Connection c = connect()) {
return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId);
return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
policies, enforce, userId);
}
}
@@ -129,12 +138,38 @@ class IdmapDaemon {
try (Connection c = connect()) {
return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
} catch (Exception e) {
Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
+ e.getMessage());
Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
return false;
}
}
FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
try (Connection c = connect()) {
return mService.createFabricatedOverlay(overlay);
} catch (Exception e) {
Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
return null;
}
}
boolean deleteFabricatedOverlay(@NonNull String path) {
try (Connection c = connect()) {
return mService.deleteFabricatedOverlay(path);
} catch (Exception e) {
Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
return false;
}
}
List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
try (Connection c = connect()) {
return mService.getFabricatedOverlayInfos();
} catch (Exception e) {
Slog.wtf(TAG, "failed to get fabricated overlays", e);
return null;
}
}
private IBinder getIdmapService() throws TimeoutException, RemoteException {
SystemService.start(IDMAP_DAEMON);

View File

@@ -22,15 +22,19 @@ import static com.android.server.om.OverlayManagerService.TAG;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.os.Build.VERSION_CODES;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.OverlayablePolicy;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Slog;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.IOException;
import java.util.List;
/**
* Handle the creation and deletion of idmap files.
@@ -74,25 +78,26 @@ final class IdmapManager {
* Creates the idmap for the target/overlay combination and returns whether the idmap file was
* modified.
*/
boolean createIdmap(@NonNull final PackageInfo targetPackage,
@NonNull final PackageInfo overlayPackage, int userId) {
boolean createIdmap(@NonNull final AndroidPackage targetPackage,
@NonNull final AndroidPackage overlayPackage, String overlayBasePath,
String overlayName, int userId) {
if (DEBUG) {
Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
+ overlayPackage.packageName);
Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
+ overlayPackage.getPackageName());
}
final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
final String targetPath = targetPackage.getBaseApkPath();
try {
int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
boolean enforce = enforceOverlayable(overlayPackage);
if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
enforce, userId)) {
return false;
}
return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
return mIdmapDaemon.createIdmap(targetPath, overlayBasePath, overlayName, policies,
enforce, userId) != null;
} catch (Exception e) {
Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
+ overlayPath + ": " + e.getMessage());
+ overlayBasePath, e);
return false;
}
}
@@ -104,7 +109,7 @@ final class IdmapManager {
try {
return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
} catch (Exception e) {
Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath, e);
return false;
}
}
@@ -113,22 +118,40 @@ final class IdmapManager {
return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
}
boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
/**
* @return the list of all fabricated overlays
*/
List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
return mIdmapDaemon.getFabricatedOverlayInfos();
}
/**
* Creates a fabricated overlay and persists it to disk.
* @return the path to the fabricated overlay
*/
FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
return mIdmapDaemon.createFabricatedOverlay(overlay);
}
/**
* Deletes the fabricated overlay file on disk.
* @return whether the path was deleted
*/
boolean deleteFabricatedOverlay(@NonNull String path) {
return mIdmapDaemon.deleteFabricatedOverlay(path);
}
/**
* Checks if overlayable and policies should be enforced on the specified overlay for backwards
* compatibility with pre-Q overlays.
*/
private boolean enforceOverlayable(@NonNull final PackageInfo overlayPackage) {
final ApplicationInfo ai = overlayPackage.applicationInfo;
if (ai.targetSdkVersion >= VERSION_CODES.Q) {
private boolean enforceOverlayable(@NonNull final AndroidPackage overlayPackage) {
if (overlayPackage.getTargetSdkVersion() >= VERSION_CODES.Q) {
// Always enforce policies for overlays targeting Q+.
return true;
}
if (ai.isVendor()) {
if (overlayPackage.isVendor()) {
// If the overlay is on a pre-Q vendor partition, do not enforce overlayable
// restrictions on this overlay because the pre-Q platform has no understanding of
// overlayable.
@@ -137,20 +160,19 @@ final class IdmapManager {
// Do not enforce overlayable restrictions on pre-Q overlays that are signed with the
// platform signature or that are preinstalled.
return !(ai.isSystemApp() || ai.isSignedWithPlatformKey());
return !(overlayPackage.isSystem() || overlayPackage.isSignedWithPlatformKey());
}
/**
* Retrieves a bitmask for idmap2 that represents the policies the overlay fulfills.
*/
private int calculateFulfilledPolicies(@NonNull final PackageInfo targetPackage,
@NonNull final PackageInfo overlayPackage, int userId) {
final ApplicationInfo ai = overlayPackage.applicationInfo;
private int calculateFulfilledPolicies(@NonNull final AndroidPackage targetPackage,
@NonNull final AndroidPackage overlayPackage, int userId) {
int fulfilledPolicies = OverlayablePolicy.PUBLIC;
// Overlay matches target signature
if (mPackageManager.signaturesMatching(targetPackage.packageName,
overlayPackage.packageName, userId)) {
if (mPackageManager.signaturesMatching(targetPackage.getPackageName(),
overlayPackage.getPackageName(), userId)) {
fulfilledPolicies |= OverlayablePolicy.SIGNATURE;
}
@@ -164,52 +186,52 @@ final class IdmapManager {
// preinstalled package, check if overlay matches its signature.
if (!TextUtils.isEmpty(mConfigSignaturePackage)
&& mPackageManager.signaturesMatching(mConfigSignaturePackage,
overlayPackage.packageName,
overlayPackage.getPackageName(),
userId)) {
fulfilledPolicies |= OverlayablePolicy.CONFIG_SIGNATURE;
}
// Vendor partition (/vendor)
if (ai.isVendor()) {
if (overlayPackage.isVendor()) {
return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
}
// Product partition (/product)
if (ai.isProduct()) {
if (overlayPackage.isProduct()) {
return fulfilledPolicies | OverlayablePolicy.PRODUCT_PARTITION;
}
// Odm partition (/odm)
if (ai.isOdm()) {
if (overlayPackage.isOdm()) {
return fulfilledPolicies | OverlayablePolicy.ODM_PARTITION;
}
// Oem partition (/oem)
if (ai.isOem()) {
if (overlayPackage.isOem()) {
return fulfilledPolicies | OverlayablePolicy.OEM_PARTITION;
}
// System_ext partition (/system_ext) is considered as system
// Check this last since every partition except for data is scanned as system in the PMS.
if (ai.isSystemApp() || ai.isSystemExt()) {
if (overlayPackage.isSystem() || overlayPackage.isSystemExt()) {
return fulfilledPolicies | OverlayablePolicy.SYSTEM_PARTITION;
}
return fulfilledPolicies;
}
private boolean matchesActorSignature(@NonNull PackageInfo targetPackage,
@NonNull PackageInfo overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.targetOverlayableName;
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetName();
if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.packageName, targetOverlayableName, userId);
targetPackage.getPackageName(), targetOverlayableName, userId);
if (overlayableInfo != null && overlayableInfo.actor != null) {
String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
overlayableInfo.actor, mPackageManager.getNamedActors()).first;
if (mPackageManager.signaturesMatching(actorPackageName,
overlayPackage.packageName, userId)) {
overlayPackage.getPackageName(), userId)) {
return true;
}
}

View File

@@ -19,8 +19,6 @@ package com.android.server.om;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
@@ -29,6 +27,7 @@ import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.IOException;
import java.util.List;
@@ -114,12 +113,13 @@ public class OverlayActorEnforcer {
}
final String targetPackageName = overlayInfo.targetPackageName;
final PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
userId);
if (targetPkgInfo == null) {
return ActorState.TARGET_NOT_FOUND;
}
if ((targetPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
if (targetPkgInfo.isDebuggable()) {
return ActorState.ALLOWED;
}
@@ -189,23 +189,18 @@ public class OverlayActorEnforcer {
return actorUriState;
}
String packageName = actorUriPair.first;
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, userId);
if (packageInfo == null) {
return ActorState.MISSING_APP_INFO;
}
ApplicationInfo appInfo = packageInfo.applicationInfo;
if (appInfo == null) {
return ActorState.MISSING_APP_INFO;
String actorPackageName = actorUriPair.first;
AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
if (actorPackage == null) {
return ActorState.ACTOR_NOT_FOUND;
}
// Currently only pre-installed apps can be actors
if (!appInfo.isSystemApp()) {
if (!actorPackage.isSystem()) {
return ActorState.ACTOR_NOT_PREINSTALLED;
}
if (ArrayUtils.contains(callingPackageNames, packageName)) {
if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
return ActorState.ALLOWED;
}
@@ -231,7 +226,7 @@ public class OverlayActorEnforcer {
NO_NAMED_ACTORS,
MISSING_NAMESPACE,
MISSING_ACTOR_NAME,
MISSING_APP_INFO,
ACTOR_NOT_FOUND,
ACTOR_NOT_PREINSTALLED,
INVALID_ACTOR,
ALLOWED

View File

@@ -24,8 +24,10 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_REASON;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
@@ -43,11 +45,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.om.IOverlayManager;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
import android.content.om.OverlayableInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.pm.overlay.OverlayPaths;
@@ -55,8 +57,10 @@ import android.content.res.ApkAssets;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.FabricatedOverlayInternal;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -71,12 +75,15 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import libcore.util.EmptyArray;
@@ -92,13 +99,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
/**
* Service to manage asset overlays.
@@ -247,15 +253,6 @@ public final class OverlayManagerService extends SystemService {
private final OverlayActorEnforcer mActorEnforcer;
private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
persistSettings();
FgThread.getHandler().post(() -> {
List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
updateActivityManager(affectedTargets, pair.userId);
broadcastActionOverlayChanged(pair.packageName, pair.userId);
});
};
public OverlayManagerService(@NonNull final Context context) {
super(context);
try {
@@ -315,8 +312,7 @@ public final class OverlayManagerService extends SystemService {
// Initialize any users that can't be switched to, as their state would
// never be setup in onSwitchUser(). We will switch to the system user right
// after this, and its state will be setup there.
final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
updatePackageManager(targets, users.get(i).id);
updatePackageManager(mImpl.updateOverlaysForUser(users.get(i).id));
}
}
}
@@ -333,9 +329,7 @@ public final class OverlayManagerService extends SystemService {
// ensure overlays in the settings are up-to-date, and propagate
// any asset changes to the rest of the system
synchronized (mLock) {
final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
final List<String> affectedTargets = updatePackageManager(targets, newUserId);
updateActivityManager(affectedTargets, newUserId);
updateActivityManager(updatePackageManager(mImpl.updateOverlaysForUser(newUserId)));
}
persistSettings();
} finally {
@@ -417,19 +411,11 @@ public final class OverlayManagerService extends SystemService {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
for (final int userId : userIds) {
synchronized (mLock) {
final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
false);
if (pi != null && !pi.applicationInfo.isInstantApp()) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
final AndroidPackage pkg = mPackageManager.onPackageAdded(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
if (pi.isOverlayPackage()) {
mImpl.onOverlayPackageAdded(packageName, userId)
.ifPresent(mPropagateOverlayChange);
} else {
mImpl.onTargetPackageAdded(packageName, userId)
.ifPresent(mPropagateOverlayChange);
}
updateTargetPackages(mImpl.onPackageAdded(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageAdded internal error", e);
}
@@ -447,19 +433,11 @@ public final class OverlayManagerService extends SystemService {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
false);
if (pi != null && pi.applicationInfo.isInstantApp()) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
if (pi.isOverlayPackage()) {
mImpl.onOverlayPackageChanged(packageName, userId)
.ifPresent(mPropagateOverlayChange);
} else {
mImpl.onTargetPackageChanged(packageName, userId)
.ifPresent(mPropagateOverlayChange);
}
updateTargetPackages(mImpl.onPackageChanged(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageChanged internal error", e);
}
@@ -477,12 +455,11 @@ public final class OverlayManagerService extends SystemService {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
mPackageManager.forgetPackageInfo(packageName, userId);
final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
if (oi != null) {
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
mImpl.onOverlayPackageReplacing(packageName, userId)
.ifPresent(mPropagateOverlayChange);
updateTargetPackages(mImpl.onPackageReplacing(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplacing internal error", e);
}
@@ -500,18 +477,11 @@ public final class OverlayManagerService extends SystemService {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
false);
if (pi != null && !pi.applicationInfo.isInstantApp()) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
if (pi.isOverlayPackage()) {
mImpl.onOverlayPackageReplaced(packageName, userId)
.ifPresent(mPropagateOverlayChange);
} else {
mImpl.onTargetPackageReplaced(packageName, userId)
.ifPresent(mPropagateOverlayChange);
}
updateTargetPackages(mImpl.onPackageReplaced(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplaced internal error", e);
}
@@ -529,20 +499,8 @@ public final class OverlayManagerService extends SystemService {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
mPackageManager.forgetPackageInfo(packageName, userId);
final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
try {
if (oi != null) {
mImpl.onOverlayPackageRemoved(packageName, userId)
.ifPresent(mPropagateOverlayChange);
} else {
mImpl.onTargetPackageRemoved(packageName, userId)
.ifPresent(mPropagateOverlayChange);
}
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageRemoved internal error", e);
}
mPackageManager.onPackageRemoved(packageName, userId);
updateTargetPackages(mImpl.onPackageRemoved(packageName, userId));
}
}
} finally {
@@ -560,11 +518,9 @@ public final class OverlayManagerService extends SystemService {
if (userId != UserHandle.USER_NULL) {
try {
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
final ArrayList<String> targets;
synchronized (mLock) {
targets = mImpl.updateOverlaysForUser(userId);
updatePackageManager(mImpl.updateOverlaysForUser(userId));
}
updatePackageManager(targets, userId);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -628,16 +584,22 @@ public final class OverlayManagerService extends SystemService {
@Override
public OverlayInfo getOverlayInfo(@Nullable final String packageName,
final int userIdArg) {
if (packageName == null) {
return getOverlayInfoByIdentifier(new OverlayIdentifier(packageName), userIdArg);
}
@Override
public OverlayInfo getOverlayInfoByIdentifier(@Nullable final OverlayIdentifier overlay,
final int userIdArg) {
if (overlay == null || overlay.getPackageName() == null) {
return null;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + packageName);
traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + overlay);
final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfo");
synchronized (mLock) {
return mImpl.getOverlayInfo(packageName, realUserId);
return mImpl.getOverlayInfo(overlay, realUserId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
@@ -653,15 +615,16 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabled");
enforceActor(packageName, "setEnabled", realUserId);
enforceActor(overlay, "setEnabled", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setEnabled(packageName, enable, realUserId)
.ifPresent(mPropagateOverlayChange);
updateTargetPackages(mImpl.setEnabled(overlay, enable, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
@@ -684,16 +647,18 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabledExclusive");
enforceActor(packageName, "setEnabledExclusive", realUserId);
enforceActor(overlay, "setEnabledExclusive", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setEnabledExclusive(packageName,
mImpl.setEnabledExclusive(overlay,
false /* withinCategory */, realUserId)
.ifPresent(mPropagateOverlayChange);
.ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -716,17 +681,19 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg,
"setEnabledExclusiveInCategory");
enforceActor(packageName, "setEnabledExclusiveInCategory", realUserId);
enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setEnabledExclusive(packageName,
mImpl.setEnabledExclusive(overlay,
true /* withinCategory */, realUserId)
.ifPresent(mPropagateOverlayChange);
.ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -750,15 +717,18 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
+ parentPackageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final OverlayIdentifier parentOverlay = new OverlayIdentifier(parentPackageName);
final int realUserId = handleIncomingUser(userIdArg, "setPriority");
enforceActor(packageName, "setPriority", realUserId);
enforceActor(overlay, "setPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setPriority(packageName, parentPackageName, realUserId)
.ifPresent(mPropagateOverlayChange);
mImpl.setPriority(overlay, parentOverlay, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -780,15 +750,16 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setHighestPriority");
enforceActor(packageName, "setHighestPriority", realUserId);
enforceActor(overlay, "setHighestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setHighestPriority(packageName, realUserId)
.ifPresent(mPropagateOverlayChange);
updateTargetPackages(mImpl.setHighestPriority(overlay, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
@@ -810,15 +781,17 @@ public final class OverlayManagerService extends SystemService {
try {
traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setLowestPriority");
enforceActor(packageName, "setLowestPriority", realUserId);
enforceActor(overlay, "setLowestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setLowestPriority(packageName, realUserId)
.ifPresent(mPropagateOverlayChange);
mImpl.setLowestPriority(overlay, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -858,12 +831,17 @@ public final class OverlayManagerService extends SystemService {
return;
}
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "invalidateCachesForOverlay");
enforceActor(packageName, "invalidateCachesForOverlay", realUserId);
enforceActor(overlay, "invalidateCachesForOverlay", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mImpl.removeIdmapForOverlay(packageName, realUserId);
try {
mImpl.removeIdmapForOverlay(overlay, realUserId);
} catch (OperationFailedException e) {
Slog.w(TAG, "invalidate caches for overlay '" + overlay + "' failed", e);
}
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -893,25 +871,63 @@ public final class OverlayManagerService extends SystemService {
}
}
private Optional<PackageAndUser> executeRequest(
@NonNull final OverlayManagerTransaction.Request request) throws Exception {
final int realUserId = handleIncomingUser(request.userId, request.typeToString());
enforceActor(request.packageName, request.typeToString(), realUserId);
private Set<PackageAndUser> executeRequest(
@NonNull final OverlayManagerTransaction.Request request)
throws OperationFailedException {
Objects.requireNonNull(request, "Transaction contains a null request");
Objects.requireNonNull(request.overlay,
"Transaction overlay identifier must be non-null");
final int callingUid = Binder.getCallingUid();
final int realUserId;
if (request.type == TYPE_REGISTER_FABRICATED
|| request.type == TYPE_UNREGISTER_FABRICATED) {
if (request.userId != UserHandle.USER_ALL) {
throw new IllegalArgumentException(request.typeToString()
+ " unsupported for user " + request.userId);
}
realUserId = UserHandle.USER_ALL;
// Enforce that the calling process can only register and unregister fabricated
// overlays using its package name.
final String pkgName = request.overlay.getPackageName();
if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
mPackageManager.getPackagesForUid(callingUid), pkgName)) {
throw new IllegalArgumentException("UID " + callingUid + " does own package"
+ "name " + pkgName);
}
} else {
// Enforce actor requirements for enabling, disabling, and reordering overlays.
realUserId = handleIncomingUser(request.userId, request.typeToString());
enforceActor(request.overlay, request.typeToString(), realUserId);
}
final long ident = Binder.clearCallingIdentity();
try {
switch (request.type) {
case TYPE_SET_ENABLED:
Optional<PackageAndUser> opt1 =
mImpl.setEnabled(request.packageName, true, request.userId);
Optional<PackageAndUser> opt2 =
mImpl.setHighestPriority(request.packageName, request.userId);
// Both setEnabled and setHighestPriority affected the same
// target package and user: if both return non-empty
// Optionals, they are identical
return opt1.isPresent() ? opt1 : opt2;
Set<PackageAndUser> result = null;
result = CollectionUtils.addAll(result,
mImpl.setEnabled(request.overlay, true, realUserId));
result = CollectionUtils.addAll(result,
mImpl.setHighestPriority(request.overlay, realUserId));
return CollectionUtils.emptyIfNull(result);
case TYPE_SET_DISABLED:
return mImpl.setEnabled(request.packageName, false, request.userId);
return mImpl.setEnabled(request.overlay, false, realUserId);
case TYPE_REGISTER_FABRICATED:
final FabricatedOverlayInternal fabricated =
request.extras.getParcelable(
OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
);
Objects.requireNonNull(fabricated,
"no fabricated overlay attached to request");
return mImpl.registerFabricatedOverlay(fabricated);
case TYPE_UNREGISTER_FABRICATED:
return mImpl.unregisterFabricatedOverlay(request.overlay);
default:
throw new IllegalArgumentException("unsupported request: " + request);
}
@@ -921,7 +937,7 @@ public final class OverlayManagerService extends SystemService {
}
private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
throws Exception {
throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "commit " + transaction);
}
@@ -931,25 +947,25 @@ public final class OverlayManagerService extends SystemService {
// map: userId -> set<package-name>: target packages of overlays in
// this transaction
SparseArray<Set<String>> transactionTargets = new SparseArray<>();
final SparseArray<Set<String>> transactionTargets = new SparseArray<>();
// map: userId -> set<package-name>: packages that need to reload
// their resources due to changes to the overlays in this
// transaction
SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
final SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
synchronized (mLock) {
// execute the requests (as calling user)
for (final OverlayManagerTransaction.Request request : transaction) {
executeRequest(request).ifPresent(target -> {
Set<String> userTargets = transactionTargets.get(target.userId);
if (userTargets == null) {
userTargets = new ArraySet<String>();
transactionTargets.put(target.userId, userTargets);
}
userTargets.add(target.packageName);
});
executeRequest(request).forEach(
target -> {
Set<String> userTargets = transactionTargets.get(target.userId);
if (userTargets == null) {
userTargets = new ArraySet<>();
transactionTargets.put(target.userId, userTargets);
}
userTargets.add(target.packageName);
});
}
// past the point of no return: the entire transaction has been
@@ -975,10 +991,7 @@ public final class OverlayManagerService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
// schedule apps to refresh
for (int index = 0; index < affectedPackagesToUpdate.size(); index++) {
final int userId = affectedPackagesToUpdate.keyAt(index);
updateActivityManager(affectedPackagesToUpdate.valueAt(index), userId);
}
updateActivityManager(affectedPackagesToUpdate);
// broadcast the ACTION_OVERLAY_CHANGED intents
for (int index = 0; index < transactionTargets.size(); index++) {
@@ -1059,12 +1072,12 @@ public final class OverlayManagerService extends SystemService {
dumpState.setField(arg);
break;
default:
dumpState.setPackageName(arg);
dumpState.setOverlyIdentifier(arg);
break;
}
}
if (dumpState.getPackageName() == null && opti < args.length) {
dumpState.setPackageName(args[opti]);
dumpState.setOverlyIdentifier(args[opti]);
opti++;
}
@@ -1101,12 +1114,12 @@ public final class OverlayManagerService extends SystemService {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
}
private void enforceActor(String packageName, String methodName, int realUserId)
throws SecurityException {
OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, realUserId);
private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
int realUserId) throws SecurityException {
OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
if (overlayInfo == null) {
throw new IllegalArgumentException("Unable to retrieve overlay information for "
+ packageName);
+ overlay);
}
int callingUid = Binder.getCallingUid();
@@ -1115,7 +1128,13 @@ public final class OverlayManagerService extends SystemService {
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
private static class AndroidPackageUsers {
private AndroidPackage mPackage;
private final Set<Integer> mInstalledUsers = new ArraySet<>();
private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
this.mPackage = pkg;
}
}
private final Context mContext;
private final IPackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -1125,7 +1144,8 @@ public final class OverlayManagerService extends SystemService {
// intent, querying the PackageManagerService for the actual current
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1133,29 +1153,112 @@ public final class OverlayManagerService extends SystemService {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
final boolean useCache) {
if (useCache) {
final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
if (cachedPi != null) {
return cachedPi;
/**
* Initializes the helper for the user. This only needs to be invoked one time before
* packages of this user are queried.
* @param userId the user id to initialize
* @return a map of package name to all packages installed in the user
*/
@NonNull
public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
if (!mInitializedUsers.contains(userId)) {
mInitializedUsers.add(userId);
mPackageManagerInternal.forEachInstalledPackage(
(pkg) -> addPackageUser(pkg, userId), userId);
}
final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
for (int i = 0, n = mCache.size(); i < n; i++) {
final AndroidPackageUsers pkg = mCache.valueAt(i);
if (pkg.mInstalledUsers.contains(userId)) {
userPackages.put(mCache.keyAt(i), pkg.mPackage);
}
}
try {
final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
if (useCache && pi != null) {
cachePackageInfo(packageName, userId, pi);
}
return pi;
} catch (RemoteException e) {
// Intentionally left empty.
}
return null;
return userPackages;
}
@Override
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
return getPackageInfo(packageName, userId, true);
@Nullable
public AndroidPackage getPackageForUser(@NonNull final String packageName,
final int userId) {
final AndroidPackageUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
return pkg.mPackage;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
return null;
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to check availability of package '" + packageName
+ "' for user " + userId, e);
return null;
}
return addPackageUser(packageName, userId);
}
@NonNull
private AndroidPackage addPackageUser(@NonNull final String packageName,
final int user) {
final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
if (pkg == null) {
Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
+ " continuing as if package was never added", new Throwable());
return null;
}
return addPackageUser(pkg, user);
}
@NonNull
private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
final int user) {
AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new AndroidPackageUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
} else {
pkgUsers.mPackage = pkg;
}
pkgUsers.mInstalledUsers.add(user);
return pkgUsers.mPackage;
}
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final AndroidPackageUsers pkgUsers = mCache.get(packageName);
if (pkgUsers == null) {
return;
}
removePackageUser(pkgUsers, user);
}
@NonNull
private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
pkg.mInstalledUsers.remove(user);
if (pkg.mInstalledUsers.isEmpty()) {
mCache.remove(pkg.mPackage.getPackageName());
}
}
@Nullable
public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
return addPackageUser(packageName, userId);
}
@Nullable
public AndroidPackage onPackageUpdated(@NonNull final String packageName,
final int userId) {
return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
removePackageUser(packageName, userId);
}
@Override
public boolean isInstantApp(@NonNull final String packageName, final int userId) {
return mPackageManagerInternal.isInstantApp(packageName, userId);
}
@NonNull
@@ -1178,15 +1281,6 @@ public final class OverlayManagerService extends SystemService {
return false;
}
@Override
public List<PackageInfo> getOverlayPackages(final int userId) {
final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
for (final PackageInfo info : overlays) {
cachePackageInfo(info.packageName, userId, info);
}
return overlays;
}
@Override
public String getConfigSignaturePackage() {
final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
@@ -1200,16 +1294,14 @@ public final class OverlayManagerService extends SystemService {
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
PackageInfo packageInfo = getPackageInfo(packageName, userId);
final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(baseCodePath);
apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
@@ -1224,16 +1316,14 @@ public final class OverlayManagerService extends SystemService {
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
PackageInfo packageInfo = getPackageInfo(targetPackageName, userId);
AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(baseCodePath);
apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.definesOverlayable();
} finally {
if (apkAssets != null) {
@@ -1250,35 +1340,10 @@ public final class OverlayManagerService extends SystemService {
mContext.enforceCallingOrSelfPermission(permission, message);
}
public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
final int userId) {
final HashMap<String, PackageInfo> map = mCache.get(userId);
return map == null ? null : map.get(packageName);
}
public void cachePackageInfo(@NonNull final String packageName, final int userId,
@NonNull final PackageInfo pi) {
HashMap<String, PackageInfo> map = mCache.get(userId);
if (map == null) {
map = new HashMap<>();
mCache.put(userId, map);
}
map.put(packageName, pi);
}
public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
final HashMap<String, PackageInfo> map = mCache.get(userId);
if (map == null) {
return;
}
map.remove(packageName);
if (map.isEmpty()) {
mCache.delete(userId);
}
}
public void forgetAllPackageInfos(final int userId) {
mCache.delete(userId);
for (int i = 0, n = mCache.size(); i < n; i++) {
removePackageUser(mCache.valueAt(i), userId);
}
}
@Nullable
@@ -1292,19 +1357,12 @@ public final class OverlayManagerService extends SystemService {
}
private static final String TAB1 = " ";
private static final String TAB2 = TAB1 + TAB1;
public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
pw.println("PackageInfo cache");
pw.println("AndroidPackage cache");
if (!dumpState.isVerbose()) {
int count = 0;
final int n = mCache.size();
for (int i = 0; i < n; i++) {
final int userId = mCache.keyAt(i);
count += mCache.get(userId).size();
}
pw.println(TAB1 + count + " package(s)");
pw.println(TAB1 + mCache.size() + " package(s)");
return;
}
@@ -1313,25 +1371,70 @@ public final class OverlayManagerService extends SystemService {
return;
}
final int n = mCache.size();
for (int i = 0; i < n; i++) {
final int userId = mCache.keyAt(i);
pw.println(TAB1 + "User " + userId);
final HashMap<String, PackageInfo> map = mCache.get(userId);
for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
}
for (int i = 0, n = mCache.size(); i < n; i++) {
final String packageName = mCache.keyAt(i);
final AndroidPackageUsers pkg = mCache.valueAt(i);
pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
}
}
}
private void updateTargetPackages(@Nullable PackageAndUser updatedTarget) {
if (updatedTarget != null) {
updateTargetPackages(Set.of(updatedTarget));
}
}
private void updateTargetPackages(@Nullable Set<PackageAndUser> updatedTargets) {
if (CollectionUtils.isEmpty(updatedTargets)) {
return;
}
persistSettings();
final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(updatedTargets);
FgThread.getHandler().post(() -> {
for (int i = 0, n = userTargets.size(); i < n; i++) {
final ArraySet<String> targets = userTargets.valueAt(i);
final int userId = userTargets.keyAt(i);
// Update the overlay paths in package manager.
final List<String> affectedPackages = updatePackageManager(targets, userId);
updateActivityManager(affectedPackages, userId);
// Overlays targeting shared libraries may cause more packages to need to be
// refreshed.
broadcastActionOverlayChanged(targets, userId);
}
});
}
@Nullable
private static SparseArray<ArraySet<String>> groupTargetsByUserId(
@Nullable final Set<PackageAndUser> targetsAndUsers) {
final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
CollectionUtils.forEach(targetsAndUsers, target -> {
ArraySet<String> targets = userTargets.get(target.userId);
if (targets == null) {
targets = new ArraySet<>();
userTargets.put(target.userId, targets);
}
targets.add(target.packageName);
});
return userTargets;
}
// Helper methods to update other parts of the system or read/write
// settings: these methods should never call into each other!
private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
CollectionUtils.forEach(targetPackages,
target -> broadcastActionOverlayChanged(target, userId));
}
private static void broadcastActionOverlayChanged(String targetPackage, final int userId) {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", targetPackageName, null));
Uri.fromParts("package", targetPackage, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
@@ -1345,7 +1448,7 @@ public final class OverlayManagerService extends SystemService {
* Tell the activity manager to tell a set of packages to reload their
* resources.
*/
private void updateActivityManager(List<String> targetPackageNames, final int userId) {
private void updateActivityManager(@NonNull List<String> targetPackageNames, final int userId) {
final IActivityManager am = ActivityManager.getService();
try {
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
@@ -1354,16 +1457,33 @@ public final class OverlayManagerService extends SystemService {
}
}
private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) {
return updatePackageManager(Collections.singletonList(targetPackageNames), userId);
private void updateActivityManager(@NonNull SparseArray<List<String>> targetPackageNames) {
for (int i = 0, n = targetPackageNames.size(); i < n; i++) {
updateActivityManager(targetPackageNames.valueAt(i), targetPackageNames.keyAt(i));
}
}
@NonNull
private SparseArray<List<String>> updatePackageManager(@Nullable Set<PackageAndUser> targets) {
if (CollectionUtils.isEmpty(targets)) {
return new SparseArray<>();
}
final SparseArray<List<String>> affectedTargets = new SparseArray<>();
final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(targets);
for (int i = 0, n = userTargets.size(); i < n; i++) {
final int userId = userTargets.keyAt(i);
affectedTargets.put(userId, updatePackageManager(userTargets.valueAt(i), userId));
}
return affectedTargets;
}
/**
* Updates the target packages' set of enabled overlays in PackageManager.
* @return the package names of affected targets (a superset of
* targetPackageNames: the target themserlves and shared libraries)
* targetPackageNames: the target themselves and shared libraries)
*/
private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
@NonNull
private List<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames);

View File

@@ -21,31 +21,32 @@ import static com.android.server.om.OverlayManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
@@ -68,34 +69,45 @@ final class OverlayManagerSettings {
*/
private final ArrayList<SettingsItem> mItems = new ArrayList<>();
void init(@NonNull final String packageName, final int userId,
@NonNull
OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
@NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
@Nullable String overlayCategory) {
remove(packageName, userId);
insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
overlayCategory));
@Nullable String overlayCategory, boolean isFabricated) {
remove(overlay, userId);
final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName,
targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled,
isMutable, priority, overlayCategory, isFabricated);
insert(item);
return item.getOverlayInfo();
}
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean remove(@NonNull final String packageName, final int userId) {
final int idx = select(packageName, userId);
boolean remove(@NonNull final OverlayIdentifier overlay, final int userId) {
final int idx = select(overlay, userId);
if (idx < 0) {
return false;
}
mItems.remove(idx);
return true;
}
@NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
@NonNull OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId)
throws BadKeyException {
final int idx = select(packageName, userId);
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).getOverlayInfo();
}
@Nullable
OverlayInfo getNullableOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) {
final int idx = select(overlay, userId);
if (idx < 0) {
return null;
}
return mItems.get(idx).getOverlayInfo();
}
@@ -103,28 +115,29 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setBaseCodePath(@NonNull final String packageName, final int userId,
boolean setBaseCodePath(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String path) throws BadKeyException {
final int idx = select(packageName, userId);
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setBaseCodePath(path);
}
boolean setCategory(@NonNull final String packageName, final int userId,
boolean setCategory(@NonNull final OverlayIdentifier overlay, final int userId,
@Nullable String category) throws BadKeyException {
final int idx = select(packageName, userId);
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setCategory(category);
}
boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
final int idx = select(packageName, userId);
boolean getEnabled(@NonNull final OverlayIdentifier overlay, final int userId)
throws BadKeyException {
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).isEnabled();
}
@@ -132,20 +145,20 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
throws BadKeyException {
final int idx = select(packageName, userId);
boolean setEnabled(@NonNull final OverlayIdentifier overlay, final int userId,
final boolean enable) throws BadKeyException {
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setEnabled(enable);
}
@OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
@OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId)
throws BadKeyException {
final int idx = select(packageName, userId);
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).getState();
}
@@ -153,11 +166,11 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setState(@NonNull final String packageName, final int userId,
boolean setState(@NonNull final OverlayIdentifier overlay, final int userId,
final @OverlayInfo.State int state) throws BadKeyException {
final int idx = select(packageName, userId);
final int idx = select(overlay, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setState(state);
}
@@ -166,53 +179,82 @@ final class OverlayManagerSettings {
final int userId) {
// Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
// ignored in OverlayManagerService.
return selectWhereTarget(targetPackageName, userId)
.filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.toList());
final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
// Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
// ignored in OverlayManagerService.
return selectWhereUser(userId)
.filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
Collectors.toList()));
final List<SettingsItem> items = selectWhereUser(userId);
items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
final ArrayMap<String, List<OverlayInfo>> targetInfos = new ArrayMap<>();
for (int i = 0, n = items.size(); i < n; i++) {
final SettingsItem item = items.get(i);
targetInfos.computeIfAbsent(item.mTargetPackageName, (String) -> new ArrayList<>())
.add(item.getOverlayInfo());
}
return targetInfos;
}
Set<String> getAllBaseCodePaths() {
final Set<String> paths = new ArraySet<>();
mItems.forEach(item -> paths.add(item.mBaseCodePath));
return paths;
}
@NonNull
List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
return removeIf(info -> (predicate.test(info) && info.userId == userId));
}
@NonNull
List<OverlayInfo> removeIf(final @NonNull Predicate<OverlayInfo> predicate) {
List<OverlayInfo> removed = null;
for (int i = mItems.size() - 1; i >= 0; i--) {
final OverlayInfo info = mItems.get(i).getOverlayInfo();
if (predicate.test(info)) {
mItems.remove(i);
removed = CollectionUtils.add(removed, info);
}
}
return CollectionUtils.emptyIfNull(removed);
}
int[] getUsers() {
return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray();
}
private static boolean isImmutableFrameworkOverlay(@NonNull SettingsItem item) {
return !item.isMutable() && "android".equals(item.getTargetPackageName());
}
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean removeUser(final int userId) {
boolean removed = false;
for (int i = 0; i < mItems.size(); i++) {
final SettingsItem item = mItems.get(i);
return mItems.removeIf(item -> {
if (item.getUserId() == userId) {
if (DEBUG) {
Slog.d(TAG, "Removing overlay " + item.mPackageName + " for user " + userId
Slog.d(TAG, "Removing overlay " + item.mOverlay + " for user " + userId
+ " from settings because user was removed");
}
mItems.remove(i);
removed = true;
i--;
return true;
}
}
return removed;
return false;
});
}
/**
* Reassigns the priority of an overlay maintaining the values of the overlays other settings.
*/
void setPriority(@NonNull final String packageName, final int userId, final int priority) {
final int moveIdx = select(packageName, userId);
void setPriority(@NonNull final OverlayIdentifier overlay, final int userId,
final int priority) throws BadKeyException {
final int moveIdx = select(overlay, userId);
if (moveIdx < 0) {
throw new BadKeyException(packageName, userId);
throw new BadKeyException(overlay, userId);
}
final SettingsItem itemToMove = mItems.get(moveIdx);
@@ -224,17 +266,17 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setPriority(@NonNull final String packageName,
@NonNull final String newParentPackageName, final int userId) {
if (packageName.equals(newParentPackageName)) {
boolean setPriority(@NonNull final OverlayIdentifier overlay,
@NonNull final OverlayIdentifier newOverlay, final int userId) {
if (overlay.equals(newOverlay)) {
return false;
}
final int moveIdx = select(packageName, userId);
final int moveIdx = select(overlay, userId);
if (moveIdx < 0) {
return false;
}
final int parentIdx = select(newParentPackageName, userId);
final int parentIdx = select(newOverlay, userId);
if (parentIdx < 0) {
return false;
}
@@ -248,7 +290,7 @@ final class OverlayManagerSettings {
}
mItems.remove(moveIdx);
final int newParentIdx = select(newParentPackageName, userId) + 1;
final int newParentIdx = select(newOverlay, userId) + 1;
mItems.add(newParentIdx, itemToMove);
return moveIdx != newParentIdx;
}
@@ -256,8 +298,8 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setLowestPriority(@NonNull final String packageName, final int userId) {
final int idx = select(packageName, userId);
boolean setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
final int idx = select(overlay, userId);
if (idx <= 0) {
// If the item doesn't exist or is already the lowest, don't change anything.
return false;
@@ -272,8 +314,8 @@ final class OverlayManagerSettings {
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean setHighestPriority(@NonNull final String packageName, final int userId) {
final int idx = select(packageName, userId);
boolean setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
final int idx = select(overlay, userId);
// If the item doesn't exist or is already the highest, don't change anything.
if (idx < 0 || idx == mItems.size() - 1) {
@@ -297,7 +339,6 @@ final class OverlayManagerSettings {
break;
}
}
mItems.add(i + 1, item);
}
@@ -308,7 +349,12 @@ final class OverlayManagerSettings {
items = items.filter(item -> item.mUserId == dumpState.getUserId());
}
if (dumpState.getPackageName() != null) {
items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName()));
items = items.filter(item -> item.mOverlay.getPackageName()
.equals(dumpState.getPackageName()));
}
if (dumpState.getOverlayName() != null) {
items = items.filter(item -> item.mOverlay.getOverlayName()
.equals(dumpState.getOverlayName()));
}
// display items
@@ -322,10 +368,11 @@ final class OverlayManagerSettings {
private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw,
@NonNull final SettingsItem item) {
pw.println(item.mPackageName + ":" + item.getUserId() + " {");
pw.println(item.mOverlay + ":" + item.getUserId() + " {");
pw.increaseIndent();
pw.println("mPackageName...........: " + item.mPackageName);
pw.println("mPackageName...........: " + item.mOverlay.getPackageName());
pw.println("mOverlayName...........: " + item.mOverlay.getOverlayName());
pw.println("mUserId................: " + item.getUserId());
pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
@@ -335,6 +382,7 @@ final class OverlayManagerSettings {
pw.println("mIsMutable.............: " + item.isMutable());
pw.println("mPriority..............: " + item.mPriority);
pw.println("mCategory..............: " + item.mCategory);
pw.println("mIsFabricated..........: " + item.mIsFabricated);
pw.decreaseIndent();
pw.println("}");
@@ -344,7 +392,10 @@ final class OverlayManagerSettings {
@NonNull final SettingsItem item, @NonNull final String field) {
switch (field) {
case "packagename":
pw.println(item.mPackageName);
pw.println(item.mOverlay.getPackageName());
break;
case "overlayname":
pw.println(item.mOverlay.getOverlayName());
break;
case "userid":
pw.println(item.mUserId);
@@ -392,6 +443,7 @@ final class OverlayManagerSettings {
private static final String ATTR_BASE_CODE_PATH = "baseCodePath";
private static final String ATTR_IS_ENABLED = "isEnabled";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_OVERLAY_NAME = "overlayName";
private static final String ATTR_STATE = "state";
private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName";
@@ -400,30 +452,26 @@ final class OverlayManagerSettings {
private static final String ATTR_CATEGORY = "category";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_VERSION = "version";
private static final String ATTR_IS_FABRICATED = "fabricated";
@VisibleForTesting
static final int CURRENT_VERSION = 4;
public static void restore(@NonNull final ArrayList<SettingsItem> table,
@NonNull final InputStream is) throws IOException, XmlPullParserException {
table.clear();
final TypedXmlPullParser parser = Xml.resolvePullParser(is);
XmlUtils.beginDocument(parser, TAG_OVERLAYS);
final int version = parser.getAttributeInt(null, ATTR_VERSION);
if (version != CURRENT_VERSION) {
upgrade(version);
}
{
table.clear();
final TypedXmlPullParser parser = Xml.resolvePullParser(is);
XmlUtils.beginDocument(parser, TAG_OVERLAYS);
int version = parser.getAttributeInt(null, ATTR_VERSION);
if (version != CURRENT_VERSION) {
upgrade(version);
}
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
switch (parser.getName()) {
case TAG_ITEM:
final SettingsItem item = restoreRow(parser, depth + 1);
table.add(item);
break;
}
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_ITEM.equals(parser.getName())) {
final SettingsItem item = restoreRow(parser, depth + 1);
table.add(item);
}
}
}
@@ -447,7 +495,9 @@ final class OverlayManagerSettings {
private static SettingsItem restoreRow(@NonNull final TypedXmlPullParser parser,
final int depth) throws IOException, XmlPullParserException {
final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME);
final OverlayIdentifier overlay = new OverlayIdentifier(
XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME),
XmlUtils.readStringAttribute(parser, ATTR_OVERLAY_NAME));
final int userId = parser.getAttributeInt(null, ATTR_USER_ID);
final String targetPackageName = XmlUtils.readStringAttribute(parser,
ATTR_TARGET_PACKAGE_NAME);
@@ -459,9 +509,11 @@ final class OverlayManagerSettings {
final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false);
final int priority = parser.getAttributeInt(null, ATTR_PRIORITY);
final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED,
false);
return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
baseCodePath, state, isEnabled, !isStatic, priority, category);
return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName,
baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated);
}
public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -484,7 +536,8 @@ final class OverlayManagerSettings {
private static void persistRow(@NonNull final TypedXmlSerializer xml,
@NonNull final SettingsItem item) throws IOException {
xml.startTag(null, TAG_ITEM);
XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName);
XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mOverlay.getPackageName());
XmlUtils.writeStringAttribute(xml, ATTR_OVERLAY_NAME, item.mOverlay.getOverlayName());
xml.attributeInt(null, ATTR_USER_ID, item.mUserId);
XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName);
XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME,
@@ -495,13 +548,14 @@ final class OverlayManagerSettings {
XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
xml.attributeInt(null, ATTR_PRIORITY, item.mPriority);
XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated);
xml.endTag(null, TAG_ITEM);
}
}
private static final class SettingsItem {
private final int mUserId;
private final String mPackageName;
private final OverlayIdentifier mOverlay;
private final String mTargetPackageName;
private final String mTargetOverlayableName;
private String mBaseCodePath;
@@ -511,13 +565,15 @@ final class OverlayManagerSettings {
private boolean mIsMutable;
private int mPriority;
private String mCategory;
private boolean mIsFabricated;
SettingsItem(@NonNull final String packageName, final int userId,
SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String targetPackageName,
@Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
final @OverlayInfo.State int state, final boolean isEnabled,
final boolean isMutable, final int priority, @Nullable String category) {
mPackageName = packageName;
final boolean isMutable, final int priority, @Nullable String category,
final boolean isFabricated) {
mOverlay = overlay;
mUserId = userId;
mTargetPackageName = targetPackageName;
mTargetOverlayableName = targetOverlayableName;
@@ -528,6 +584,7 @@ final class OverlayManagerSettings {
mCache = null;
mIsMutable = isMutable;
mPriority = priority;
mIsFabricated = isFabricated;
}
private String getTargetPackageName() {
@@ -596,8 +653,9 @@ final class OverlayManagerSettings {
private OverlayInfo getOverlayInfo() {
if (mCache == null) {
mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsMutable);
mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(),
mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath,
mState, mUserId, mPriority, mIsMutable, mIsFabricated);
}
return mCache;
}
@@ -620,30 +678,40 @@ final class OverlayManagerSettings {
}
}
private int select(@NonNull final String packageName, final int userId) {
private int select(@NonNull final OverlayIdentifier overlay, final int userId) {
final int n = mItems.size();
for (int i = 0; i < n; i++) {
final SettingsItem item = mItems.get(i);
if (item.mUserId == userId && item.mPackageName.equals(packageName)) {
if (item.mUserId == userId && item.mOverlay.equals(overlay)) {
return i;
}
}
return -1;
}
private Stream<SettingsItem> selectWhereUser(final int userId) {
return mItems.stream().filter(item -> item.mUserId == userId);
private List<SettingsItem> selectWhereUser(final int userId) {
final List<SettingsItem> selectedItems = new ArrayList<>();
CollectionUtils.addIf(mItems, selectedItems, i -> i.mUserId == userId);
return selectedItems;
}
private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
private List<SettingsItem> selectWhereOverlay(@NonNull final String packageName,
final int userId) {
return selectWhereUser(userId)
.filter(item -> item.getTargetPackageName().equals(targetPackageName));
final List<SettingsItem> items = selectWhereUser(userId);
items.removeIf(i -> !i.mOverlay.getPackageName().equals(packageName));
return items;
}
static final class BadKeyException extends RuntimeException {
BadKeyException(@NonNull final String packageName, final int userId) {
super("Bad key mPackageName=" + packageName + " mUserId=" + userId);
private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
items.removeIf(i -> !i.getTargetPackageName().equals(targetPackageName));
return items;
}
static final class BadKeyException extends Exception {
BadKeyException(@NonNull final OverlayIdentifier overlay, final int userId) {
super("Bad key '" + overlay + "' for user " + userId );
}
}
}

View File

@@ -19,20 +19,28 @@ package com.android.server.om;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.om.FabricatedOverlay;
import android.content.om.IOverlayManager;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.util.TypedValue;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -72,6 +80,8 @@ final class OverlayManagerShellCommand extends ShellCommand {
return runSetPriority();
case "lookup":
return runLookup();
case "fabricate":
return runFabricate();
default:
return handleDefaultCommands(cmd);
}
@@ -89,35 +99,36 @@ final class OverlayManagerShellCommand extends ShellCommand {
out.println("Overlay manager (overlay) commands:");
out.println(" help");
out.println(" Print this help text.");
out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
out.println(" Print debugging information about the overlay manager.");
out.println(" With optional parameter PACKAGE, limit output to the specified");
out.println(" package. With optional parameter FIELD, limit output to");
out.println(" With optional parameters PACKAGE and NAME, limit output to the specified");
out.println(" overlay or target. With optional parameter FIELD, limit output to");
out.println(" the value of that SettingsItem field. Field names are");
out.println(" case insensitive and out.println the m prefix can be omitted,");
out.println(" so the following are equivalent: mState, mstate, State, state.");
out.println(" list [--user USER_ID] [PACKAGE]");
out.println(" list [--user USER_ID] [PACKAGE[:NAME]]");
out.println(" Print information about target and overlay packages.");
out.println(" Overlay packages are printed in priority order. With optional");
out.println(" parameter PACKAGE, limit output to the specified package.");
out.println(" enable [--user USER_ID] PACKAGE");
out.println(" Enable overlay package PACKAGE.");
out.println(" disable [--user USER_ID] PACKAGE");
out.println(" Disable overlay package PACKAGE.");
out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE");
out.println(" Enable overlay package PACKAGE and disable all other overlays for");
out.println(" its target package. If the --category option is given, only disables");
out.println(" other overlays in the same category.");
out.println(" parameters PACKAGE and NAME, limit output to the specified overlay or");
out.println(" target.");
out.println(" enable [--user USER_ID] PACKAGE[:NAME]");
out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME.");
out.println(" disable [--user USER_ID] PACKAGE[:NAME]");
out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME.");
out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE[:NAME]");
out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME and");
out.println(" disable all other overlays for its target package. If the --category");
out.println(" option is given, only disables other overlays in the same category.");
out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
out.println(" Change the priority of the overlay PACKAGE to be just higher than");
out.println(" the priority of PACKAGE_PARENT If PARENT is the special keyword");
out.println(" Change the priority of the overlay to be just higher than");
out.println(" the priority of PARENT If PARENT is the special keyword");
out.println(" 'lowest', change priority of PACKAGE to the lowest priority.");
out.println(" If PARENT is the special keyword 'highest', change priority of");
out.println(" PACKAGE to the highest priority.");
out.println(" lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
out.println(" Load a package and print the value of a given resource");
out.println(" applying the current configuration and enabled overlays.");
out.println(" For a more fine-grained alernative, use 'idmap2 lookup'.");
out.println(" For a more fine-grained alternative, use 'idmap2 lookup'.");
}
private int runList() throws RemoteException {
@@ -192,7 +203,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
status = "---";
break;
}
out.println(String.format("%s %s", status, oi.packageName));
out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
}
private int runEnableDisable(final boolean enable) throws RemoteException {
@@ -211,8 +222,88 @@ final class OverlayManagerShellCommand extends ShellCommand {
}
}
final String packageName = getNextArgRequired();
return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
mInterface.commit(new OverlayManagerTransaction.Builder()
.setEnabled(overlay, enable, userId)
.build());
return 0;
}
private int runFabricate() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
if (Binder.getCallingUid() != Process.ROOT_UID) {
err.println("Error: must be root to fabricate overlays through the shell");
return 1;
}
int userId = UserHandle.USER_SYSTEM;
String targetPackage = "";
String targetOverlayable = "";
String name = "";
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--target":
targetPackage = getNextArgRequired();
break;
case "--target-name":
targetOverlayable = getNextArgRequired();
break;
case "--name":
name = getNextArgRequired();
break;
default:
err.println("Error: Unknown option: " + opt);
return 1;
}
}
if (name.isEmpty()) {
err.println("Error: Missing required arg '--name'");
return 1;
}
if (targetPackage.isEmpty()) {
err.println("Error: Missing required arg '--target'");
return 1;
}
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
final int type;
if (typeStr.startsWith("0x")) {
type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
} else {
type = Integer.parseUnsignedInt(typeStr);
}
final String dataStr = getNextArgRequired();
final int data;
if (dataStr.startsWith("0x")) {
data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
} else {
data = Integer.parseUnsignedInt(dataStr);
}
final PackageManager pm = mContext.getPackageManager();
if (pm == null) {
err.println("Error: failed to get package manager");
return 1;
}
final String overlayPackageName = "com.android.shell";
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
overlayPackageName, name, targetPackage)
.setTargetOverlayable(targetOverlayable)
.setResourceValue(resourceName, type, data)
.build();
mInterface.commit(new OverlayManagerTransaction.Builder()
.registerFabricatedOverlay(overlay)
.build());
return 0;
}
private int runEnableExclusive() throws RemoteException {
@@ -234,12 +325,27 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 1;
}
}
final String overlay = getNextArgRequired();
if (inCategory) {
return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
} else {
return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId);
if (overlayInfo == null) {
err.println("Error: Unable to get overlay info of: " + overlay);
return 1;
}
final List<OverlayInfo> overlaysForTarget =
mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
for (final OverlayInfo disableOverlay : overlaysForTarget) {
if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
|| !disableOverlay.isMutable) {
continue;
}
builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId);
}
builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId);
mInterface.commit(builder.build());
return 0;
}
private int runSetPriority() throws RemoteException {

View File

@@ -22,10 +22,15 @@ import android.content.om.OverlayableInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +41,33 @@ import java.util.Map;
* @hide
*/
interface PackageManagerHelper {
/**
* Initializes the helper for the user. This only needs to be invoked one time before
* packages of this user are queried.
* @param userId the user id to initialize
* @return a map of package name to all packages installed in the user
*/
@NonNull
ArrayMap<String, AndroidPackage> initializeForUser(final int userId);
/**
* Retrieves the package information if it is installed for the user.
*/
@Nullable
AndroidPackage getPackageForUser(@NonNull final String packageName, final int userId);
/**
* Returns whether the package is an instant app.
*/
boolean isInstantApp(@NonNull final String packageName, final int userId);
/**
* @see PackageManager#getPackagesForUid(int)
*/
@Nullable
String[] getPackagesForUid(int uid);
/**
* @return true if the target package has declared an overlayable
*/
@@ -63,11 +95,6 @@ interface PackageManagerHelper {
@NonNull
Map<String, Map<String, String>> getNamedActors();
/**
* @see PackageManagerInternal#getOverlayPackages(int)
*/
List<PackageInfo> getOverlayPackages(int userId);
/**
* Read from the APK and AndroidManifest of a package to return the overlayable defined for
* a given name.
@@ -79,19 +106,6 @@ interface PackageManagerHelper {
@NonNull String targetOverlayableName, int userId)
throws IOException;
/**
* @see PackageManager#getPackagesForUid(int)
*/
@Nullable
String[] getPackagesForUid(int uid);
/**
* @param userId user to filter package visibility by
* @see PackageManager#getPackageInfo(String, int)
*/
@Nullable
PackageInfo getPackageInfo(@NonNull String packageName, int userId);
/**
* @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
* in the system returns {@link PackageManager#SIGNATURE_MATCH}

View File

@@ -21,7 +21,9 @@ import android.content.om.OverlayableInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.os.Process
import android.util.ArrayMap
import com.android.server.om.OverlayActorEnforcer.ActorState
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
@@ -29,6 +31,7 @@ import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito
import org.mockito.Mockito.spy
import java.io.IOException
@@ -125,11 +128,11 @@ class OverlayActorEnforcerTests {
ActorState.TARGET_NOT_FOUND withCases {
failure("nullPkgInfo") { targetPkgInfo = null }
allowed("debuggable") {
targetPkgInfo = pkgInfo(TARGET_PKG).apply {
applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE
targetPkgInfo = androidPackage(TARGET_PKG).apply {
whenever(this.isDebuggable).thenReturn(true)
}
}
skip { targetPkgInfo = pkgInfo(TARGET_PKG) }
skip { targetPkgInfo = androidPackage(TARGET_PKG) }
},
ActorState.NO_PACKAGES_FOR_UID withCases {
failure("empty") { callingUid = EMPTY_UID }
@@ -236,22 +239,20 @@ class OverlayActorEnforcerTests {
mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG))
}
},
ActorState.MISSING_APP_INFO withCases {
ActorState.ACTOR_NOT_FOUND withCases {
failure("nullActorPkgInfo") { actorPkgInfo = null }
failure("nullActorAppInfo") {
actorPkgInfo = PackageInfo().apply { applicationInfo = null }
actorPkgInfo = null
}
skip { actorPkgInfo = pkgInfo(VALID_ACTOR_PKG) }
skip { actorPkgInfo = androidPackage(VALID_ACTOR_PKG) }
},
ActorState.ACTOR_NOT_PREINSTALLED withCases {
failure("notSystem") {
actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
applicationInfo.flags = 0
}
actorPkgInfo = androidPackage(VALID_ACTOR_PKG)
}
skip {
actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM
actorPkgInfo = androidPackage(VALID_ACTOR_PKG).apply {
whenever(this.isSystem).thenReturn(true)
}
}
},
@@ -272,22 +273,22 @@ class OverlayActorEnforcerTests {
) {
fun toOverlayInfo() = OverlayInfo(
OVERLAY_PKG,
"",
targetPackageName,
targetOverlayableName,
null,
"/path",
OverlayInfo.STATE_UNKNOWN, 0,
0, false)
0, false, false)
}
private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
TestCase(this).apply(block)
private fun pkgInfo(pkgName: String): PackageInfo = mockThrowOnUnmocked {
this.packageName = pkgName
this.applicationInfo = ApplicationInfo().apply {
this.packageName = pkgName
}
private fun androidPackage(pkgName: String): AndroidPackage = mockThrowOnUnmocked {
whenever(this.packageName).thenReturn(pkgName)
whenever(this.isDebuggable).thenReturn(false)
whenever(this.isSystem).thenReturn(false)
}
private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
@@ -363,8 +364,8 @@ class OverlayActorEnforcerTests {
var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
var hasPermission: Boolean = false,
var targetOverlayableInfo: OverlayableInfo? = null,
var targetPkgInfo: PackageInfo? = null,
var actorPkgInfo: PackageInfo? = null,
var targetPkgInfo: AndroidPackage? = null,
var actorPkgInfo: AndroidPackage? = null,
vararg val packageNames: String = arrayOf("com.test.actor.one")
) : PackageManagerHelper {
@@ -375,6 +376,14 @@ class OverlayActorEnforcerTests {
override fun getNamedActors() = namedActorsMap
override fun isInstantApp(packageName: String, userId: Int): Boolean {
throw UnsupportedOperationException()
}
override fun initializeForUser(userId: Int): ArrayMap<String, AndroidPackage> {
throw UnsupportedOperationException()
}
@Throws(IOException::class)
override fun getOverlayableForTarget(
packageName: String,
@@ -394,9 +403,6 @@ class OverlayActorEnforcerTests {
else -> null
}
override fun getPackageInfo(packageName: String, userId: Int) =
listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
@Throws(IOException::class) // Mockito requires this checked exception to be declared
override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
return targetOverlayableInfo?.takeIf {
@@ -411,11 +417,10 @@ class OverlayActorEnforcerTests {
}
}
override fun getConfigSignaturePackage(): String {
throw UnsupportedOperationException()
}
override fun getPackageForUser(packageName: String, userId: Int) =
listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
override fun getOverlayPackages(userId: Int): MutableList<PackageInfo> {
override fun getConfigSignaturePackage(): String {
throw UnsupportedOperationException()
}

View File

@@ -21,7 +21,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
@@ -29,77 +31,66 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceImplTestsBase {
private static final String OVERLAY = "com.test.overlay";
private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
private static final String TARGET = "com.test.target";
private static final int USER = 0;
private static final String OVERLAY2 = OVERLAY + "2";
private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
@Test
public void testUpdateOverlaysForUser() {
final OverlayManagerServiceImpl impl = getImpl();
final String otherTarget = "some.other.target";
addPackage(target(TARGET), USER);
addPackage(target("some.other.target"), USER);
addPackage(target(otherTarget), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
// do nothing, expect no change
final List<String> a = impl.updateOverlaysForUser(USER);
assertEquals(1, a.size());
assertTrue(a.contains(TARGET));
final ArraySet<PackageAndUser> a = impl.updateOverlaysForUser(USER);
assertEquals(3, a.size());
assertTrue(a.containsAll(Arrays.asList(
new PackageAndUser(TARGET, USER),
new PackageAndUser(otherTarget, USER),
new PackageAndUser(OVERLAY, USER))));
// upgrade overlay, keep target
addPackage(overlay(OVERLAY, TARGET), USER);
final List<String> b = impl.updateOverlaysForUser(USER);
assertEquals(1, b.size());
assertTrue(b.contains(TARGET));
// do nothing, expect no change
final List<String> c = impl.updateOverlaysForUser(USER);
assertEquals(1, c.size());
assertTrue(c.contains(TARGET));
// upgrade overlay, switch to new target
addPackage(overlay(OVERLAY, "some.other.target"), USER);
final List<String> d = impl.updateOverlaysForUser(USER);
assertEquals(2, d.size());
assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
// do nothing, expect no change
final List<String> f = impl.updateOverlaysForUser(USER);
assertEquals(1, f.size());
assertTrue(f.contains("some.other.target"));
final ArraySet<PackageAndUser> b = impl.updateOverlaysForUser(USER);
assertEquals(3, b.size());
assertTrue(b.containsAll(Arrays.asList(
new PackageAndUser(TARGET, USER),
new PackageAndUser(otherTarget, USER),
new PackageAndUser(OVERLAY, USER))));
}
@Test
public void testImmutableEnabledChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertFalse(o1.isMutable);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o2);
assertTrue(o2.isEnabled());
assertFalse(o2.isMutable);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertFalse(o3.isEnabled());
assertFalse(o3.isMutable);
@@ -108,26 +99,26 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutableEnabledChangeHasNoEffect() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertTrue(o1.isMutable);
configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o2);
assertFalse(o2.isEnabled());
assertTrue(o2.isMutable);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertFalse(o3.isEnabled());
assertTrue(o3.isMutable);
@@ -136,13 +127,13 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutableEnabledToImmutableEnabled() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o);
assertEquals(enabled, o.isEnabled());
assertEquals(mutable, o.isMutable);
@@ -180,38 +171,38 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutablePriorityChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertFalse(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
assertFalse(o2.isEnabled());
// Overlay priority changing between reboots should not affect enable state of mutable
// overlays.
impl.setEnabled(OVERLAY, true, USER);
impl.setEnabled(IDENTIFIER, true, USER);
// Reorder the overlays
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertFalse(o4.isEnabled());
@@ -220,19 +211,19 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testImmutablePriorityChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertTrue(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
assertTrue(o2.isEnabled());
@@ -242,12 +233,12 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertTrue(o4.isEnabled());

View File

@@ -28,6 +28,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.util.Pair;
@@ -39,19 +40,23 @@ import org.junit.runner.RunWith;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
private static final String OVERLAY = "com.test.overlay";
private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
private static final String TARGET = "com.test.target";
private static final int USER = 0;
private static final String OVERLAY2 = OVERLAY + "2";
private static final String TARGET2 = TARGET + "2";
private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
private static final int USER2 = USER + 1;
private static final String OVERLAY3 = OVERLAY + "3";
private static final OverlayIdentifier IDENTIFIER3 = new OverlayIdentifier(OVERLAY3);
private static final int USER3 = USER2 + 1;
private static final String CONFIG_SIGNATURE_REFERENCE_PKG = "com.test.ref";
@@ -60,10 +65,10 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testGetOverlayInfo() throws Exception {
installNewPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(oi);
assertEquals(oi.packageName, OVERLAY);
assertEquals(oi.targetPackageName, TARGET);
@@ -72,19 +77,19 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testGetOverlayInfosForTarget() throws Exception {
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET), USER2);
installPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY2, TARGET), USER);
installPackage(overlay(OVERLAY3, TARGET), USER2);
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
assertEquals(ois.size(), 2);
assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
final List<OverlayInfo> ois2 = impl.getOverlayInfosForTarget(TARGET, USER2);
assertEquals(ois2.size(), 1);
assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER2)));
assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER2)));
final List<OverlayInfo> ois3 = impl.getOverlayInfosForTarget(TARGET, USER3);
assertNotNull(ois3);
@@ -97,10 +102,10 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testGetOverlayInfosForUser() throws Exception {
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET2), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY2, TARGET), USER);
installPackage(overlay(OVERLAY3, TARGET2), USER);
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -109,13 +114,13 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
final List<OverlayInfo> ois = everything.get(TARGET);
assertNotNull(ois);
assertEquals(ois.size(), 2);
assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
final List<OverlayInfo> ois2 = everything.get(TARGET2);
assertNotNull(ois2);
assertEquals(ois2.size(), 1);
assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER)));
assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER)));
final Map<String, List<OverlayInfo>> everything2 = impl.getOverlaysForUser(USER2);
assertNotNull(everything2);
@@ -124,26 +129,26 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testPriority() throws Exception {
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
installPackage(overlay(OVERLAY2, TARGET), USER);
installPackage(overlay(OVERLAY3, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER3, USER);
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertEquals(impl.setLowestPriority(OVERLAY3, USER),
assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
assertEquals(impl.setHighestPriority(OVERLAY3, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
Set.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER),
assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@@ -151,61 +156,63 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testOverlayInfoStateTransitions() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
assertNull(impl.getOverlayInfo(OVERLAY, USER));
assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
installNewPackage(overlay(OVERLAY, TARGET), USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
installPackage(overlay(OVERLAY, TARGET), USER);
assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
final FakeDeviceState.PackageBuilder target = target(TARGET);
installNewPackage(target, USER);
assertState(STATE_DISABLED, OVERLAY, USER);
installPackage(target, USER);
assertState(STATE_DISABLED, IDENTIFIER, USER);
assertEquals(impl.setEnabled(OVERLAY, true, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertState(STATE_ENABLED, OVERLAY, USER);
assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
Set.of(new PackageAndUser(TARGET, USER)));
assertState(STATE_ENABLED, IDENTIFIER, USER);
// target upgrades do not change the state of the overlay
upgradePackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
assertState(STATE_ENABLED, IDENTIFIER, USER);
uninstallPackage(TARGET, USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
installNewPackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
installPackage(target, USER);
assertState(STATE_ENABLED, IDENTIFIER, USER);
}
@Test
public void testOnOverlayPackageUpgraded() throws Exception {
final FakeDeviceState.PackageBuilder target = target(TARGET);
final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
installNewPackage(target, USER);
installNewPackage(overlay, USER);
installPackage(target, USER);
installPackage(overlay, USER);
upgradePackage(overlay, USER);
// upgrade to a version where the overlay has changed its target
final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair =
upgradePackage(overlay2, USER);
assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER)));
assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER)));
final Pair<Set<PackageAndUser>, Set<PackageAndUser>> pair = upgradePackage(overlay2, USER);
assertEquals(pair.first, Set.of(new PackageAndUser(TARGET, USER)));
assertEquals(
Set.of(new PackageAndUser(TARGET, USER),
new PackageAndUser("some.other.target", USER)),
pair.second);
}
@Test
public void testSetEnabledAtVariousConditions() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
() -> impl.setEnabled(OVERLAY, true, USER));
() -> impl.setEnabled(IDENTIFIER, true, USER));
// request succeeded, and there was a change that needs to be
// propagated to the rest of the system
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
assertEquals(impl.setEnabled(OVERLAY, true, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET), USER);
assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
Set.of(new PackageAndUser(TARGET, USER)));
// request succeeded, but nothing changed
assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent());
assertTrue(impl.setEnabled(IDENTIFIER, true, USER).isEmpty());
}
@Test
@@ -214,8 +221,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
reinitializeImpl();
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -232,8 +239,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
reinitializeImpl();
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -247,8 +254,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testConfigSignaturePolicyNoConfig() throws Exception {
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -261,8 +268,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
@Test
public void testConfigSignaturePolicyNoRefPkg() throws Exception {
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -279,8 +286,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
reinitializeImpl();
addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
installPackage(target(TARGET), USER);
installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();

View File

@@ -24,11 +24,12 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayInfo.State;
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,17 +38,19 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.CollectionUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import org.junit.Assert;
import org.junit.Before;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.Set;
/** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
class OverlayManagerServiceImplTestsBase {
@@ -94,12 +97,11 @@ class OverlayManagerServiceImplTestsBase {
mConfigSignaturePackageName = packageName;
}
void assertState(@State int expected, final String overlayPackageName, int userId) {
final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
void assertState(@State int expected, final OverlayIdentifier overlay, int userId) {
final OverlayInfo info = mImpl.getOverlayInfo(overlay, userId);
if (info == null) {
throw new IllegalStateException("package not installed");
throw new IllegalStateException("overlay '" + overlay + "' not installed");
}
final String msg = String.format("expected %s but was %s:",
OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
assertEquals(msg, expected, info.state);
@@ -152,17 +154,13 @@ class OverlayManagerServiceImplTestsBase {
*
* @throws IllegalStateException if the package is currently installed
*/
void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId)
Set<PackageAndUser> installPackage(FakeDeviceState.PackageBuilder pkg, int userId)
throws OperationFailedException {
if (mState.select(pkg.packageName, userId) != null) {
throw new IllegalStateException("package " + pkg.packageName + " already installed");
}
mState.add(pkg, userId);
if (pkg.targetPackage == null) {
mImpl.onTargetPackageAdded(pkg.packageName, userId);
} else {
mImpl.onOverlayPackageAdded(pkg.packageName, userId);
}
return CollectionUtils.emptyIfNull(mImpl.onPackageAdded(pkg.packageName, userId));
}
/**
@@ -178,26 +176,21 @@ class OverlayManagerServiceImplTestsBase {
*
* @throws IllegalStateException if the package is not currently installed
*/
Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage(
Pair<Set<PackageAndUser>, Set<PackageAndUser>> upgradePackage(
FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException {
final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
if (replacedPackage == null) {
throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
Optional<PackageAndUser> opt1 = Optional.empty();
if (replacedPackage.targetPackageName != null) {
opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
}
final Set<PackageAndUser> updatedPackages1 =
CollectionUtils.emptyIfNull(mImpl.onPackageReplacing(pkg.packageName, userId));
mState.add(pkg, userId);
Optional<PackageAndUser> opt2;
if (pkg.targetPackage == null) {
opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId);
} else {
opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
}
final Set<PackageAndUser> updatedPackages2 =
CollectionUtils.emptyIfNull(mImpl.onPackageReplaced(pkg.packageName, userId));
return Pair.create(opt1, opt2);
return Pair.create(updatedPackages1, updatedPackages2);
}
/**
@@ -208,17 +201,13 @@ class OverlayManagerServiceImplTestsBase {
*
* @throws IllegalStateException if the package is not currently installed
*/
void uninstallPackage(String packageName, int userId) throws OperationFailedException {
Set<PackageAndUser> uninstallPackage(String packageName, int userId) {
final FakeDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null) {
throw new IllegalStateException("package " + packageName+ " not installed");
}
mState.remove(pkg.packageName);
if (pkg.targetPackageName == null) {
mImpl.onTargetPackageRemoved(pkg.packageName, userId);
} else {
mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
}
return CollectionUtils.emptyIfNull(mImpl.onPackageRemoved(packageName, userId));
}
/** Represents the state of packages installed on a fake device. */
@@ -247,11 +236,6 @@ class OverlayManagerServiceImplTestsBase {
}
}
List<Package> select(int userId) {
return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
.collect(Collectors.toList());
}
Package select(String packageName, int userId) {
final Package pkg = mPackages.get(packageName);
return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
@@ -335,6 +319,21 @@ class OverlayManagerServiceImplTestsBase {
this.apkPath = apkPath;
this.certificate = certificate;
}
@Nullable
private AndroidPackage getPackageForUser(int user) {
if (!installedUserIds.contains(user)) {
return null;
}
final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
when(pkg.getPackageName()).thenReturn(packageName);
when(pkg.getBaseApkPath()).thenReturn(apkPath);
when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
when(pkg.getOverlayTargetName()).thenReturn(targetOverlayableName);
when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
return pkg;
}
}
}
@@ -345,21 +344,29 @@ class OverlayManagerServiceImplTestsBase {
mState = state;
}
@NonNull
@Override
public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
final FakeDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null) {
return null;
}
final ApplicationInfo ai = new ApplicationInfo();
ai.sourceDir = pkg.apkPath;
PackageInfo pi = new PackageInfo();
pi.applicationInfo = ai;
pi.packageName = pkg.packageName;
pi.overlayTarget = pkg.targetPackageName;
pi.targetOverlayableName = pkg.targetOverlayableName;
pi.overlayCategory = "Fake-category-" + pkg.targetPackageName;
return pi;
public ArrayMap<String, AndroidPackage> initializeForUser(int userId) {
final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
mState.mPackages.forEach((key, value) -> {
final AndroidPackage pkg = value.getPackageForUser(userId);
if (pkg != null) {
packages.put(key, pkg);
}
});
return packages;
}
@Nullable
@Override
public AndroidPackage getPackageForUser(@NonNull String packageName, int userId) {
final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
return pkgState == null ? null : pkgState.getPackageForUser(userId);
}
@Override
public boolean isInstantApp(@NonNull String packageName, int userId) {
return false;
}
@Override
@@ -370,14 +377,6 @@ class OverlayManagerServiceImplTestsBase {
return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate);
}
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
return mState.select(userId).stream()
.filter(p -> p.targetPackageName != null)
.map(p -> getPackageInfo(p.packageName, userId))
.collect(Collectors.toList());
}
@Override
public @NonNull String getConfigSignaturePackage() {
return mConfigSignaturePackageName;
@@ -421,6 +420,9 @@ class OverlayManagerServiceImplTestsBase {
static class FakeIdmapDaemon extends IdmapDaemon {
private final FakeDeviceState mState;
private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
new ArrayMap<>();
private int mFabricatedAssetSeq = 0;
FakeIdmapDaemon(FakeDeviceState state) {
this.mState = state;
@@ -433,10 +435,10 @@ class OverlayManagerServiceImplTestsBase {
}
@Override
String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId) {
String createIdmap(String targetPath, String overlayPath, String overlayName,
int policies, boolean enforce, int userId) {
mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
getCrc(overlayPath), targetPath, policies, enforce));
getCrc(overlayPath), targetPath, overlayName, policies, enforce));
return overlayPath;
}
@@ -446,8 +448,8 @@ class OverlayManagerServiceImplTestsBase {
}
@Override
boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId) {
boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
boolean enforce, int userId) {
final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
if (idmap == null) {
return false;
@@ -461,6 +463,29 @@ class OverlayManagerServiceImplTestsBase {
return mIdmapFiles.containsKey(overlayPath);
}
@Override
FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
final String path = Integer.toString(mFabricatedAssetSeq++);
final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
info.path = path;
info.overlayName = overlay.overlayName;
info.packageName = overlay.packageName;
info.targetPackageName = overlay.targetPackageName;
info.targetOverlayable = overlay.targetOverlayable;
mFabricatedOverlays.put(path, info);
return info;
}
@Override
boolean deleteFabricatedOverlay(@NonNull String path) {
return mFabricatedOverlays.remove(path) != null;
}
@Override
List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
return new ArrayList<>(mFabricatedOverlays.values());
}
IdmapHeader getIdmap(String overlayPath) {
return mIdmapFiles.get(overlayPath);
}
@@ -469,14 +494,16 @@ class OverlayManagerServiceImplTestsBase {
private final int targetCrc;
private final int overlayCrc;
final String targetPath;
final String overlayName;
final int policies;
final boolean enforceOverlayable;
private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
boolean enforceOverlayable) {
private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
String overlayName, int policies, boolean enforceOverlayable) {
this.targetCrc = targetCrc;
this.overlayCrc = overlayCrc;
this.targetPath = targetPath;
this.overlayName = overlayName;
this.policies = policies;
this.enforceOverlayable = enforceOverlayable;
}

View File

@@ -19,17 +19,20 @@ package com.android.server.om;
import static android.content.om.OverlayInfo.STATE_DISABLED;
import static android.content.om.OverlayInfo.STATE_ENABLED;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.text.TextUtils;
import android.util.TypedXmlPullParser;
import android.util.Xml;
import androidx.annotation.NonNull;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -43,67 +46,32 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerSettingsTests {
private OverlayManagerSettings mSettings;
private static int USER_0 = 0;
private static int USER_1 = 1;
private static final OverlayInfo OVERLAY_A0 = new OverlayInfo(
"com.test.overlay_a",
"com.test.target",
null,
"some-category",
"/data/app/com.test.overlay_a-1/base.apk",
STATE_DISABLED,
0,
0,
true);
private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
null /* overlayName */);
private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b",
null /* overlayName */);
private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c",
null /* overlayName */);
private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
"com.test.overlay_b",
"com.test.target",
null,
"some-category",
"/data/app/com.test.overlay_b-1/base.apk",
STATE_DISABLED,
0,
0,
true);
private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0);
private static final OverlayInfo OVERLAY_B_USER0 = createInfo(OVERLAY_B, USER_0);
private static final OverlayInfo OVERLAY_C_USER0 = createInfo(OVERLAY_C, USER_0);
private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
"com.test.overlay_c",
"com.test.target",
null,
"some-category",
"/data/app/com.test.overlay_c-1/base.apk",
STATE_DISABLED,
0,
0,
true);
private static final OverlayInfo OVERLAY_A_USER1 = createInfo(OVERLAY_A, USER_1);
private static final OverlayInfo OVERLAY_B_USER1 = createInfo(OVERLAY_B, USER_1);
private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
"com.test.overlay_a",
"com.test.target",
null,
"some-category",
"/data/app/com.test.overlay_a-1/base.apk",
STATE_DISABLED,
1,
0,
true);
private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
"com.test.overlay_b",
"com.test.target",
null,
"some-category",
"/data/app/com.test.overlay_b-1/base.apk",
STATE_DISABLED,
1,
0,
true);
private static final String TARGET_PACKAGE = "com.test.target";
@Before
public void setUp() throws Exception {
@@ -114,124 +82,112 @@ public class OverlayManagerSettingsTests {
@Test
public void testSettingsInitiallyEmpty() throws Exception {
final int userId = 0;
Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(userId);
final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(0 /* userId */);
assertEquals(0, map.size());
}
@Test
public void testBasicSetAndGet() throws Exception {
assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
assertDoesNotContain(mSettings, OVERLAY_A_USER0);
insert(OVERLAY_A0);
assertContains(mSettings, OVERLAY_A0);
OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
assertEquals(OVERLAY_A0, oi);
insertSetting(OVERLAY_A_USER0);
assertContains(mSettings, OVERLAY_A_USER0);
final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A, USER_0);
assertEquals(OVERLAY_A_USER0, oi);
assertTrue(mSettings.remove(OVERLAY_A0.packageName, OVERLAY_A0.userId));
assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
assertTrue(mSettings.remove(OVERLAY_A, USER_0));
assertDoesNotContain(mSettings, OVERLAY_A, USER_0);
}
@Test
public void testGetUsers() throws Exception {
int[] users = mSettings.getUsers();
assertEquals(0, users.length);
assertArrayEquals(new int[]{}, mSettings.getUsers());
insert(OVERLAY_A0);
users = mSettings.getUsers();
assertEquals(1, users.length);
assertContains(users, OVERLAY_A0.userId);
insertSetting(OVERLAY_A_USER0);
assertArrayEquals(new int[]{USER_0}, mSettings.getUsers());
insert(OVERLAY_A1);
insert(OVERLAY_B1);
users = mSettings.getUsers();
assertEquals(2, users.length);
assertContains(users, OVERLAY_A0.userId);
assertContains(users, OVERLAY_A1.userId);
insertSetting(OVERLAY_A_USER1);
insertSetting(OVERLAY_B_USER1);
assertArrayEquals(new int[]{USER_0, USER_1}, mSettings.getUsers());
}
@Test
public void testGetOverlaysForUser() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_A1);
insert(OVERLAY_B1);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_A_USER1);
insertSetting(OVERLAY_B_USER0);
Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(OVERLAY_A0.userId);
assertEquals(1, map.keySet().size());
assertTrue(map.keySet().contains(OVERLAY_A0.targetPackageName));
final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(USER_0);
assertEquals(Set.of(TARGET_PACKAGE), map.keySet());
List<OverlayInfo> list = map.get(OVERLAY_A0.targetPackageName);
assertEquals(2, list.size());
assertTrue(list.contains(OVERLAY_A0));
assertTrue(list.contains(OVERLAY_B0));
// Two overlays in user 0 target the same package
final List<OverlayInfo> list = map.get(TARGET_PACKAGE);
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), list);
// getOverlaysForUser should never return null
map = mSettings.getOverlaysForUser(-1);
assertNotNull(map);
assertEquals(0, map.size());
// No users installed for user 3
assertEquals(Map.<String, List<OverlayInfo>>of(), mSettings.getOverlaysForUser(3));
}
@Test
public void testRemoveUser() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_A1);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_A_USER1);
assertContains(mSettings, OVERLAY_A0);
assertContains(mSettings, OVERLAY_B0);
assertContains(mSettings, OVERLAY_A1);
assertContains(mSettings, OVERLAY_A_USER0);
assertContains(mSettings, OVERLAY_B_USER0);
assertContains(mSettings, OVERLAY_A_USER1);
mSettings.removeUser(OVERLAY_A0.userId);
mSettings.removeUser(USER_0);
assertDoesNotContain(mSettings, OVERLAY_A0);
assertDoesNotContain(mSettings, OVERLAY_B0);
assertContains(mSettings, OVERLAY_A1);
assertDoesNotContain(mSettings, OVERLAY_A_USER0);
assertDoesNotContain(mSettings, OVERLAY_B_USER0);
assertContains(mSettings, OVERLAY_A_USER1);
}
@Test
public void testOrderOfNewlyAddedItems() throws Exception {
// new items are appended to the list
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_C0);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_C_USER0);
List<OverlayInfo> list =
mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
// overlays keep their positions when updated
mSettings.setState(OVERLAY_B0.packageName, OVERLAY_B0.userId, STATE_ENABLED);
OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B0.packageName, OVERLAY_B0.userId);
mSettings.setState(OVERLAY_B, USER_0, STATE_ENABLED);
final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B, USER_0);
assertNotNull(oi);
list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, oi, OVERLAY_C0);
assertListsAreEqual(List.of(OVERLAY_A_USER0, oi, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
}
@Test
public void testSetPriority() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_C0);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_C_USER0);
List<OverlayInfo> list =
mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
boolean changed = mSettings.setPriority(OVERLAY_B0.packageName, OVERLAY_C0.packageName,
OVERLAY_B0.userId);
assertTrue(changed);
list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
assertTrue(mSettings.setPriority(OVERLAY_B, OVERLAY_C, USER_0));
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
changed =
mSettings.setPriority(OVERLAY_B0.packageName, "does.not.exist", OVERLAY_B0.userId);
assertFalse(changed);
list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
// Nothing happens if the parent package cannot be found
assertFalse(mSettings.setPriority(OVERLAY_B, new OverlayIdentifier("does.not.exist"),
USER_0));
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
OverlayInfo otherTarget = new OverlayInfo(
// An overlay should not affect the priority of overlays targeting a different package
final OverlayInfo otherTarget = new OverlayInfo(
"com.test.overlay_other",
null,
"com.test.some.other.target",
null,
"some-category",
@@ -239,45 +195,36 @@ public class OverlayManagerSettingsTests {
STATE_DISABLED,
0,
0,
true);
insert(otherTarget);
changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
OVERLAY_A0.userId);
assertFalse(changed);
true,
false);
insertSetting(otherTarget);
assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
}
@Test
public void testSetLowestPriority() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_C0);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_C_USER0);
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
List<OverlayInfo> list =
mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
boolean changed = mSettings.setLowestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
assertTrue(changed);
list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_B0, OVERLAY_A0, OVERLAY_C0);
assertTrue(mSettings.setLowestPriority(OVERLAY_B, USER_0));
assertListsAreEqual(List.of(OVERLAY_B_USER0, OVERLAY_A_USER0, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
}
@Test
public void testSetHighestPriority() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insert(OVERLAY_C0);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
insertSetting(OVERLAY_C_USER0);
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
List<OverlayInfo> list =
mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
boolean changed = mSettings.setHighestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
assertTrue(changed);
list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
assertTrue(mSettings.setHighestPriority(OVERLAY_B, USER_0));
assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0));
}
// tests: persist and restore
@@ -294,8 +241,8 @@ public class OverlayManagerSettingsTests {
@Test
public void testPersistDifferentOverlaysSameUser() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B0);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER0);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -304,17 +251,17 @@ public class OverlayManagerSettingsTests {
assertEquals(1, countXmlTags(xml, "overlays"));
assertEquals(2, countXmlTags(xml, "item"));
assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
OVERLAY_A0.packageName));
OVERLAY_A.getPackageName()));
assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
OVERLAY_B0.packageName));
OVERLAY_B.getPackageName()));
assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
Integer.toString(OVERLAY_A0.userId)));
Integer.toString(USER_0)));
}
@Test
public void testPersistSameOverlayDifferentUsers() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_A1);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_A_USER1);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -323,17 +270,17 @@ public class OverlayManagerSettingsTests {
assertEquals(1, countXmlTags(xml, "overlays"));
assertEquals(2, countXmlTags(xml, "item"));
assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
OVERLAY_A0.packageName));
OVERLAY_A.getPackageName()));
assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
Integer.toString(OVERLAY_A0.userId)));
Integer.toString(USER_0)));
assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
Integer.toString(OVERLAY_A1.userId)));
Integer.toString(USER_1)));
}
@Test
public void testPersistEnabled() throws Exception {
insert(OVERLAY_A0);
mSettings.setEnabled(OVERLAY_A0.packageName, OVERLAY_A0.userId, true);
insertSetting(OVERLAY_A_USER0);
mSettings.setEnabled(OVERLAY_A, USER_0, true);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -351,7 +298,7 @@ public class OverlayManagerSettingsTests {
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
mSettings.restore(is);
assertDoesNotContain(mSettings, "com.test.overlay", 0);
assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0);
}
@Test
@@ -361,6 +308,7 @@ public class OverlayManagerSettingsTests {
"<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
+ "<overlays version='" + version + "'>\n"
+ "<item packageName='com.test.overlay'\n"
+ " overlayName='test'\n"
+ " userId='1234'\n"
+ " targetPackageName='com.test.target'\n"
+ " baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
@@ -373,20 +321,22 @@ public class OverlayManagerSettingsTests {
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
mSettings.restore(is);
OverlayInfo oi = mSettings.getOverlayInfo("com.test.overlay", 1234);
final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
assertNotNull(oi);
assertEquals("com.test.overlay", oi.packageName);
assertEquals("test", oi.overlayName);
assertEquals("com.test.target", oi.targetPackageName);
assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
assertEquals(1234, oi.userId);
assertEquals(STATE_DISABLED, oi.state);
assertFalse(mSettings.getEnabled("com.test.overlay", 1234));
assertFalse(mSettings.getEnabled(identifier, 1234));
}
@Test
public void testPersistAndRestore() throws Exception {
insert(OVERLAY_A0);
insert(OVERLAY_B1);
insertSetting(OVERLAY_A_USER0);
insertSetting(OVERLAY_B_USER1);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -394,11 +344,11 @@ public class OverlayManagerSettingsTests {
OverlayManagerSettings newSettings = new OverlayManagerSettings();
newSettings.restore(is);
OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
assertEquals(OVERLAY_A0, a);
OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0);
assertEquals(OVERLAY_A_USER0, a);
OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B1.packageName, OVERLAY_B1.userId);
assertEquals(OVERLAY_B1, b);
OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1);
assertEquals(OVERLAY_B_USER1, b);
}
private int countXmlTags(InputStream in, String tagToLookFor) throws Exception {
@@ -433,43 +383,53 @@ public class OverlayManagerSettingsTests {
return count;
}
private void insert(OverlayInfo oi) throws Exception {
mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
true, false,0, oi.category);
mSettings.setState(oi.packageName, oi.userId, oi.state);
mSettings.setEnabled(oi.packageName, oi.userId, false);
private void insertSetting(OverlayInfo oi) throws Exception {
mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
}
private static void assertContains(final OverlayManagerSettings settings,
final OverlayInfo oi) {
assertContains(settings, oi.packageName, oi.userId);
}
private static void assertContains(final OverlayManagerSettings settings,
final String packageName, int userId) {
try {
settings.getOverlayInfo(packageName, userId);
settings.getOverlayInfo(oi.getOverlayIdentifier(), oi.userId);
} catch (OverlayManagerSettings.BadKeyException e) {
fail(String.format("settings does not contain packageName=%s userId=%d",
packageName, userId));
fail(String.format("settings does not contain overlay=%s userId=%d",
oi.getOverlayIdentifier(), oi.userId));
}
}
private static void assertDoesNotContain(final OverlayManagerSettings settings,
final OverlayInfo oi) {
assertDoesNotContain(settings, oi.packageName, oi.userId);
assertDoesNotContain(settings, oi.getOverlayIdentifier(), oi.userId);
}
private static void assertDoesNotContain(final OverlayManagerSettings settings,
final String packageName, int userId) {
final OverlayIdentifier overlay, int userId) {
try {
settings.getOverlayInfo(packageName, userId);
fail(String.format("settings contains packageName=%s userId=%d", packageName, userId));
settings.getOverlayInfo(overlay, userId);
fail(String.format("settings contains overlay=%s userId=%d", overlay, userId));
} catch (OverlayManagerSettings.BadKeyException e) {
// do nothing: we expect to end up here
}
}
private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) {
return new OverlayInfo(
identifier.getPackageName(),
identifier.getOverlayName(),
"com.test.target",
null,
"some-category",
"/data/app/" + identifier + "/base.apk",
STATE_DISABLED,
userId,
0,
true,
false);
}
private static void assertContains(int[] haystack, int needle) {
List<Integer> list = IntStream.of(haystack)
.boxed()
@@ -490,16 +450,11 @@ public class OverlayManagerSettingsTests {
}
}
private static void assertListsAreEqual(List<OverlayInfo> list, OverlayInfo... array) {
List<OverlayInfo> other = Stream.of(array)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
assertListsAreEqual(list, other);
}
private static void assertListsAreEqual(List<OverlayInfo> list, List<OverlayInfo> other) {
if (!list.equals(other)) {
private static void assertListsAreEqual(
@NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) {
if (!expected.equals(actual)) {
fail(String.format("lists [%s] and [%s] differ",
TextUtils.join(",", list), TextUtils.join(",", other)));
TextUtils.join(",", expected), TextUtils.join(",", actual)));
}
}
}