diff options
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))); |