diff options
author | 2025-01-31 13:09:44 +0000 | |
---|---|---|
committer | 2025-02-06 17:48:20 +0000 | |
commit | b1608a954f53515c90b500b93b5164b3365d6c55 (patch) | |
tree | 9191f1dc6805569345ebecd18ec3b2845faecb13 | |
parent | 3dcc45555519cc0a27c313bbb0fa251d7ae750ac (diff) |
Add constraints for enabling RROs
This change introduces hidden API's for setting constraints
for enabling RROs. Currently, the constraints are of the following
types:
1. Display id constraint: This can be set for a RRO if the RRO is
desired to be applied on only the apps running on that specific
display.
2. Device id constraint: This can be set for a RRO if the RRO is
desired to be applied on only the apps running on that specific
device (for apps running on the default device, this would be
Context#DEVICE_ID_DEFAULT, and for apps running on a virtual device,
this would be the id of the virtual device).
An overlay would be enabled when any of the given constraints are met.
Constraints can only be set while enabling a RRO. Re-enabling a RRO
with different constraints updates the constraints for the RRO.
This change facilitates the writing of the constraints for RROs
into the correspodning idmap files, and also persists them as part
of the settings used by OverlayManagerService. The filtering of
resource overlays based on constraints during resource resolution
will be done in a follow-up CL.
Test: atest FrameworksServicesTests
Test: atest idmap2_tests
Test: atest libandroidfw_tests
Test: atest CtsResourcesTestCases
Bug: 371801644
Flag: android.content.res.rro_constraints
Change-Id: I0cad58bfb5b9b90105e2ef839c58147b9a50767c
45 files changed, 1287 insertions, 223 deletions
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index d9ff19051de9..f6bee52661da 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -348,6 +348,7 @@ filegroup { "idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl", "idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl", "idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl", + "idmap2d/aidl/core/android/os/OverlayConstraint.aidl", ], path: "idmap2d/aidl/core/", } diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp index d5f1b895facf..d94940131c8b 100644 --- a/cmds/idmap2/idmap2/Create.cpp +++ b/cmds/idmap2/idmap2/Create.cpp @@ -35,6 +35,7 @@ using android::idmap2::BinaryStreamVisitor; using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::Idmap; +using android::idmap2::IdmapConstraints; using android::idmap2::OverlayResourceContainer; using android::idmap2::Result; using android::idmap2::TargetResourceContainer; @@ -104,8 +105,10 @@ Result<Unit> Create(const std::vector<std::string>& args) { return Error("failed to load apk overlay '%s'", overlay_apk_path.c_str()); } + // TODO(b/371801644): Add command-line support for RRO constraints. + auto constraints = std::make_unique<const IdmapConstraints>(); const auto idmap = Idmap::FromContainers(**target, **overlay, overlay_name, fulfilled_policies, - !ignore_overlayable); + !ignore_overlayable, std::move(constraints)); if (!idmap) { return Error(idmap.GetError(), "failed to create idmap"); } diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp index 2608c69be66f..70a2ed1940b2 100644 --- a/cmds/idmap2/idmap2/CreateMultiple.cpp +++ b/cmds/idmap2/idmap2/CreateMultiple.cpp @@ -39,6 +39,7 @@ using android::idmap2::BinaryStreamVisitor; using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::Idmap; +using android::idmap2::IdmapConstraints; using android::idmap2::OverlayResourceContainer; using android::idmap2::Result; using android::idmap2::TargetResourceContainer; @@ -115,8 +116,11 @@ Result<Unit> CreateMultiple(const std::vector<std::string>& args) { continue; } + // TODO(b/371801644): Add command-line support for RRO constraints. + auto constraints = std::make_unique<const IdmapConstraints>(); const auto idmap = - Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable); + Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable, + std::move(constraints)); if (!idmap) { LOG(WARNING) << "failed to create idmap"; continue; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 6902d6db6751..2495c55cc065 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -46,6 +46,8 @@ using android::binder::Status; using android::idmap2::BinaryStreamVisitor; using android::idmap2::FabricatedOverlayContainer; using android::idmap2::Idmap; +using android::idmap2::IdmapConstraint; +using android::idmap2::IdmapConstraints; using android::idmap2::IdmapHeader; using android::idmap2::OverlayResourceContainer; using android::idmap2::PrettyPrintVisitor; @@ -74,6 +76,18 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) { return static_cast<PolicyBitmask>(arg); } +std::unique_ptr<const IdmapConstraints> ConvertAidlConstraintsToIdmapConstraints( + const std::vector<android::os::OverlayConstraint>& constraints) { + auto idmapConstraints = std::make_unique<IdmapConstraints>(); + for (const auto& constraint : constraints) { + IdmapConstraint idmapConstraint{}; + idmapConstraint.constraint_type = constraint.type; + idmapConstraint.constraint_value = constraint.value; + idmapConstraints->constraints.insert(idmapConstraint); + } + return idmapConstraints; +} + } // namespace namespace android::os { @@ -113,6 +127,7 @@ Status Idmap2Service::removeIdmap(const std::string& overlay_path, int32_t user_ 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, + const std::vector<os::OverlayConstraint>& constraints, bool* _aidl_return) { SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path; assert(_aidl_return); @@ -120,12 +135,19 @@ Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::str 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); + const std::unique_ptr<const IdmapConstraints> oldConstraints = + IdmapConstraints::FromBinaryStream(fin); fin.close(); if (!header) { *_aidl_return = false; LOG(WARNING) << "failed to parse idmap header of '" << idmap_path << "'"; return ok(); } + if (!oldConstraints) { + *_aidl_return = false; + LOG(WARNING) << "failed to parse idmap constraints of '" << idmap_path << "'"; + return ok(); + } const auto target = GetTargetContainer(target_path); if (!target) { @@ -145,7 +167,10 @@ Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::str header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name, ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); - *_aidl_return = static_cast<bool>(up_to_date); + std::unique_ptr<const IdmapConstraints> newConstraints = + ConvertAidlConstraintsToIdmapConstraints(constraints); + + *_aidl_return = static_cast<bool>(up_to_date && (*oldConstraints == *newConstraints)); if (!up_to_date) { LOG(WARNING) << "idmap '" << idmap_path << "' not up to date : " << up_to_date.GetErrorMessage(); @@ -156,6 +181,7 @@ Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::str 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, + const std::vector<os::OverlayConstraint>& constraints, std::optional<std::string>* _aidl_return) { assert(_aidl_return); SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path; @@ -186,8 +212,11 @@ Status Idmap2Service::createIdmap(const std::string& target_path, const std::str return error("failed to load apk overlay '%s'" + overlay_path); } + std::unique_ptr<const IdmapConstraints> idmapConstraints = + ConvertAidlConstraintsToIdmapConstraints(constraints); const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name, - policy_bitmask, enforce_overlayable); + policy_bitmask, enforce_overlayable, + std::move(idmapConstraints)); if (!idmap) { return error(idmap.GetErrorMessage()); } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h index 272ec6be3bac..344a77f5581f 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.h +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -20,6 +20,7 @@ #include <android-base/unique_fd.h> #include <android/os/BnIdmap2.h> #include <android/os/FabricatedOverlayInfo.h> +#include <android/os/OverlayConstraint.h> #include <binder/BinderService.h> #include <idmap2/ResourceContainer.h> #include <idmap2/Result.h> @@ -49,11 +50,13 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 { 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, + const std::vector<os::OverlayConstraint>& constraints, bool* _aidl_return) override; 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, + const std::vector<os::OverlayConstraint>& constraints, std::optional<std::string>* _aidl_return) override; binder::Status createFabricatedOverlay( diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/OverlayConstraint.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/OverlayConstraint.aidl new file mode 100644 index 000000000000..8fce3d6567ab --- /dev/null +++ b/cmds/idmap2/idmap2d/aidl/core/android/os/OverlayConstraint.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 OverlayConstraint { + int type; + int value; +}
\ No newline at end of file diff --git a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl index 2bbfba97a6c6..4f4f075a0e63 100644 --- a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl +++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl @@ -18,6 +18,7 @@ package android.os; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; +import android.os.OverlayConstraint; /** * @hide @@ -30,13 +31,15 @@ interface IIdmap2 { @utf8InCpp String overlayName, int fulfilledPolicies, boolean enforceOverlayable, - int userId); + int userId, + in OverlayConstraint[] constraints); @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, @utf8InCpp String overlayName, int fulfilledPolicies, boolean enforceOverlayable, - int userId); + int userId, + in OverlayConstraint[] constraints); @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay); boolean deleteFabricatedOverlay(@utf8InCpp String path); diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h index 57af1b61c300..3009293bc4ab 100644 --- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -32,6 +32,7 @@ class BinaryStreamVisitor : public Visitor { ~BinaryStreamVisitor() override = default; void visit(const Idmap& idmap) override; void visit(const IdmapHeader& header) override; + void visit(const IdmapConstraints& constraints) override; void visit(const IdmapData& data) override; void visit(const IdmapData::Header& header) override; diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index b0ba01957ab6..1f15daf1ba47 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -17,10 +17,11 @@ /* * # idmap file format (current version) * - * idmap := header data* + * idmap := header constraints_count constraint* data* * header := magic version target_crc overlay_crc fulfilled_policies * enforce_overlayable target_path overlay_path overlay_name * debug_info + * constraints := constraint_type constraint_value * data := data_header target_entries target_inline_entries target_inline_entry_value* config* overlay_entries string_pool * data_header := target_entry_count target_inline_entry_count @@ -67,6 +68,9 @@ * value_type := <uint8_t> * value_data := <uint32_t> * version := <uint32_t> + * constraints_count := <uint32_t> + * constraint_type := <uint32_t> + * constraint_value := <uint32_t> */ #ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ @@ -76,6 +80,7 @@ #include <memory> #include <string> #include <string_view> +#include <unordered_set> #include <vector> #include "android-base/macros.h" @@ -171,6 +176,33 @@ class IdmapHeader { friend Idmap; DISALLOW_COPY_AND_ASSIGN(IdmapHeader); }; + +struct IdmapConstraint { + // Constraint type can be TYPE_DISPLAY_ID or TYP_DEVICE_ID, please refer + // to ConstraintType in OverlayConstraint.java + uint32_t constraint_type; + uint32_t constraint_value; + + bool operator==(const IdmapConstraint&) const = default; +}; + +struct IdmapConstraints { + static std::unique_ptr<const IdmapConstraints> FromBinaryStream(std::istream& stream); + + struct Hash { + static std::size_t operator()(const IdmapConstraint& constraint) { + return std::hash<int>()(constraint.constraint_type) * 31 + + std::hash<int>()(constraint.constraint_value); + } + }; + + bool operator == (const IdmapConstraints& constraints) const = default; + + void accept(Visitor* v) const; + + std::unordered_set<IdmapConstraint, Hash> constraints; +}; + class IdmapData { public: class Header { @@ -286,12 +318,16 @@ class Idmap { 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); + bool enforce_overlayable, std::unique_ptr<const IdmapConstraints>&& constraints); const std::unique_ptr<const IdmapHeader>& GetHeader() const { return header_; } + const std::unique_ptr<const IdmapConstraints>& GetConstraints() const { + return constraints_; + } + const std::vector<std::unique_ptr<const IdmapData>>& GetData() const { return data_; } @@ -302,6 +338,7 @@ class Idmap { Idmap() = default; std::unique_ptr<const IdmapHeader> header_; + std::unique_ptr<const IdmapConstraints> constraints_; std::vector<std::unique_ptr<const IdmapData>> data_; DISALLOW_COPY_AND_ASSIGN(Idmap); @@ -312,6 +349,7 @@ class Visitor { virtual ~Visitor() = default; virtual void visit(const Idmap& idmap) = 0; virtual void visit(const IdmapHeader& header) = 0; + virtual void visit(const IdmapConstraints& constraints) = 0; virtual void visit(const IdmapData& data) = 0; virtual void visit(const IdmapData::Header& header) = 0; }; diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h index ed18d9cbf20f..033ef85f5133 100644 --- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h +++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h @@ -36,6 +36,7 @@ class PrettyPrintVisitor : public Visitor { ~PrettyPrintVisitor() override = default; void visit(const Idmap& idmap) override; void visit(const IdmapHeader& header) override; + void visit(const IdmapConstraints& constraints) override; void visit(const IdmapData& data) override; void visit(const IdmapData::Header& header) override; diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h index 849ba11aacff..bd27c0d62c0d 100644 --- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h +++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h @@ -37,6 +37,7 @@ class RawPrintVisitor : public Visitor { ~RawPrintVisitor() override = default; void visit(const Idmap& idmap) override; void visit(const IdmapHeader& header) override; + void visit(const IdmapConstraints& constraints) override; void visit(const IdmapData& data) override; void visit(const IdmapData::Header& header) override; diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index 00ef0c7f8cf0..b029aea1a1bf 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -63,6 +63,14 @@ void BinaryStreamVisitor::visit(const IdmapHeader& header) { WriteString(header.GetDebugInfo()); } +void BinaryStreamVisitor::visit(const IdmapConstraints& constraints) { + Write32(static_cast<uint32_t>(constraints.constraints.size())); + for (const auto& constraint : constraints.constraints) { + Write32(constraint.constraint_type); + Write32(constraint.constraint_value); + } +} + void BinaryStreamVisitor::visit(const IdmapData& data) { for (const auto& target_entry : data.GetTargetEntries()) { Write32(target_entry.target_id); diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 7680109f1d54..556ca228e83d 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -182,6 +182,26 @@ Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path, return Unit{}; } +std::unique_ptr<const IdmapConstraints> IdmapConstraints::FromBinaryStream(std::istream& stream) { + auto idmap_constraints = std::make_unique<IdmapConstraints>(); + uint32_t count = 0; + if (!Read32(stream, &count)) { + return nullptr; + } + for (size_t i = 0; i < count; i++) { + IdmapConstraint constraint{}; + if (!Read32(stream, &constraint.constraint_type)) { + return nullptr; + } + if (!Read32(stream, &constraint.constraint_value)) { + return nullptr; + } + idmap_constraints->constraints.insert(constraint); + } + + return idmap_constraints; +} + std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) { std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header()); if (!Read32(stream, &idmap_data_header->target_entry_count) || @@ -315,6 +335,10 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromBinaryStream(std::istream& strea if (!idmap->header_) { return Error("failed to parse idmap header"); } + idmap->constraints_ = IdmapConstraints::FromBinaryStream(stream); + if (!idmap->constraints_) { + return Error("failed to parse idmap constraints"); + } // idmap version 0x01 does not specify the number of data blocks that follow // the idmap header; assume exactly one data block @@ -374,10 +398,9 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( } 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) { + const OverlayResourceContainer& overlay, const std::string& overlay_name, + const PolicyBitmask& fulfilled_policies, bool enforce_overlayable, + std::unique_ptr<const IdmapConstraints>&& constraints) { SYSTRACE << "Idmap::FromApkAssets"; std::unique_ptr<IdmapHeader> header(new IdmapHeader()); header->magic_ = kIdmapMagic; @@ -424,6 +447,11 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromContainers(const TargetResourceC header->debug_info_ = log_info.GetString(); idmap->header_ = std::move(header); idmap->data_.push_back(std::move(*idmap_data)); + if (constraints == nullptr) { + idmap->constraints_ = std::make_unique<IdmapConstraints>(); + } else { + idmap->constraints_ = std::move(constraints); + } return {std::move(idmap)}; } @@ -433,6 +461,11 @@ void IdmapHeader::accept(Visitor* v) const { v->visit(*this); } +void IdmapConstraints::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + void IdmapData::Header::accept(Visitor* v) const { assert(v != nullptr); v->visit(*this); @@ -447,6 +480,7 @@ void IdmapData::accept(Visitor* v) const { void Idmap::accept(Visitor* v) const { assert(v != nullptr); header_->accept(v); + constraints_->accept(v); v->visit(*this); auto end = data_.cend(); for (auto iter = data_.cbegin(); iter != end; ++iter) { diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp index eb9458268dad..0ec31f4f63f6 100644 --- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -58,6 +58,19 @@ void PrettyPrintVisitor::visit(const IdmapHeader& header) { if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) { overlay_ = std::move(*overlay); } +} + +void PrettyPrintVisitor::visit(const IdmapConstraints& constraints) { + stream_ << "Constraints:" << '\n'; + if (constraints.constraints.empty()) { + stream_ << TAB << "None\n"; + } else { + for (const IdmapConstraint& constraint : constraints.constraints) { + stream_ << TAB + << base::StringPrintf("Type: %d, Value: %d\n", constraint.constraint_type, + constraint.constraint_value); + } + } stream_ << "Mapping:" << '\n'; } diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index 9d04a7f87400..41a3da39d872 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -55,6 +55,14 @@ void RawPrintVisitor::visit(const IdmapHeader& header) { } } +void RawPrintVisitor::visit(const IdmapConstraints &idmapConstraints) { + print(static_cast<uint32_t>(idmapConstraints.constraints.size()), "constraints count"); + for (const auto& constraint : idmapConstraints.constraints) { + print(constraint.constraint_type, "constraint type"); + print(constraint.constraint_value, "constraint value"); + } +} + void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { for (auto& target_entry : data.GetTargetEntries()) { Result<std::string> target_name(Error("")); diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp index 7f9c4686c55a..26888ab17d66 100644 --- a/cmds/idmap2/self_targeting/SelfTargeting.cpp +++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp @@ -31,6 +31,7 @@ using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; using android::idmap2::BinaryStreamVisitor; using android::idmap2::Idmap; +using android::idmap2::IdmapConstraints; using android::idmap2::OverlayResourceContainer; namespace android::self_targeting { @@ -155,9 +156,10 @@ CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std:: // Overlay self target process. Only allow self-targeting types. const auto fulfilled_policies = GetFulfilledPolicy(isSystem, isVendor, isProduct, isTargetSignature, isOdm, isOem); - + auto constraints = std::make_unique<const IdmapConstraints>(); const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName, - fulfilled_policies, true /* enforce_overlayable */); + fulfilled_policies, true /* enforce_overlayable */, + std::move(constraints)); if (!idmap) { out_err = base::StringPrintf("Failed to create idmap because of %s", idmap.GetErrorMessage().c_str()); diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp index f1eeab9c803b..76cccb556ca2 100644 --- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -58,6 +58,8 @@ TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { ASSERT_EQ(idmap1->GetData().size(), 1U); ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size()); + ASSERT_EQ(idmap1->GetConstraints()->constraints, idmap2->GetConstraints()->constraints); + const std::vector<std::unique_ptr<const IdmapData>>& data_blocks1 = idmap1->GetData(); ASSERT_EQ(data_blocks1.size(), 1U); const std::unique_ptr<const IdmapData>& data1 = data_blocks1[0]; diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp index 5a7fcd519cfd..760bbb3f72ba 100644 --- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -105,6 +105,7 @@ TEST_F(Idmap2BinaryTests, Create) { fin.close(); ASSERT_TRUE(idmap); + ASSERT_EQ((*idmap)->GetConstraints()->constraints.size(), 0); ASSERT_IDMAP(**idmap, GetTargetApkPath(), GetOverlayApkPath()); unlink(GetIdmapPath().c_str()); diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 7093614f4047..4de2a6b7c125 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -68,7 +68,7 @@ TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); ASSERT_EQ(header->GetMagic(), 0x504d4449U); - ASSERT_EQ(header->GetVersion(), 10); + ASSERT_EQ(header->GetVersion(), 11); ASSERT_EQ(header->GetTargetCrc(), 0x1234U); ASSERT_EQ(header->GetOverlayCrc(), 0x5678U); ASSERT_EQ(header->GetFulfilledPolicies(), 0x11); @@ -96,6 +96,19 @@ TEST(IdmapTests, IdmapFailParsingDifferentMagic) { ASSERT_FALSE(Idmap::FromBinaryStream(stream)); } +TEST(IdmapTests, CreateIdmapConstraintsFromBinaryStream) { + std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen); + std::istringstream stream(raw); + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + std::unique_ptr<const IdmapConstraints> constraints = IdmapConstraints::FromBinaryStream(stream); + ASSERT_THAT(constraints, NotNull()); + ASSERT_EQ(constraints->constraints.size(), 2); + IdmapConstraint constraint1{.constraint_type = 0, .constraint_value = 1}; + IdmapConstraint constraint2{.constraint_type = 1, .constraint_value = 2}; + ASSERT_NE(constraints->constraints.find(constraint1), constraints->constraints.end()); + ASSERT_NE(constraints->constraints.find(constraint2), constraints->constraints.end()); +} + TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { const size_t offset = kIdmapRawDataOffset; std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset); @@ -143,7 +156,7 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 10); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 11); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U); ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies); @@ -195,16 +208,17 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) { auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers( **target, **overlay, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, std::move(constraints)); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 10); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 11); 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); @@ -238,9 +252,10 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) { auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers( **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, std::move(constraints)); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); @@ -296,8 +311,9 @@ TEST(IdmapTests, FabricatedOverlay) { auto overlay = OverlayResourceContainer::FromPath(tf.path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers(**target, **overlay, "SandTheme", PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, std::move(constraints)); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); @@ -341,13 +357,17 @@ TEST(IdmapTests, FailCreateIdmapInvalidName) { ASSERT_TRUE(overlay); { + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers(**target, **overlay, "", PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, + std::move(constraints)); ASSERT_FALSE(idmap_result); } { + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers(**target, **overlay, "unknown", PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, + std::move(constraints)); ASSERT_FALSE(idmap_result); } } @@ -362,9 +382,10 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); auto idmap_result = Idmap::FromContainers( **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC, - /* enforce_overlayable */ true); + /* enforce_overlayable */ true, std::move(constraints)); ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); @@ -634,6 +655,10 @@ class TestVisitor : public Visitor { stream_ << "TestVisitor::visit(IdmapHeader)" << '\n'; } + void visit(const IdmapConstraints& idmap ATTRIBUTE_UNUSED) override { + stream_ << "TestVisitor::visit(IdmapConstraints)" << '\n'; + } + void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) override { stream_ << "TestVisitor::visit(IdmapData)" << '\n'; } @@ -659,6 +684,7 @@ TEST(IdmapTests, TestVisitor) { ASSERT_EQ(test_stream.str(), "TestVisitor::visit(IdmapHeader)\n" + "TestVisitor::visit(IdmapConstraints)\n" "TestVisitor::visit(Idmap)\n" "TestVisitor::visit(IdmapData::Header)\n" "TestVisitor::visit(IdmapData)\n"); diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp index 3d3d82a8c7dd..2f42f798f64a 100644 --- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp @@ -42,8 +42,10 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, - PolicyFlags::PUBLIC, /* enforce_overlayable */ true); + PolicyFlags::PUBLIC, /* enforce_overlayable */ true, + std::move(constraints)); ASSERT_TRUE(idmap); std::stringstream stream; diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp index 7fae1c64f014..d5aafe6b8d35 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -55,8 +55,10 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path); ASSERT_TRUE(overlay); + auto constraints = std::make_unique<const IdmapConstraints>(); const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, - PolicyFlags::PUBLIC, /* enforce_overlayable */ true); + PolicyFlags::PUBLIC, /* enforce_overlayable */ true, + std::move(constraints)); ASSERT_TRUE(idmap); std::stringstream stream; @@ -64,7 +66,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "0000000a version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000b version\n", stream.str()); ASSERT_CONTAINS_REGEX( StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING), stream.str()); @@ -73,6 +75,7 @@ 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 "00000000 constraints count\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 "00000000 target inline entry value count", stream.str()); @@ -113,7 +116,7 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { (*idmap)->accept(&visitor); ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "0000000a version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "0000000b 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()); @@ -124,6 +127,11 @@ 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 "00000002 constraints count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 constraint type\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 constraint value\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 constraint type\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000002 constraint value\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 "00000001 target inline entry value count", stream.str()); diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h index 2b4ebd1ae800..6f645bd01229 100644 --- a/cmds/idmap2/tests/TestHelpers.h +++ b/cmds/idmap2/tests/TestHelpers.h @@ -34,7 +34,7 @@ const unsigned char kIdmapRawData[] = { 0x49, 0x44, 0x4d, 0x50, // 0x4: version - 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, // 0x8: target crc 0x34, 0x12, 0x00, 0x00, @@ -73,131 +73,147 @@ const unsigned char kIdmapRawData[] = { // 0x4c string contents "debug\0\0\0" (padded to word alignment) 0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00, + // CONSTRAINTS + // 0x54: constraints_count + 0x02, 0x00, 0x00, 0x00, + + // 0x58: constraint_type + 0x00, 0x00, 0x00, 0x00, + + // 0x5c: constraint_value + 0x01, 0x00, 0x00, 0x00, + + // 0x60: constraint_type + 0x01, 0x00, 0x00, 0x00, + + // 0x64: constraint_value + 0x02, 0x00, 0x00, 0x00, + // DATA HEADER - // 0x54: target_entry_count + // 0x68: target_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x58: target_inline_entry_count + // 0x6c: target_inline_entry_count 0x01, 0x00, 0x00, 0x00, - // 0x5c: target_inline_entry_value_count + // 0x70: target_inline_entry_value_count 0x01, 0x00, 0x00, 0x00, // 0x60: config_count 0x01, 0x00, 0x00, 0x00, - // 0x64: overlay_entry_count + // 0x74: overlay_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x68: string_pool_offset + // 0x78: string_pool_offset 0x00, 0x00, 0x00, 0x00, // TARGET ENTRIES - // 0x6c: target id (0x7f020000) + // 0x7c: target id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x70: target id (0x7f030000) + // 0x80: target id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x74: target id (0x7f030002) + // 0x84: target id (0x7f030002) 0x02, 0x00, 0x03, 0x7f, - // 0x78: overlay_id (0x7f020000) + // 0x88: overlay_id (0x7f020000) 0x00, 0x00, 0x02, 0x7f, - // 0x7c: overlay_id (0x7f030000) + // 0x8c: overlay_id (0x7f030000) 0x00, 0x00, 0x03, 0x7f, - // 0x80: overlay_id (0x7f030001) + // 0x90: overlay_id (0x7f030001) 0x01, 0x00, 0x03, 0x7f, // INLINE TARGET ENTRIES - // 0x84: target_id + // 0x94: target_id 0x00, 0x00, 0x04, 0x7f, - // 0x88: start value index + // 0x98: start value index 0x00, 0x00, 0x00, 0x00, - // 0x8c: value count + // 0x9c: value count 0x01, 0x00, 0x00, 0x00, // INLINE TARGET ENTRY VALUES - // 0x90: config index + // 0xa0: config index 0x00, 0x00, 0x00, 0x00, - // 0x94: Res_value::size (value ignored by idmap) + // 0xa4: Res_value::size (value ignored by idmap) 0x08, 0x00, - // 0x98: Res_value::res0 (value ignored by idmap) + // 0xa8: Res_value::res0 (value ignored by idmap) 0x00, - // 0x9c: Res_value::dataType (TYPE_INT_HEX) + // 0xac: Res_value::dataType (TYPE_INT_HEX) 0x11, - // 0xa0: Res_value::data + // 0xb0: Res_value::data 0x78, 0x56, 0x34, 0x12, // CONFIGURATIONS - // 0xa4: ConfigDescription + // 0xb4: ConfigDescription // size 0x40, 0x00, 0x00, 0x00, - // 0xa8: imsi + // 0xb8: imsi 0x00, 0x00, 0x00, 0x00, - // 0xac: locale + // 0xbc: locale 0x00, 0x00, 0x00, 0x00, - // 0xb0: screenType + // 0xc0: screenType 0x02, 0x00, 0xe0, 0x01, - // 0xb4: input + // 0xc4: input 0x00, 0x00, 0x00, 0x00, - // 0xb8: screenSize + // 0xc8: screenSize 0x00, 0x00, 0x00, 0x00, - // 0xbc: version + // 0xcc: version 0x07, 0x00, 0x00, 0x00, - // 0xc0: screenConfig + // 0xd0: screenConfig 0x00, 0x00, 0x00, 0x00, - // 0xc4: screenSizeDp + // 0xd4: screenSizeDp 0x00, 0x00, 0x00, 0x00, - // 0xc8: localeScript + // 0xd8: localeScript 0x00, 0x00, 0x00, 0x00, - // 0xcc: localVariant(1) + // 0xdc: localVariant(1) 0x00, 0x00, 0x00, 0x00, - // 0xd0: localVariant(2) + // 0xe0: localVariant(2) 0x00, 0x00, 0x00, 0x00, - // 0xd4: screenConfig2 + // 0xe4: screenConfig2 0x00, 0x00, 0x00, 0x00, - // 0xd8: localeScriptWasComputed + // 0xe8: localeScriptWasComputed 0x00, - // 0xd9: localeNumberingSystem(1) + // 0xe9: localeNumberingSystem(1) 0x00, 0x00, 0x00, 0x00, - // 0xdd: localeNumberingSystem(2) + // 0xed: localeNumberingSystem(2) 0x00, 0x00, 0x00, 0x00, - // 0xe1: padding + // 0xf1: padding 0x00, 0x00, 0x00, // OVERLAY ENTRIES - // 0xe4: 0x7f020000 -> ... + // 0xf4: 0x7f020000 -> ... 0x00, 0x00, 0x02, 0x7f, - // 0xe8: 0x7f030000 -> ... + // 0xf8: 0x7f030000 -> ... 0x00, 0x00, 0x03, 0x7f, - // 0xec: 0x7f030001 -> ... + // 0xfc: 0x7f030001 -> ... 0x01, 0x00, 0x03, 0x7f, - // 0xf0: ... -> 0x7f020000 + // 0x100: ... -> 0x7f020000 0x00, 0x00, 0x02, 0x7f, - // 0xf4: ... -> 0x7f030000 + // 0x104: ... -> 0x7f030000 0x00, 0x00, 0x03, 0x7f, - // 0xf8: ... -> 0x7f030002 + // 0x108: ... -> 0x7f030002 0x02, 0x00, 0x03, 0x7f, - // 0xfc: string pool + // 0x10c: string pool // string length, 0x04, 0x00, 0x00, 0x00, - // 0x100 string contents "test" + // 0x110 string contents "test" 0x74, 0x65, 0x73, 0x74}; constexpr unsigned int kIdmapRawDataLen = std::size(kIdmapRawData); -const unsigned int kIdmapRawDataOffset = 0x54; +const unsigned int kIdmapRawDataOffset = 0x68; const unsigned int kIdmapRawDataTargetCrc = 0x1234; const unsigned int kIdmapRawOverlayCrc = 0x5678; const unsigned int kIdmapRawDataPolicies = 0x11; diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl index 122ab486948f..d865ba749adc 100644 --- a/core/java/android/content/om/IOverlayManager.aidl +++ b/core/java/android/content/om/IOverlayManager.aidl @@ -16,10 +16,13 @@ package android.content.om; +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.om.OverlayManagerTransaction; +import java.util.List; + /** * Api for getting information about overlay packages. * @@ -103,6 +106,22 @@ interface IOverlayManager { boolean setEnabled(in String packageName, in boolean enable, in int userId); /** + * Enable an overlay package for a specific set of constraints. In case of multiple constraints, + * the overlay would be enabled when any of the given constraints are satisfied. + * + * Re-enabling an overlay with new constraints updates the constraints for the overlay. + * + * The caller must pass the actor requirements specified in the class comment. + * + * @param packageName the name of the overlay package to enable. + * @param user The user for which to change the overlay. + * @param constraints list of {@link OverlayConstraint} for enabling the overlay. + * @return true if the system successfully registered the request, false otherwise. + */ + boolean enableWithConstraints(in String packageName, in int userId, + in List<OverlayConstraint> constraints); + + /** * Request that an overlay package is enabled and any other overlay packages with the same * target package are disabled. * diff --git a/core/java/android/content/om/OverlayConstraint.aidl b/core/java/android/content/om/OverlayConstraint.aidl new file mode 100644 index 000000000000..95aac8069617 --- /dev/null +++ b/core/java/android/content/om/OverlayConstraint.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 OverlayConstraint; diff --git a/core/java/android/content/om/OverlayConstraint.java b/core/java/android/content/om/OverlayConstraint.java new file mode 100644 index 000000000000..c1902def882f --- /dev/null +++ b/core/java/android/content/om/OverlayConstraint.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.IntDef; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Objects; + +/** + * Constraint for enabling a RRO. Currently this can be a displayId or a deviceId, i.e., + * the overlay would be applied only when a target package is running on the given displayId + * or deviceId. + * + * @hide + */ +public final class OverlayConstraint implements Parcelable { + + /** + * Constraint type for enabling a RRO for a specific display id. For contexts associated with + * the default display, this would be {@link android.view.Display#DEFAULT_DISPLAY}, and + * for contexts associated with a virtual display, this would be the id of the virtual display. + */ + public static final int TYPE_DISPLAY_ID = 0; + + /** + * Constraint type for enabling a RRO for a specific device id. For contexts associated with + * the default device, this would be {@link android.content.Context#DEVICE_ID_DEFAULT}, and + * for contexts associated with virtual device, this would be the id of the virtual device. + */ + public static final int TYPE_DEVICE_ID = 1; + + @IntDef(prefix = "TYPE_", value = { + TYPE_DISPLAY_ID, + TYPE_DEVICE_ID, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ConstraintType { + } + + @ConstraintType + private final int mType; + private final int mValue; + + public OverlayConstraint(int type, int value) { + if (type != TYPE_DEVICE_ID && type != TYPE_DISPLAY_ID) { + throw new IllegalArgumentException( + "Type must be either TYPE_DISPLAY_ID or TYPE_DEVICE_ID"); + } + if (value < 0) { + throw new IllegalArgumentException("Value must be greater than 0"); + } + this.mType = type; + this.mValue = value; + } + + private OverlayConstraint(Parcel in) { + this(in.readInt(), in.readInt()); + } + + /** + * Returns the type of the constraint. + */ + public int getType() { + return mType; + } + + /** + * Returns the value of the constraint. + */ + public int getValue() { + return mValue; + } + + @Override + public String toString() { + return "{type: " + typeToString(mType) + ", value: " + mValue + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OverlayConstraint that)) { + return false; + } + return mType == that.mType && mValue == that.mValue; + } + + @Override + public int hashCode() { + return Objects.hash(mType, mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mValue); + } + + public static final Creator<OverlayConstraint> CREATOR = new Creator<>() { + @Override + public OverlayConstraint createFromParcel(Parcel in) { + return new OverlayConstraint(in); + } + + @Override + public OverlayConstraint[] newArray(int size) { + return new OverlayConstraint[size]; + } + }; + + /** + * Returns a string description for a list of constraints. + */ + public static String constraintsToString(final List<OverlayConstraint> overlayConstraints) { + if (overlayConstraints == null || overlayConstraints.isEmpty()) { + return "None"; + } + return "[" + TextUtils.join(",", overlayConstraints) + "]"; + } + + private static String typeToString(@ConstraintType int type) { + return type == TYPE_DEVICE_ID ? "DEVICE_ID" : "DISPLAY_ID"; + } +} diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index 2e898562655b..4977c820ba55 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -30,6 +30,9 @@ import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -230,12 +233,17 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { private OverlayIdentifier mIdentifierCached; /** - * * @hide */ public final boolean isFabricated; /** + * @hide + */ + @NonNull + public final List<OverlayConstraint> constraints; + + /** * Create a new OverlayInfo based on source with an updated state. * * @param source the source OverlayInfo to base the new instance on @@ -246,7 +254,8 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { public OverlayInfo(@NonNull OverlayInfo source, @State int state) { this(source.packageName, source.overlayName, source.targetPackageName, source.targetOverlayableName, source.category, source.baseCodePath, state, - source.userId, source.priority, source.isMutable, source.isFabricated); + source.userId, source.priority, source.isMutable, source.isFabricated, + source.constraints); } /** @hide */ @@ -264,6 +273,17 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { @NonNull String targetPackageName, @Nullable String targetOverlayableName, @Nullable String category, @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable, boolean isFabricated) { + this(packageName, overlayName, targetPackageName, targetOverlayableName, category, + baseCodePath, state, userId, priority, isMutable, isFabricated, + Collections.emptyList() /* constraints */); + } + + /** @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, + @NonNull List<OverlayConstraint> constraints) { this.packageName = packageName; this.overlayName = overlayName; this.targetPackageName = targetPackageName; @@ -275,6 +295,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { this.priority = priority; this.isMutable = isMutable; this.isFabricated = isFabricated; + this.constraints = constraints; ensureValidState(); } @@ -291,6 +312,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { priority = source.readInt(); isMutable = source.readBoolean(); isFabricated = source.readBoolean(); + constraints = Arrays.asList(source.createTypedArray(OverlayConstraint.CREATOR)); ensureValidState(); } @@ -395,6 +417,17 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { return mIdentifierCached; } + /** + * Returns the currently applied constraints (if any) for the overlay. An overlay + * may have constraints only when it is enabled. + * + * @hide + */ + @NonNull + public List<OverlayConstraint> getConstraints() { + return constraints; + } + @SuppressWarnings("ConstantConditions") private void ensureValidState() { if (packageName == null) { @@ -406,6 +439,9 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { if (baseCodePath == null) { throw new IllegalArgumentException("baseCodePath must not be null"); } + if (constraints == null) { + throw new IllegalArgumentException("constraints must not be null"); + } switch (state) { case STATE_UNKNOWN: case STATE_MISSING_TARGET: @@ -439,20 +475,21 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { dest.writeInt(priority); dest.writeBoolean(isMutable); dest.writeBoolean(isFabricated); + dest.writeTypedArray(constraints.toArray(new OverlayConstraint[0]), flags); } public static final @NonNull Parcelable.Creator<OverlayInfo> CREATOR = - new Parcelable.Creator<OverlayInfo>() { - @Override - public OverlayInfo createFromParcel(Parcel source) { - return new OverlayInfo(source); - } + new Parcelable.Creator<>() { + @Override + public OverlayInfo createFromParcel(Parcel source) { + return new OverlayInfo(source); + } - @Override - public OverlayInfo[] newArray(int size) { - return new OverlayInfo[size]; - } - }; + @Override + public OverlayInfo[] newArray(int size) { + return new OverlayInfo[size]; + } + }; /** * Return true if this overlay is enabled, i.e. should be used to overlay @@ -461,6 +498,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * Disabled overlay packages are installed but are currently not in use. * * @return true if the overlay is enabled, else false. + * * @hide */ @SystemApi @@ -479,6 +517,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * debugging purposes. * * @return a human readable String representing the state. + * * @hide */ public static String stateToString(@State int state) { @@ -522,6 +561,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { : targetOverlayableName.hashCode()); result = prime * result + ((category == null) ? 0 : category.hashCode()); result = prime * result + ((baseCodePath == null) ? 0 : baseCodePath.hashCode()); + result = prime * result + (constraints.isEmpty() ? 0 : constraints.hashCode()); return result; } @@ -566,7 +606,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { if (!baseCodePath.equals(other.baseCodePath)) { return false; } - return true; + return Objects.equals(constraints, other.constraints); } /** @@ -584,6 +624,7 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { + ", targetOverlayable=" + targetOverlayableName + ", state=" + state + " (" + stateToString(state) + ")," + ", userId=" + userId + + ", constraints=" + OverlayConstraint.constraintsToString(constraints) + " }"; } } diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 6db7dfe4f705..fd9bfa274289 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -59,7 +59,6 @@ import java.util.List; public class OverlayManager { private final IOverlayManager mService; - private final Context mContext; private final OverlayManagerImpl mOverlayManagerImpl; /** @@ -137,7 +136,6 @@ public class OverlayManager { */ @SuppressLint("ReferencesHidden") public OverlayManager(@NonNull Context context, @Nullable IOverlayManager service) { - mContext = context; mService = service; mOverlayManagerImpl = new OverlayManagerImpl(context); } @@ -161,7 +159,7 @@ public class OverlayManager { * @param packageName the name of the overlay package to enable. * @param user The user for which to change the overlay. * - * @throws SecurityException when caller is not allowed to enable {@param packageName} + * @throws SecurityException when caller is not allowed to enable {@code packageName} * @throws IllegalStateException when enabling fails otherwise * * @hide @@ -196,7 +194,7 @@ public class OverlayManager { * @param enable {@code false} if the overlay should be turned off. * @param user The user for which to change the overlay. * - * @throws SecurityException when caller is not allowed to enable/disable {@param packageName} + * @throws SecurityException when caller is not allowed to enable/disable {@code packageName} * @throws IllegalStateException when enabling/disabling fails otherwise * * @hide @@ -220,6 +218,43 @@ public class OverlayManager { } /** + * Enable an overlay package for a specific set of constraints. In case of multiple constraints, + * the overlay would be enabled when any of the given constraints are satisfied. + * + * Re-enabling an overlay with new constraints updates the constraints for the overlay. + * + * The caller must pass the actor requirements specified in the class comment. + * + * @param packageName the name of the overlay package to enable. + * @param user The user for which to change the overlay. + * @param constraints list of {@link OverlayConstraint} for enabling the overlay. + * + * @throws SecurityException when caller is not allowed to enable {@code packageName} + * @throws IllegalStateException when enabling fails otherwise + * + * @see OverlayConstraint + * + * @hide + */ + @RequiresPermission(anyOf = { + "android.permission.INTERACT_ACROSS_USERS", + "android.permission.INTERACT_ACROSS_USERS_FULL" + }) + public void enableWithConstraints(@NonNull final String packageName, @NonNull UserHandle user, + @Nullable final List<OverlayConstraint> constraints) + throws SecurityException, IllegalStateException { + try { + if (!mService.enableWithConstraints(packageName, user.getIdentifier(), constraints)) { + throw new IllegalStateException("enableWithConstraints failed"); + } + } catch (SecurityException e) { + rethrowSecurityException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns information about the overlay with the given package name for * the specified user. * @@ -299,7 +334,6 @@ public class OverlayManager { @RequiresPermission(anyOf = { "android.permission.INTERACT_ACROSS_USERS", }) - @NonNull public void invalidateCachesForOverlay(@NonNull final String targetPackageName, @NonNull UserHandle user) { try { diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java index 87b2e9350aa1..f9eb5e010d71 100644 --- a/core/java/android/content/om/OverlayManagerTransaction.java +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -18,8 +18,6 @@ package android.content.om; import static android.annotation.SystemApi.Client.SYSTEM_SERVER; -import static com.android.internal.util.Preconditions.checkNotNull; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,13 +27,15 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Objects; /** @@ -106,7 +106,9 @@ public final class OverlayManagerTransaction implements Parcelable { final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class); final int userId = source.readInt(); final Bundle extras = source.readBundle(null); - mRequests.add(new Request(request, overlay, userId, extras)); + OverlayConstraint[] constraints = source.createTypedArray(OverlayConstraint.CREATOR); + mRequests.add(new Request(request, overlay, userId, extras, + Arrays.asList(constraints))); } mSelfTargeting = false; } @@ -115,6 +117,7 @@ public final class OverlayManagerTransaction implements Parcelable { * Get the iterator of requests * * @return the iterator of request + * * @hide */ @SuppressLint("ReferencesHidden") @@ -145,6 +148,8 @@ public final class OverlayManagerTransaction implements Parcelable { @IntDef(prefix = "TYPE_", value = { TYPE_SET_ENABLED, TYPE_SET_DISABLED, + TYPE_REGISTER_FABRICATED, + TYPE_UNREGISTER_FABRICATED, }) @Retention(RetentionPolicy.SOURCE) @interface RequestType {} @@ -166,23 +171,51 @@ public final class OverlayManagerTransaction implements Parcelable { @Nullable public final Bundle extras; + /** + * @hide + */ + @NonNull + public final List<OverlayConstraint> constraints; + public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay, final int userId) { - this(type, overlay, userId, null /* extras */); + this(type, overlay, userId, null /* extras */, + Collections.emptyList() /* constraints */); } public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay, final int userId, @Nullable Bundle extras) { + this(type, overlay, userId, extras, Collections.emptyList() /* constraints */); + } + + /** + * @hide + */ + public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay, + final int userId, @NonNull List<OverlayConstraint> constraints) { + this(type, overlay, userId, null /* extras */, constraints); + } + + /** + * @hide + */ + public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay, + final int userId, @Nullable Bundle extras, + @NonNull List<OverlayConstraint> constraints) { this.type = type; this.overlay = overlay; this.userId = userId; this.extras = extras; + Objects.requireNonNull(constraints); + this.constraints = constraints; } @Override public String toString() { - return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}", - type, typeToString(), overlay, userId); + return TextUtils.formatSimple( + "Request{type=0x%02x (%s), overlay=%s, userId=%d, constraints=%s}", + type, typeToString(), overlay, userId, + OverlayConstraint.constraintsToString(constraints)); } /** @@ -205,6 +238,7 @@ public final class OverlayManagerTransaction implements Parcelable { /** * Builder class for OverlayManagerTransaction objects. * TODO(b/269197647): mark the API used by the systemUI. + * * @hide */ public static final class Builder { @@ -238,11 +272,27 @@ public final class OverlayManagerTransaction implements Parcelable { /** * @hide */ + public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, + @NonNull List<OverlayConstraint> constraints) { + return setEnabled(overlay, enable, UserHandle.myUserId(), constraints); + } + + /** + * @hide + */ public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) { - checkNotNull(overlay); + return setEnabled(overlay, enable, userId, Collections.emptyList() /* constraints */); + } + + /** + * @hide + */ + public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId, + @NonNull List<OverlayConstraint> constraints) { + Objects.requireNonNull(overlay); @Request.RequestType final int type = enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED; - mRequests.add(new Request(type, overlay, userId)); + mRequests.add(new Request(type, overlay, userId, constraints)); return this; } @@ -251,6 +301,7 @@ public final class OverlayManagerTransaction implements Parcelable { * applications to overlay on itself resources. The overlay target is itself, or the Android * package, and the work range is only in caller application. * @param selfTargeting whether the overlay is self-targeting, the default is false. + * * @hide */ public Builder setSelfTargeting(boolean selfTargeting) { @@ -324,23 +375,24 @@ public final class OverlayManagerTransaction implements Parcelable { dest.writeParcelable(req.overlay, flags); dest.writeInt(req.userId); dest.writeBundle(req.extras); + dest.writeTypedArray(req.constraints.toArray(new OverlayConstraint[0]), flags); } } @NonNull public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR = - new Parcelable.Creator<OverlayManagerTransaction>() { - - @Override - public OverlayManagerTransaction createFromParcel(Parcel source) { - return new OverlayManagerTransaction(source); - } - - @Override - public OverlayManagerTransaction[] newArray(int size) { - return new OverlayManagerTransaction[size]; - } - }; + new Parcelable.Creator<>() { + + @Override + public OverlayManagerTransaction createFromParcel(Parcel source) { + return new OverlayManagerTransaction(source); + } + + @Override + public OverlayManagerTransaction[] newArray(int size) { + return new OverlayManagerTransaction[size]; + } + }; private static Request generateRegisterFabricatedOverlayRequest( @NonNull FabricatedOverlay overlay) { diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index ecb4bb1394b6..5cbb596bb498 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -76,6 +76,14 @@ flag { } flag { + name: "rro_constraints" + is_exported: false + namespace: "resource_manager" + description: "Feature flag for setting constraints for a RRO" + bug: "371801644" +} + +flag { name: "rro_control_for_android_no_overlayable" is_exported: true namespace: "resource_manager" diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java index 5d4e6a083af4..4b3365221bf5 100644 --- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java +++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java @@ -21,7 +21,6 @@ import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRIC import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY; @@ -85,7 +84,6 @@ public class OverlayManagerImpl { * * @param context the context to create overlay environment */ - @VisibleForTesting(visibility = PACKAGE) public OverlayManagerImpl(@NonNull Context context) { mContext = Objects.requireNonNull(context); diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 3ecd82b074a1..095be57a5dc8 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -55,6 +55,13 @@ struct Idmap_header { // without having to read/store each header entry separately. }; +struct Idmap_constraint { + // Constraint type can be TYPE_DISPLAY_ID or TYP_DEVICE_ID, please refer + // to ConstraintType in OverlayConstraint.java + uint32_t constraint_type; + uint32_t constraint_value; +}; + struct Idmap_data_header { uint32_t target_entry_count; uint32_t target_inline_entry_count; @@ -254,13 +261,18 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size #endif LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header, - const Idmap_data_header* data_header, Idmap_target_entries target_entries, + const Idmap_constraint* constraints, + uint32_t constraints_count, + const Idmap_data_header* data_header, + Idmap_target_entries target_entries, Idmap_target_inline_entries target_inline_entries, const Idmap_target_entry_inline_value* inline_entry_values, const ConfigDescription* configs, Idmap_overlay_entries overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, std::string_view overlay_apk_path, std::string_view target_apk_path) : header_(header), + constraints_(constraints), + constraints_count_(constraints_count), data_header_(data_header), target_entries_(target_entries), target_inline_entries_(target_inline_entries), @@ -298,9 +310,9 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie return {}; } std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path"); - if (!target_path) { - return {}; - } + if (!target_path) { + return {}; + } std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path"); if (!overlay_path) { return {}; @@ -310,6 +322,17 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie return {}; } + auto constraints_count = ReadType<uint32_t>(&data_ptr, &data_size, "constraints count"); + if (!constraints_count) { + return {}; + } + auto constraints = *constraints_count > 0 ? + ReadType<Idmap_constraint>(&data_ptr, &data_size, "constraints", *constraints_count) + : nullptr; + if (*constraints_count > 0 && !constraints) { + return {}; + } + // Parse the idmap data blocks. Currently idmap2 can only generate one data block. auto data_header = ReadType<Idmap_data_header>(&data_ptr, &data_size, "data header"); if (data_header == nullptr) { @@ -376,9 +399,10 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie // Can't use make_unique because LoadedIdmap constructor is private. return std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries, - target_inline_entries, target_inline_entry_values, configurations, - overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path)); + new LoadedIdmap(std::string(idmap_path), header, constraints, *constraints_count, + data_header, target_entries, target_inline_entries, + target_inline_entry_values,configurations, overlay_entries, + std::move(idmap_string_pool),*overlay_path, *target_path)); } bool LoadedIdmap::IsUpToDate() const { diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index de9991a8be5e..978bc768cd3d 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -290,11 +290,11 @@ static bool assertIdmapHeader(const void* idmap, size_t size) { } const uint32_t version = htodl(*(reinterpret_cast<const uint32_t*>(idmap) + 1)); - if (version != ResTable::IDMAP_CURRENT_VERSION) { + if (version != kIdmapCurrentVersion) { // We are strict about versions because files with this format are // auto-generated and don't need backwards compatibility. ALOGW("idmap: version mismatch in header (is 0x%08x, expected 0x%08x)", - version, ResTable::IDMAP_CURRENT_VERSION); + version, kIdmapCurrentVersion); return false; } return true; @@ -400,14 +400,18 @@ status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, Keyed return UNKNOWN_ERROR; } - size -= ResTable::IDMAP_HEADER_SIZE_BYTES; + size_t sizeOfHeaderAndConstraints = ResTable::IDMAP_HEADER_SIZE_BYTES + + // This accounts for zero constraints, and hence takes only 4 bytes for + // the constraints count. + ResTable::IDMAP_CONSTRAINTS_COUNT_SIZE_BYTES; + size -= sizeOfHeaderAndConstraints; if (size < sizeof(uint16_t) * 2) { ALOGE("idmap: too small to contain any mapping"); return UNKNOWN_ERROR; } const uint16_t* data = reinterpret_cast<const uint16_t*>( - reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES); + reinterpret_cast<const uint8_t*>(idmap) + sizeOfHeaderAndConstraints); uint16_t targetPackageId = dtohs(*(data++)); if (targetPackageId == 0 || targetPackageId > 255) { @@ -7492,7 +7496,7 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, // write idmap header uint32_t* data = reinterpret_cast<uint32_t*>(*outData); *data++ = htodl(IDMAP_MAGIC); // write: magic - *data++ = htodl(ResTable::IDMAP_CURRENT_VERSION); // write: version + *data++ = htodl(kIdmapCurrentVersion); // write: version *data++ = htodl(targetCrc); // write: target crc *data++ = htodl(overlayCrc); // write: overlay crc @@ -7507,6 +7511,9 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, } data += (2 * 256) / sizeof(uint32_t); + // write zero constraints count (no constraints) + *data++ = htodl(0); + // write idmap data header uint16_t* typeData = reinterpret_cast<uint16_t*>(data); *typeData++ = htods(targetPackageStruct->id); // write: target package id diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index ac75eb3bb98c..d1db13f53069 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -35,6 +35,7 @@ namespace android { class LoadedIdmap; class IdmapResMap; struct Idmap_header; +struct Idmap_constraint; struct Idmap_data_header; struct Idmap_target_entry; struct Idmap_target_entry_inline; @@ -203,6 +204,8 @@ class LoadedIdmap { LoadedIdmap() = default; const Idmap_header* header_; + const Idmap_constraint* constraints_; + uint32_t constraints_count_; const Idmap_data_header* data_header_; Idmap_target_entries target_entries_; Idmap_target_inline_entries target_inline_entries_; @@ -220,7 +223,10 @@ class LoadedIdmap { DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); explicit LoadedIdmap(const std::string& idmap_path, const Idmap_header* header, - const Idmap_data_header* data_header, Idmap_target_entries target_entries, + const Idmap_constraint* constraints, + uint32_t constraints_count, + const Idmap_data_header* data_header, + Idmap_target_entries target_entries, Idmap_target_inline_entries target_inline_entries, const Idmap_target_entry_inline_value* inline_entry_values_, const ConfigDescription* configs, Idmap_overlay_entries overlay_entries, diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index e330410ed1a0..8b2871c21a1e 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -48,7 +48,7 @@ namespace android { constexpr const uint32_t kIdmapMagic = 0x504D4449u; -constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au; +constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Bu; // This must never change. constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian) @@ -2267,7 +2267,7 @@ public: void** outData, size_t* outSize) const; static const size_t IDMAP_HEADER_SIZE_BYTES = 4 * sizeof(uint32_t) + 2 * 256; - static const uint32_t IDMAP_CURRENT_VERSION = 0x00000001; + static const size_t IDMAP_CONSTRAINTS_COUNT_SIZE_BYTES = sizeof(uint32_t); // Retrieve idmap meta-data. // diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap Binary files differindex 7e4b261cf109..6bd57c8d517c 100644 --- a/libs/androidfw/tests/data/overlay/overlay.idmap +++ b/libs/androidfw/tests/data/overlay/overlay.idmap diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index d33c860343c5..9e311511c24f 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -26,6 +26,7 @@ import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.os.IBinder; import android.os.IIdmap2; +import android.os.OverlayConstraint; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -135,8 +136,8 @@ class IdmapDaemon { } String createIdmap(@NonNull String targetPath, @NonNull String overlayPath, - @Nullable String overlayName, int policies, boolean enforce, int userId) - throws TimeoutException, RemoteException { + @Nullable String overlayName, int policies, boolean enforce, int userId, + @NonNull OverlayConstraint[] constraints) throws TimeoutException, RemoteException { try (Connection c = connect()) { final IIdmap2 idmap2 = c.getIdmap2(); if (idmap2 == null) { @@ -147,7 +148,7 @@ class IdmapDaemon { } return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), - policies, enforce, userId); + policies, enforce, userId, constraints); } } @@ -165,8 +166,8 @@ class IdmapDaemon { } boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath, - @Nullable String overlayName, int policies, boolean enforce, int userId) - throws Exception { + @Nullable String overlayName, int policies, boolean enforce, int userId, + @NonNull OverlayConstraint[] constraints) throws Exception { try (Connection c = connect()) { final IIdmap2 idmap2 = c.getIdmap2(); if (idmap2 == null) { @@ -177,7 +178,7 @@ class IdmapDaemon { } return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), - policies, enforce, userId); + policies, enforce, userId, constraints); } } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 86d05d92c95b..4e86aa00657d 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -22,6 +22,7 @@ import static com.android.server.om.OverlayManagerService.TAG; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.content.om.OverlayConstraint; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; import android.os.Build.VERSION_CODES; @@ -102,7 +103,8 @@ final class IdmapManager { */ @IdmapStatus int createIdmap(@NonNull final AndroidPackage targetPackage, @NonNull PackageState overlayPackageState, @NonNull final AndroidPackage overlayPackage, - String overlayBasePath, String overlayName, @UserIdInt int userId) { + String overlayBasePath, String overlayName, @UserIdInt int userId, + @NonNull final List<OverlayConstraint> constraints) { if (DEBUG) { Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and " + overlayPackage.getPackageName()); @@ -112,12 +114,13 @@ final class IdmapManager { int policies = calculateFulfilledPolicies(targetPackage, overlayPackageState, overlayPackage, userId); boolean enforce = enforceOverlayable(overlayPackageState, overlayPackage); + android.os.OverlayConstraint[] idmapConstraints = toIdmapConstraints(constraints); if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies, - enforce, userId)) { + enforce, userId, idmapConstraints)) { return IDMAP_IS_VERIFIED; } final boolean idmapCreated = mIdmapDaemon.createIdmap(targetPath, overlayBasePath, - overlayName, policies, enforce, userId) != null; + overlayName, policies, enforce, userId, idmapConstraints) != null; return (idmapCreated) ? IDMAP_IS_MODIFIED | IDMAP_IS_VERIFIED : IDMAP_NOT_EXIST; } catch (Exception e) { Slog.w(TAG, "failed to generate idmap for " + targetPath + " and " @@ -275,4 +278,19 @@ final class IdmapManager { return false; } + + @NonNull + private static android.os.OverlayConstraint[] toIdmapConstraints( + @NonNull final List<OverlayConstraint> constraints) { + android.os.OverlayConstraint[] idmapConstraints = + new android.os.OverlayConstraint[constraints.size()]; + int index = 0; + for (OverlayConstraint constraint : constraints) { + android.os.OverlayConstraint idmapConstraint = new android.os.OverlayConstraint(); + idmapConstraint.type = constraint.getType(); + idmapConstraint.value = constraint.getValue(); + idmapConstraints[index++] = idmapConstraint; + } + return idmapConstraints; + } } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 8710438d76b3..847da8642100 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -46,6 +46,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.om.IOverlayManager; +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.om.OverlayManagerTransaction; @@ -655,6 +656,18 @@ public final class OverlayManagerService extends SystemService { @Override public boolean setEnabled(@Nullable final String packageName, final boolean enable, int userIdArg) { + return setEnabled(packageName, enable, userIdArg, + Collections.emptyList() /* constraints */); + } + + @Override + public boolean enableWithConstraints(@Nullable final String packageName, int userIdArg, + @NonNull final List<OverlayConstraint> constraints) { + return setEnabled(packageName, true /* enable */, userIdArg, constraints); + } + + private boolean setEnabled(@Nullable final String packageName, final boolean enable, + int userIdArg, @NonNull final List<OverlayConstraint> constraints) { if (packageName == null) { return false; } @@ -671,7 +684,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { updateTargetPackagesLocked( - mImpl.setEnabled(overlay, enable, realUserId)); + mImpl.setEnabled(overlay, enable, realUserId, constraints)); return true; } catch (OperationFailedException e) { return false; @@ -967,13 +980,15 @@ public final class OverlayManagerService extends SystemService { case TYPE_SET_ENABLED: Set<UserPackage> result = null; result = CollectionUtils.addAll(result, - mImpl.setEnabled(request.overlay, true, realUserId)); + mImpl.setEnabled(request.overlay, true /* enable */, realUserId, + request.constraints)); result = CollectionUtils.addAll(result, mImpl.setHighestPriority(request.overlay, realUserId)); return CollectionUtils.emptyIfNull(result); case TYPE_SET_DISABLED: - return mImpl.setEnabled(request.overlay, false, realUserId); + return mImpl.setEnabled(request.overlay, false /* enable */, realUserId, + request.constraints); case TYPE_REGISTER_FABRICATED: final FabricatedOverlayInternal fabricated = diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 0e9ec4d71421..bcebe0b07d91 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -34,11 +34,13 @@ import static com.android.server.om.OverlayManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.om.CriticalOverlayInfo; +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.FrameworkParsingPackageUtils; +import android.content.res.Flags; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.text.TextUtils; @@ -246,7 +248,7 @@ final class OverlayManagerServiceImpl { + oi.targetPackageName + "' in category '" + oi.category + "' for user " + newUserId); mSettings.setEnabled(overlay, newUserId, true); - if (updateState(oi, newUserId, 0)) { + if (updateState(oi, newUserId, 0, oi.constraints)) { CollectionUtils.add(updatedTargets, UserPackage.of(oi.userId, oi.targetPackageName)); } @@ -338,7 +340,7 @@ final class OverlayManagerServiceImpl { for (int i = 0, n = overlays.size(); i < n; i++) { final OverlayInfo oi = overlays.get(i); try { - modified |= updateState(oi, userId, flags); + modified |= updateState(oi, userId, flags, oi.constraints); } catch (OverlayManagerSettings.BadKeyException e) { Slog.e(TAG, "failed to update settings", e); modified |= mSettings.remove(oi.getOverlayIdentifier(), userId); @@ -386,7 +388,7 @@ final class OverlayManagerServiceImpl { } // Update the enabled state of the overlay. - if (updateState(currentInfo, userId, flags)) { + if (updateState(currentInfo, userId, flags, currentInfo.constraints)) { updatedTargets = CollectionUtils.add(updatedTargets, UserPackage.of(userId, currentInfo.targetPackageName)); } @@ -440,10 +442,22 @@ final class OverlayManagerServiceImpl { @NonNull Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay, - final boolean enable, final int userId) throws OperationFailedException { + final boolean enable, final int userId, + @NonNull final List<OverlayConstraint> constraints) + throws OperationFailedException { if (DEBUG) { - Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d", - overlay, enable, userId)); + Slog.d(TAG, TextUtils.formatSimple( + "setEnabled overlay=%s enable=%s userId=%d constraints=%s", + overlay, enable, userId, OverlayConstraint.constraintsToString(constraints))); + } + + boolean hasConstraints = constraints != null && !constraints.isEmpty(); + if (!Flags.rroConstraints() && hasConstraints) { + throw new OperationFailedException("RRO constraints are not supported"); + } + if (!enable && hasConstraints) { + throw new OperationFailedException( + "Constraints can only be set when enabling an overlay"); } try { @@ -455,7 +469,7 @@ final class OverlayManagerServiceImpl { } boolean modified = mSettings.setEnabled(overlay, userId, enable); - modified |= updateState(oi, userId, 0); + modified |= updateState(oi, userId, 0, constraints); if (modified) { return Set.of(UserPackage.of(userId, oi.targetPackageName)); @@ -469,7 +483,7 @@ final class OverlayManagerServiceImpl { Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay, boolean withinCategory, final int userId) throws OperationFailedException { if (DEBUG) { - Slog.d(TAG, String.format("setEnabledExclusive overlay=%s" + Slog.d(TAG, TextUtils.formatSimple("setEnabledExclusive overlay=%s" + " withinCategory=%s userId=%d", overlay, withinCategory, userId)); } @@ -501,12 +515,16 @@ final class OverlayManagerServiceImpl { // Disable the overlay. modified |= mSettings.setEnabled(disabledOverlay, userId, false); - modified |= updateState(disabledInfo, userId, 0); + modified |= updateState(disabledInfo, userId, 0 /* flags */, + Collections.emptyList() /* constraints */); } // Enable the selected overlay. modified |= mSettings.setEnabled(overlay, userId, true); - modified |= updateState(enabledInfo, userId, 0); + // No constraints should be applied when exclusively enabling an overlay within + // a category. + modified |= updateState(enabledInfo, userId, 0 /* flags */, + Collections.emptyList() /* constraints */); if (modified) { return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName)); @@ -569,7 +587,8 @@ final class OverlayManagerServiceImpl { // overlay. mSettings.setBaseCodePath(overlayIdentifier, userId, info.path); } - if (updateState(oi, userId, 0)) { + // No constraints should be applied when registering a fabricated overlay. + if (updateState(oi, userId, 0 /* flags */, Collections.emptyList() /* constraints */)) { updatedTargets.add(UserPackage.of(userId, oi.targetPackageName)); } } catch (OverlayManagerSettings.BadKeyException e) { @@ -670,7 +689,7 @@ final class OverlayManagerServiceImpl { Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) throws OperationFailedException { - try{ + try { if (DEBUG) { Slog.d(TAG, "setHighestPriority overlay=" + overlay + " userId=" + userId); } @@ -693,7 +712,7 @@ final class OverlayManagerServiceImpl { Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) throws OperationFailedException { - try{ + try { if (DEBUG) { Slog.d(TAG, "setLowestPriority packageName=" + overlay + " userId=" + userId); } @@ -793,7 +812,8 @@ final class OverlayManagerServiceImpl { * Returns true if the settings/state was modified, false otherwise. */ private boolean updateState(@NonNull final CriticalOverlayInfo info, - final int userId, final int flags) throws OverlayManagerSettings.BadKeyException { + final int userId, final int flags, @NonNull final List<OverlayConstraint> constraints) + throws OverlayManagerSettings.BadKeyException { final OverlayIdentifier overlay = info.getOverlayIdentifier(); var targetPackageState = mPackageManager.getPackageStateForUser(info.getTargetPackageName(), userId); @@ -812,6 +832,7 @@ final class OverlayManagerServiceImpl { } modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory()); + modified |= mSettings.setConstraints(overlay, userId, constraints); if (!info.isFabricated()) { modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getSplits().get(0).getPath()); @@ -826,7 +847,7 @@ final class OverlayManagerServiceImpl { && !isPackageConfiguredMutable(overlayPackage))) { idmapStatus = mIdmapManager.createIdmap(targetPackage, overlayPackageState, overlayPackage, updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), - userId); + userId, updatedOverlayInfo.constraints); modified |= (idmapStatus & IDMAP_IS_MODIFIED) != 0; } @@ -835,7 +856,7 @@ final class OverlayManagerServiceImpl { userId, flags, idmapStatus); if (currentState != newState) { if (DEBUG) { - Slog.d(TAG, String.format("%s:%d: %s -> %s", + Slog.d(TAG, TextUtils.formatSimple("%s:%d: %s -> %s", overlay, userId, OverlayInfo.stateToString(currentState), OverlayInfo.stateToString(newState))); diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index f9758fcd5d01..4e5c73209be2 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -21,6 +21,7 @@ import static com.android.server.om.OverlayManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.os.UserHandle; @@ -44,6 +45,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -78,7 +80,8 @@ final class OverlayManagerSettings { remove(overlay, userId); final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, - isMutable, priority, overlayCategory, isFabricated); + isMutable, priority, overlayCategory, isFabricated, + Collections.emptyList() /* constraints */); insert(item); return item.getOverlayInfo(); } @@ -155,6 +158,15 @@ final class OverlayManagerSettings { return mItems.get(idx).setEnabled(enable); } + boolean setConstraints(@NonNull final OverlayIdentifier overlay, final int userId, + @NonNull final List<OverlayConstraint> constraints) throws BadKeyException { + final int idx = select(overlay, userId); + if (idx < 0) { + throw new BadKeyException(overlay, userId); + } + return mItems.get(idx).setConstraints(constraints); + } + @OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId) throws BadKeyException { final int idx = select(overlay, userId); @@ -400,6 +412,8 @@ final class OverlayManagerSettings { pw.println("mPriority..............: " + item.mPriority); pw.println("mCategory..............: " + item.mCategory); pw.println("mIsFabricated..........: " + item.mIsFabricated); + pw.println("mConstraints...........: " + + OverlayConstraint.constraintsToString(item.mConstraints)); pw.decreaseIndent(); pw.println("}"); @@ -456,6 +470,7 @@ final class OverlayManagerSettings { static final class Serializer { private static final String TAG_OVERLAYS = "overlays"; private static final String TAG_ITEM = "item"; + private static final String TAG_CONSTRAINT = "constraint"; private static final String ATTR_BASE_CODE_PATH = "baseCodePath"; private static final String ATTR_IS_ENABLED = "isEnabled"; @@ -471,8 +486,11 @@ final class OverlayManagerSettings { private static final String ATTR_VERSION = "version"; private static final String ATTR_IS_FABRICATED = "fabricated"; + private static final String ATTR_CONSTRAINT_TYPE = "type"; + private static final String ATTR_CONSTRAINT_VALUE = "value"; + @VisibleForTesting - static final int CURRENT_VERSION = 4; + static final int CURRENT_VERSION = 5; public static void restore(@NonNull final ArrayList<SettingsItem> table, @NonNull final InputStream is) throws IOException, XmlPullParserException { @@ -502,7 +520,7 @@ final class OverlayManagerSettings { // and overwritten. throw new XmlPullParserException("old version " + oldVersion + "; ignoring"); case 3: - // Upgrading from version 3 to 4 is not a breaking change so do not ignore the + // Upgrading from version 3 to 5 is not a breaking change so do not ignore the // overlay file. return; default: @@ -529,12 +547,23 @@ final class OverlayManagerSettings { final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED, false); + final List<OverlayConstraint> constraints = new ArrayList<>(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_CONSTRAINT.equals(parser.getName())) { + final OverlayConstraint constraint = new OverlayConstraint( + parser.getAttributeInt(null, ATTR_CONSTRAINT_TYPE), + parser.getAttributeInt(null, ATTR_CONSTRAINT_VALUE)); + constraints.add(constraint); + } + } + return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName, - baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated); + baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated, + constraints); } public static void persist(@NonNull final ArrayList<SettingsItem> table, - @NonNull final OutputStream os) throws IOException, XmlPullParserException { + @NonNull final OutputStream os) throws IOException { final TypedXmlSerializer xml = Xml.resolveSerializer(os); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); @@ -566,6 +595,14 @@ final class OverlayManagerSettings { xml.attributeInt(null, ATTR_PRIORITY, item.mPriority); XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory); XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated); + + for (OverlayConstraint constraint : item.mConstraints) { + xml.startTag(null, TAG_CONSTRAINT); + xml.attributeInt(null, ATTR_CONSTRAINT_TYPE, constraint.getType()); + xml.attributeInt(null, ATTR_CONSTRAINT_VALUE, constraint.getValue()); + xml.endTag(null, TAG_CONSTRAINT); + } + xml.endTag(null, TAG_ITEM); } } @@ -579,17 +616,19 @@ final class OverlayManagerSettings { private @OverlayInfo.State int mState; private boolean mIsEnabled; private OverlayInfo mCache; - private boolean mIsMutable; + private final boolean mIsMutable; private int mPriority; private String mCategory; - private boolean mIsFabricated; + private final boolean mIsFabricated; + @NonNull + private List<OverlayConstraint> mConstraints; 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, - final boolean isFabricated) { + final boolean isFabricated, @NonNull final List<OverlayConstraint> constraints) { mOverlay = overlay; mUserId = userId; mTargetPackageName = targetPackageName; @@ -602,6 +641,8 @@ final class OverlayManagerSettings { mIsMutable = isMutable; mPriority = priority; mIsFabricated = isFabricated; + Objects.requireNonNull(constraints); + mConstraints = constraints; } private String getTargetPackageName() { @@ -668,11 +709,26 @@ final class OverlayManagerSettings { return false; } + private boolean setConstraints(@NonNull List<OverlayConstraint> constraints) { + Objects.requireNonNull(constraints); + + if (!mIsMutable) { + return false; + } + + if (!Objects.equals(mConstraints, constraints)) { + mConstraints = constraints; + invalidateCache(); + return true; + } + return false; + } + private OverlayInfo getOverlayInfo() { if (mCache == null) { mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(), mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath, - mState, mUserId, mPriority, mIsMutable, mIsFabricated); + mState, mUserId, mPriority, mIsMutable, mIsFabricated, mConstraints); } return mCache; } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java new file mode 100644 index 000000000000..b2e296a36b93 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/om/OverlayConstraintsTests.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.om; + +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.content.om.OverlayConstraint.TYPE_DEVICE_ID; +import static android.content.om.OverlayConstraint.TYPE_DISPLAY_ID; +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import static org.testng.Assert.assertThrows; + +import android.content.om.FabricatedOverlay; +import android.content.om.OverlayConstraint; +import android.content.om.OverlayIdentifier; +import android.content.om.OverlayInfo; +import android.content.om.OverlayManager; +import android.content.om.OverlayManagerTransaction; +import android.content.res.Flags; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.TypedValue; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.List; + +@RunWith(JUnitParamsRunner.class) +public class OverlayConstraintsTests { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private OverlayManager mOverlayManager; + private UserHandle mUserHandle; + private OverlayIdentifier mOverlayIdentifier = null; + + @Before + public void setUp() throws Exception { + mOverlayManager = getApplicationContext().getSystemService(OverlayManager.class); + mUserHandle = UserHandle.of(UserHandle.myUserId()); + } + + @After + public void tearDown() throws Exception { + if (mOverlayIdentifier != null) { + OverlayManagerTransaction transaction = + new OverlayManagerTransaction.Builder() + .unregisterFabricatedOverlay(mOverlayIdentifier) + .build(); + mOverlayManager.commit(transaction); + mOverlayIdentifier = null; + } + } + + @Test + public void createOverlayConstraint_withInvalidType_fails() { + assertThrows(IllegalArgumentException.class, + () -> new OverlayConstraint(500 /* type */, 1 /* value */)); + } + + @Test + public void createOverlayConstraint_withInvalidValue_fails() { + assertThrows(IllegalArgumentException.class, + () -> new OverlayConstraint(TYPE_DEVICE_ID, -1 /* value */)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithNullConstraints_fails() { + FabricatedOverlay fabricatedOverlay = createFabricatedOverlay(); + assertThrows(NullPointerException.class, + () -> mOverlayManager.commit(new OverlayManagerTransaction.Builder() + .registerFabricatedOverlay(fabricatedOverlay) + .setEnabled(fabricatedOverlay.getIdentifier(), true /* enable */, + null /* constraints */) + .build())); + } + + @Test + @Parameters(method = "getAllConstraintLists") + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_writesConstraintsIntoOverlayInfo( + List<OverlayConstraint> constraints) throws Exception { + enableOverlay(constraints); + + OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(mOverlayIdentifier, mUserHandle); + assertNotNull(overlayInfo); + assertEquals(constraints, overlayInfo.getConstraints()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void disableOverlayWithConstraints_fails() throws Exception { + FabricatedOverlay fabricatedOverlay = createFabricatedOverlay(); + assertThrows(SecurityException.class, + () -> mOverlayManager.commit(new OverlayManagerTransaction.Builder() + .registerFabricatedOverlay(fabricatedOverlay) + .setEnabled(fabricatedOverlay.getIdentifier(), false /* enable */, + List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY))) + .build())); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithNewConstraints_updatesConstraintsIntoOverlayInfo() + throws Exception { + List<OverlayConstraint> constraints1 = + List.of(new OverlayConstraint(TYPE_DISPLAY_ID, 1 /* value*/)); + enableOverlay(constraints1); + + OverlayInfo overlayInfo1 = mOverlayManager.getOverlayInfo(mOverlayIdentifier, mUserHandle); + assertNotNull(overlayInfo1); + assertEquals(constraints1, overlayInfo1.getConstraints()); + + List<OverlayConstraint> constraints2 = List.of( + new OverlayConstraint(TYPE_DISPLAY_ID, 2 /* value */)); + enableOverlay(constraints2); + + OverlayInfo overlayInfo2 = mOverlayManager.getOverlayInfo(mOverlayIdentifier, mUserHandle); + assertNotNull(overlayInfo2); + assertEquals(overlayInfo1.overlayName, overlayInfo2.overlayName); + assertEquals(overlayInfo1.targetPackageName, overlayInfo2.targetPackageName); + assertEquals(overlayInfo1.packageName, overlayInfo2.packageName); + assertEquals(constraints2, overlayInfo2.getConstraints()); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_RRO_CONSTRAINTS) + public void enableOverlayWithConstraints_fails() throws Exception { + assertThrows(SecurityException.class, () -> enableOverlay( + List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY)))); + } + + private FabricatedOverlay createFabricatedOverlay() { + String packageName = getApplicationContext().getPackageName(); + FabricatedOverlay fabricatedOverlay = new FabricatedOverlay.Builder( + packageName, "testOverlay" /* name */, packageName) + .build(); + fabricatedOverlay.setResourceValue("string/module_2_name" /* resourceName */, + TypedValue.TYPE_STRING, "hello" /* value */, null /* configuration */); + return fabricatedOverlay; + } + + private void enableOverlay(List<OverlayConstraint> constraints) { + FabricatedOverlay fabricatedOverlay = createFabricatedOverlay(); + OverlayManagerTransaction transaction = + new OverlayManagerTransaction.Builder() + .registerFabricatedOverlay(fabricatedOverlay) + .setEnabled(fabricatedOverlay.getIdentifier(), true /* enable */, + constraints) + .build(); + mOverlayManager.commit(transaction); + mOverlayIdentifier = fabricatedOverlay.getIdentifier(); + } + + private static List<OverlayConstraint>[] getAllConstraintLists() { + return new List[]{ + Collections.emptyList(), + List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY)), + List.of(new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT)), + List.of(new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT), + new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT)) + }; + } +} diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index ec61b877a3e4..0818db1db3bc 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -30,6 +30,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Collections; import java.util.Set; import java.util.function.Consumer; @@ -203,7 +204,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI // Overlay priority changing between reboots should not affect enable state of mutable // overlays. - impl.setEnabled(IDENTIFIER, true, USER); + impl.setEnabled(IDENTIFIER, true, USER, Collections.emptyList() /* constraints */); // Reorder the overlays configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 1 /* priority */); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java index 578b888a6496..e46a806727c0 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -16,10 +16,14 @@ package com.android.server.om; +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.content.om.OverlayConstraint.TYPE_DEVICE_ID; +import static android.content.om.OverlayConstraint.TYPE_DISPLAY_ID; import static android.content.om.OverlayInfo.STATE_DISABLED; import static android.content.om.OverlayInfo.STATE_ENABLED; import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.os.OverlayablePolicy.CONFIG_SIGNATURE; +import static android.view.Display.DEFAULT_DISPLAY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,21 +31,30 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertThrows; +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.pm.UserPackage; +import android.content.res.Flags; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import androidx.test.runner.AndroidJUnit4; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -@RunWith(AndroidJUnit4.class) +@RunWith(JUnitParamsRunner.class) public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase { private static final String OVERLAY = "com.test.overlay"; @@ -62,6 +75,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes private static final String CERT_CONFIG_OK = "config_certificate_ok"; private static final String CERT_CONFIG_NOK = "config_certificate_nok"; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void testGetOverlayInfo() throws Exception { installAndAssert(overlay(OVERLAY, TARGET), USER, @@ -176,7 +192,8 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_DISABLED, IDENTIFIER, USER); - assertEquals(impl.setEnabled(IDENTIFIER, true, USER), + assertEquals(impl.setEnabled(IDENTIFIER, true /* enable */, USER, + Collections.emptyList() /* constraints */), Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_ENABLED, IDENTIFIER, USER); @@ -213,22 +230,17 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test + @RequiresFlagsDisabled(Flags.FLAG_RRO_CONSTRAINTS) public void testSetEnabledAtVariousConditions() throws Exception { - final OverlayManagerServiceImpl impl = getImpl(); - assertThrows(OverlayManagerServiceImpl.OperationFailedException.class, - () -> impl.setEnabled(IDENTIFIER, true, USER)); - - // request succeeded, and there was a change that needs to be - // propagated to the rest of the system - installAndAssert(target(TARGET), USER, - Set.of(UserPackage.of(USER, TARGET))); - installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); - assertEquals(Set.of(UserPackage.of(USER, TARGET)), - impl.setEnabled(IDENTIFIER, true, USER)); + testSetEnabledAtVariousConditions(Collections.emptyList()); + } - // request succeeded, but nothing changed - assertEquals(Set.of(), impl.setEnabled(IDENTIFIER, true, USER)); + @Test + @Parameters(method = "getConstraintLists") + @RequiresFlagsEnabled(Flags.FLAG_RRO_CONSTRAINTS) + public void testSetEnabledAtVariousConditionsWithConstraints( + List<OverlayConstraint> constraints) throws Exception { + testSetEnabledAtVariousConditions(constraints); } @Test @@ -338,4 +350,33 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes Set.of(UserPackage.of(USER, TARGET)), Set.of(UserPackage.of(USER, TARGET))); } + + private void testSetEnabledAtVariousConditions(final List<OverlayConstraint> constraints) + throws Exception { + final OverlayManagerServiceImpl impl = getImpl(); + assertThrows(OverlayManagerServiceImpl.OperationFailedException.class, + () -> impl.setEnabled(IDENTIFIER, true /* enable */, USER, constraints)); + + // request succeeded, and there was a change that needs to be + // propagated to the rest of the system + installAndAssert(target(TARGET), USER, + Set.of(UserPackage.of(USER, TARGET))); + installAndAssert(overlay(OVERLAY, TARGET), USER, + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); + assertEquals(Set.of(UserPackage.of(USER, TARGET)), + impl.setEnabled(IDENTIFIER, true /* enable */, USER, constraints)); + + // request succeeded, but nothing changed + assertEquals(Set.of(), impl.setEnabled(IDENTIFIER, true /* enable */, USER, constraints)); + } + + private static List<OverlayConstraint>[] getConstraintLists() { + return new List[]{ + Collections.emptyList(), + List.of(new OverlayConstraint(TYPE_DISPLAY_ID, DEFAULT_DISPLAY)), + List.of(new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT)), + List.of(new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT), + new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID_DEFAULT)) + }; + } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 3e82d45595d7..c69de8d2dbc4 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -32,6 +32,7 @@ import android.content.om.OverlayableInfo; import android.content.pm.UserPackage; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; +import android.os.OverlayConstraint; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -480,7 +481,7 @@ class OverlayManagerServiceImplTestsBase { @Override String createIdmap(String targetPath, String overlayPath, String overlayName, - int policies, boolean enforce, int userId) { + int policies, boolean enforce, int userId, OverlayConstraint[] constraints) { mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath), getCrc(overlayPath), targetPath, overlayName, policies, enforce)); return overlayPath; @@ -493,7 +494,7 @@ class OverlayManagerServiceImplTestsBase { @Override boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies, - boolean enforce, int userId) { + boolean enforce, int userId, OverlayConstraint[] constraints) { final IdmapHeader idmap = mIdmapFiles.get(overlayPath); if (idmap == null) { return false; diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java index 3f7eac798ccc..ad3855f4c28f 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java @@ -16,6 +16,8 @@ package com.android.server.om; +import static android.content.om.OverlayConstraint.TYPE_DEVICE_ID; +import static android.content.om.OverlayConstraint.TYPE_DISPLAY_ID; import static android.content.om.OverlayInfo.STATE_DISABLED; import static android.content.om.OverlayInfo.STATE_ENABLED; @@ -26,6 +28,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.content.om.OverlayConstraint; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.text.TextUtils; @@ -44,25 +49,32 @@ import org.xmlpull.v1.XmlPullParser; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.IntStream; 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 int USER_0 = 0; + private static final int USER_1 = 1; + + private static final int DISPLAY_ID = 1; + private static final int DEVICE_ID = 2; + + private static final OverlayConstraint CONSTRAINT_0 = + new OverlayConstraint(TYPE_DISPLAY_ID, DISPLAY_ID); + private static final OverlayConstraint CONSTRAINT_1 = + new OverlayConstraint(TYPE_DEVICE_ID, DEVICE_ID); - private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a", + private static final OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a", null /* overlayName */); - private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b", + private static final OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b", null /* overlayName */); - private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c", + private static final OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c", null /* overlayName */); private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0); @@ -72,6 +84,13 @@ public class OverlayManagerSettingsTests { 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_A_USER0_WITH_CONSTRAINTS = + createInfo(OVERLAY_A, USER_0, List.of(CONSTRAINT_0, CONSTRAINT_1)); + private static final OverlayInfo OVERLAY_B_USER0_WITH_CONSTRAINTS = + createInfo(OVERLAY_B, USER_0, List.of(CONSTRAINT_1)); + private static final OverlayInfo OVERLAY_B_USER1_WITH_CONSTRAINTS = + createInfo(OVERLAY_B, USER_1, List.of(CONSTRAINT_1)); + private static final String TARGET_PACKAGE = "com.test.target"; @Before @@ -228,6 +247,22 @@ public class OverlayManagerSettingsTests { mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0)); } + @Test + public void testSetConstraints() throws Exception { + insertSetting(OVERLAY_A_USER0); + insertSetting(OVERLAY_B_USER0); + assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), + mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0)); + + assertTrue(mSettings.setConstraints(OVERLAY_A, USER_0, + List.of(CONSTRAINT_0, CONSTRAINT_1))); + assertTrue(mSettings.setConstraints(OVERLAY_B, USER_0, List.of(CONSTRAINT_1))); + + assertListsAreEqual( + List.of(OVERLAY_A_USER0_WITH_CONSTRAINTS, OVERLAY_B_USER0_WITH_CONSTRAINTS), + mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0)); + } + // tests: persist and restore @Test @@ -291,12 +326,42 @@ public class OverlayManagerSettingsTests { } @Test + public void testPersistWithConstraints() throws Exception { + insertSetting(OVERLAY_A_USER0_WITH_CONSTRAINTS); + insertSetting(OVERLAY_B_USER1_WITH_CONSTRAINTS); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + mSettings.persist(os); + ByteArrayInputStream xml = new ByteArrayInputStream(os.toByteArray()); + + assertEquals(1, countXmlTags(xml, "overlays")); + assertEquals(2, countXmlTags(xml, "item")); + assertEquals(3, countXmlTags(xml, "constraint")); + assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName", + OVERLAY_A.getPackageName())); + assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName", + OVERLAY_B.getPackageName())); + assertEquals(1, countXmlAttributesWhere(xml, "item", "userId", + Integer.toString(USER_0))); + assertEquals(1, countXmlAttributesWhere(xml, "item", "userId", + Integer.toString(USER_1))); + assertEquals(1, countXmlAttributesWhere(xml, "constraint", "type", + TYPE_DISPLAY_ID)); + assertEquals(2, countXmlAttributesWhere(xml, "constraint", "type", + TYPE_DEVICE_ID)); + assertEquals(1, countXmlAttributesWhere(xml, "constraint", "value", + DISPLAY_ID)); + assertEquals(2, countXmlAttributesWhere(xml, "constraint", "value", + DEVICE_ID)); + } + + @Test public void testRestoreEmpty() throws Exception { final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION; final String xml = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<overlays version=\"" + version + "\" />\n"; - ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8")); + ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(UTF_8)); mSettings.restore(is); assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0); @@ -319,7 +384,45 @@ public class OverlayManagerSettingsTests { + " isStatic='false'\n" + " priority='0' />\n" + "</overlays>\n"; - ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8")); + ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(UTF_8)); + + mSettings.restore(is); + 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(identifier, 1234)); + assertTrue(oi.constraints.isEmpty()); + } + + @Test + public void testRestoreSingleUserSingleOverlayWithConstraints() throws Exception { + final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION; + final String xml = + "<?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" + + " state='" + STATE_DISABLED + "'\n" + + " isEnabled='false'\n" + + " category='test-category'\n" + + " isStatic='false'\n" + + " priority='0' >\n" + + "<constraint type='" + TYPE_DISPLAY_ID + "'\n" + + " value = '" + DISPLAY_ID + "' />\n" + + "<constraint type='" + TYPE_DEVICE_ID + "'\n" + + " value = '" + DEVICE_ID + "' />\n" + + "</item>\n" + + "</overlays>\n"; + ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(UTF_8)); mSettings.restore(is); final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test"); @@ -332,6 +435,7 @@ public class OverlayManagerSettingsTests { assertEquals(1234, oi.userId); assertEquals(STATE_DISABLED, oi.state); assertFalse(mSettings.getEnabled(identifier, 1234)); + assertListsAreEqual(List.of(CONSTRAINT_0, CONSTRAINT_1), oi.constraints); } @Test @@ -352,6 +456,24 @@ public class OverlayManagerSettingsTests { assertEquals(OVERLAY_B_USER1, b); } + @Test + public void testPersistAndRestoreWithConstraints() throws Exception { + insertSetting(OVERLAY_A_USER0_WITH_CONSTRAINTS); + insertSetting(OVERLAY_B_USER1_WITH_CONSTRAINTS); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + mSettings.persist(os); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + OverlayManagerSettings newSettings = new OverlayManagerSettings(); + newSettings.restore(is); + + OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0); + assertEquals(OVERLAY_A_USER0_WITH_CONSTRAINTS, a); + + OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1); + assertEquals(OVERLAY_B_USER1_WITH_CONSTRAINTS, b); + } + private int countXmlTags(InputStream in, String tagToLookFor) throws Exception { in.reset(); int count = 0; @@ -384,11 +506,30 @@ public class OverlayManagerSettingsTests { return count; } + private int countXmlAttributesWhere(InputStream in, String tag, String attr, int value) + throws Exception { + in.reset(); + int count = 0; + TypedXmlPullParser parser = Xml.resolvePullParser(in); + int event = parser.getEventType(); + while (event != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG && tag.equals(parser.getName())) { + int v = parser.getAttributeInt(null, attr); + if (value == v) { + count++; + } + } + event = parser.next(); + } + return count; + } + 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); + mSettings.setConstraints(oi.getOverlayIdentifier(), oi.userId, oi.constraints); } private static void assertContains(final OverlayManagerSettings settings, @@ -417,42 +558,28 @@ public class OverlayManagerSettingsTests { } private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) { + return createInfo(identifier, userId, Collections.emptyList()); + } + + private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId, + @NonNull List<OverlayConstraint> constraints) { return new OverlayInfo( identifier.getPackageName(), identifier.getOverlayName(), "com.test.target", - null, - "some-category", - "/data/app/" + identifier + "/base.apk", + null /* targetOverlayableName */, + "some-category" /* category */, + "/data/app/" + identifier + "/base.apk" /* baseCodePath */, STATE_DISABLED, userId, - 0, - true, - false); - } - - private static void assertContains(int[] haystack, int needle) { - List<Integer> list = IntStream.of(haystack) - .boxed() - .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); - if (!list.contains(needle)) { - fail(String.format("integer array [%s] does not contain value %s", - TextUtils.join(",", list), needle)); - } - } - - private static void assertDoesNotContain(int[] haystack, int needle) { - List<Integer> list = IntStream.of(haystack) - .boxed() - .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); - if (list.contains(needle)) { - fail(String.format("integer array [%s] contains value %s", - TextUtils.join(",", list), needle)); - } + 0 /* priority */, + true /* isMutable */, + false /* isFabricated */, + constraints); } - private static void assertListsAreEqual( - @NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) { + private static <T> void assertListsAreEqual( + @NonNull List<T> expected, @Nullable List<T> actual) { if (!expected.equals(actual)) { fail(String.format("lists [%s] and [%s] differ", TextUtils.join(",", expected), TextUtils.join(",", actual))); |