Add constraints for enabling RROs
This change introduces hidden API's for setting constraints
for enabling RROs. Currently, the constraints are of the following
types:
1. Display id constraint: This can be set for a RRO if the RRO is
desired to be applied on only the apps running on that specific
display.
2. Device id constraint: This can be set for a RRO if the RRO is
desired to be applied on only the apps running on that specific
device (for apps running on the default device, this would be
Context#DEVICE_ID_DEFAULT, and for apps running on a virtual device,
this would be the id of the virtual device).
An overlay would be enabled when any of the given constraints are met.
Constraints can only be set while enabling a RRO. Re-enabling a RRO
with different constraints updates the constraints for the RRO.
This change facilitates the writing of the constraints for RROs
into the correspodning idmap files, and also persists them as part
of the settings used by OverlayManagerService. The filtering of
resource overlays based on constraints during resource resolution
will be done in a follow-up CL.
Test: atest FrameworksServicesTests
Test: atest idmap2_tests
Test: atest libandroidfw_tests
Test: atest CtsResourcesTestCases
Bug: 371801644
Flag: android.content.res.rro_constraints
Change-Id: I0cad58bfb5b9b90105e2ef839c58147b9a50767c
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index d9ff190..f6bee52 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -348,6 +348,7 @@
"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 d5f1b89..d949401 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -35,6 +35,7 @@
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 @@
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 2608c69..70a2ed1 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -39,6 +39,7 @@
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 @@
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 6902d6d..2495c55 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -46,6 +46,8 @@
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 @@
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::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 @@
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 @@
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::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 @@
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 272ec6b..344a77f 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 @@
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 0000000..8fce3d6
--- /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 2bbfba9..4f4f075 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 @@
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
+import android.os.OverlayConstraint;
/**
* @hide
@@ -30,13 +31,15 @@
@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 57af1b6..3009293 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -32,6 +32,7 @@
~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 b0ba019..1f15daf 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 @@
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 @@
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 @@
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 @@
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 ed18d9c..033ef85 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -36,6 +36,7 @@
~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 849ba11..bd27c0d 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -37,6 +37,7 @@
~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 00ef0c7..b029aea 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -63,6 +63,14 @@
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 7680109..556ca22 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -182,6 +182,26 @@
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 @@
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 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 @@
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 @@
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 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 eb94582..0ec31f4 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -58,6 +58,19 @@
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 9d04a7f..41a3da3 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -55,6 +55,14 @@
}
}
+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 7f9c468..26888ab 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -31,6 +31,7 @@
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 @@
// 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 f1eeab9..76cccb5 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -58,6 +58,8 @@
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 5a7fcd5..760bbb3 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -105,6 +105,7 @@
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 7093614..4de2a6b 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -68,7 +68,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 3d3d82a..2f42f79 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -42,8 +42,10 @@
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 7fae1c6..d5aafe6 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -55,8 +55,10 @@
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 @@
(*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 @@
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 @@
(*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 @@
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 2b4ebd1..6f645bd 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -34,7 +34,7 @@
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 @@
// 0x4c string contents "debug\0\0\0" (padded to word alignment)
0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
- // DATA HEADER
- // 0x54: target_entry_count
- 0x03, 0x00, 0x00, 0x00,
+ // CONSTRAINTS
+ // 0x54: constraints_count
+ 0x02, 0x00, 0x00, 0x00,
- // 0x58: target_inline_entry_count
+ // 0x58: constraint_type
+ 0x00, 0x00, 0x00, 0x00,
+
+ // 0x5c: constraint_value
0x01, 0x00, 0x00, 0x00,
- // 0x5c: target_inline_entry_value_count
+ // 0x60: constraint_type
+ 0x01, 0x00, 0x00, 0x00,
+
+ // 0x64: constraint_value
+ 0x02, 0x00, 0x00, 0x00,
+
+ // DATA HEADER
+ // 0x68: target_entry_count
+ 0x03, 0x00, 0x00, 0x00,
+
+ // 0x6c: target_inline_entry_count
+ 0x01, 0x00, 0x00, 0x00,
+
+ // 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 122ab48..d865ba7 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 @@
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 0000000..95aac80
--- /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 0000000..c1902de
--- /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 2e89856..4977c820 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -30,6 +30,9 @@
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 @@
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 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 @@
@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 @@
this.priority = priority;
this.isMutable = isMutable;
this.isFabricated = isFabricated;
+ this.constraints = constraints;
ensureValidState();
}
@@ -291,6 +312,7 @@
priority = source.readInt();
isMutable = source.readBoolean();
isFabricated = source.readBoolean();
+ constraints = Arrays.asList(source.createTypedArray(OverlayConstraint.CREATOR));
ensureValidState();
}
@@ -395,6 +417,17 @@
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 @@
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 @@
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 @@
* 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 @@
* debugging purposes.
*
* @return a human readable String representing the state.
+ *
* @hide
*/
public static String stateToString(@State int state) {
@@ -522,6 +561,7 @@
: 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 @@
if (!baseCodePath.equals(other.baseCodePath)) {
return false;
}
- return true;
+ return Objects.equals(constraints, other.constraints);
}
/**
@@ -584,6 +624,7 @@
+ ", 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 6db7dfe..fd9bfa2 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -59,7 +59,6 @@
public class OverlayManager {
private final IOverlayManager mService;
- private final Context mContext;
private final OverlayManagerImpl mOverlayManagerImpl;
/**
@@ -137,7 +136,6 @@
*/
@SuppressLint("ReferencesHidden")
public OverlayManager(@NonNull Context context, @Nullable IOverlayManager service) {
- mContext = context;
mService = service;
mOverlayManagerImpl = new OverlayManagerImpl(context);
}
@@ -161,7 +159,7 @@
* @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 @@
* @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 @@
}
/**
+ * 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 @@
@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 87b2e93..f9eb5e0 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -18,8 +18,6 @@
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.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 @@
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 @@
* Get the iterator of requests
*
* @return the iterator of request
+ *
* @hide
*/
@SuppressLint("ReferencesHidden")
@@ -145,6 +148,8 @@
@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 @@
@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 @@
/**
* 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 @@
/**
* @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 @@
* 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 @@
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>() {
+ new Parcelable.Creator<>() {
- @Override
- public OverlayManagerTransaction createFromParcel(Parcel source) {
- return new OverlayManagerTransaction(source);
- }
+ @Override
+ public OverlayManagerTransaction createFromParcel(Parcel source) {
+ return new OverlayManagerTransaction(source);
+ }
- @Override
- public OverlayManagerTransaction[] newArray(int size) {
- return new OverlayManagerTransaction[size];
- }
- };
+ @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 ecb4bb1..5cbb596 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -76,6 +76,14 @@
}
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 5d4e6a0..4b33652 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.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 @@
*
* @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 3ecd82b..095be57 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -55,6 +55,13 @@
// 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 @@
#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 @@
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 @@
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 @@
// 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 de9991a..978bc76 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -290,11 +290,11 @@
}
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 @@
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 @@
// 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 @@
}
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 ac75eb3..d1db13f 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -35,6 +35,7 @@
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 @@
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 @@
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 e330410..8b2871c 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 @@
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
index 7e4b261..6bd57c8 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index d33c860..9e31151 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.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 @@
}
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 @@
}
return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
- policies, enforce, userId);
+ policies, enforce, userId, constraints);
}
}
@@ -165,8 +166,8 @@
}
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 @@
}
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 86d05d9..4e86aa0 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 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 @@
*/
@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 @@
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 @@
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 8710438..847da86 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.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 @@
@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 @@
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 @@
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 0e9ec4d..bcebe0b 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 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 @@
+ 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 @@
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 @@
}
// 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 @@
@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 @@
}
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
* 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 @@
}
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 @@
&& !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 @@
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 f9758fc..4e5c732 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 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 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 @@
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 @@
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 @@
mIsMutable = isMutable;
mPriority = priority;
mIsFabricated = isFabricated;
+ Objects.requireNonNull(constraints);
+ mConstraints = constraints;
}
private String getTargetPackageName() {
@@ -668,11 +709,26 @@
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 0000000..b2e296a
--- /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 ec61b87..0818db1 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.Test;
import org.junit.runner.RunWith;
+import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;
@@ -203,7 +204,7 @@
// 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 578b888..e46a806 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.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 @@
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 @@
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 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_RRO_CONSTRAINTS)
public void testSetEnabledAtVariousConditions() throws Exception {
- final OverlayManagerServiceImpl impl = getImpl();
- assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
- () -> impl.setEnabled(IDENTIFIER, true, USER));
+ testSetEnabledAtVariousConditions(Collections.emptyList());
+ }
- // 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));
-
- // 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 @@
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 3e82d45..c69de8d 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.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 @@
@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 @@
@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 3f7eac7..ad3855f 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.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 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 OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
+ 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 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 @@
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 @@
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 @@
}
@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,7 @@
+ " 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");
@@ -332,6 +397,45 @@
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");
+ 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));
+ assertListsAreEqual(List.of(CONSTRAINT_0, CONSTRAINT_1), oi.constraints);
}
@Test
@@ -352,6 +456,24 @@
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 @@
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 @@
}
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);
+ 0 /* priority */,
+ true /* isMutable */,
+ false /* isFabricated */,
+ constraints);
}
- 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));
- }
- }
-
- 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)));