RRO: Added partition policies for overlays

<overlayable> tags can now have policy elements that indicate which
partition the overlay apk must reside on in order to be allowed to
overlay a resource. This change only adds parsing of <policy> and
encoding of policy in the proto ResourceTable. A later change will add
the encoding of policy and overlayable in the binary APK.

<overlayable>
  <policy type="system|vendor|product|product_services|public" >
    <item type="string" name="oof" />
  </policy>
</overlayable>

Bug: 110869880
Test: make aapt2_tests
Change-Id: I8d4ed7b0e01f981149c6e3190af1681073b79b03
This commit is contained in:
Ryan Mitchell
2018-10-29 02:21:50 -07:00
parent cfc152af9c
commit e4e989ccba
18 changed files with 732 additions and 147 deletions

View File

@ -306,8 +306,29 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
break;
}
if (entry->overlayable) {
printer->Print(" OVERLAYABLE");
for (size_t i = 0; i < entry->overlayable_declarations.size(); i++) {
printer->Print((i == 0) ? " " : "|");
printer->Print("OVERLAYABLE");
if (entry->overlayable_declarations[i].policy) {
switch (entry->overlayable_declarations[i].policy.value()) {
case Overlayable::Policy::kProduct:
printer->Print("_PRODUCT");
break;
case Overlayable::Policy::kProductServices:
printer->Print("_PRODUCT_SERVICES");
break;
case Overlayable::Policy::kSystem:
printer->Print("_SYSTEM");
break;
case Overlayable::Policy::kVendor:
printer->Print("_VENDOR");
break;
case Overlayable::Policy::kPublic:
printer->Print("_PUBLIC");
break;
}
}
}
printer->Println();

View File

@ -99,7 +99,7 @@ struct ParsedResource {
ResourceId id;
Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool allow_new = false;
bool overlayable = false;
std::vector<Overlayable> overlayable_declarations;
std::string comment;
std::unique_ptr<Value> value;
@ -133,11 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
}
}
if (res->overlayable) {
Overlayable overlayable;
overlayable.source = res->source;
overlayable.comment = res->comment;
if (!table->SetOverlayable(res->name, overlayable, diag)) {
for (auto& overlayable : res->overlayable_declarations) {
if (!table->AddOverlayable(res->name, overlayable, diag)) {
return false;
}
}
@ -673,7 +670,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
if (can_be_bag) {
const auto bag_iter = elToBagMap.find(resource_type);
if (bag_iter != elToBagMap.end()) {
// Ensure we have a name (unless this is a <public-group>).
// Ensure we have a name (unless this is a <public-group> or <overlayable>).
if (resource_type != "public-group" && resource_type != "overlayable") {
if (!maybe_name) {
diag_->Error(DiagMessage(out_resource->source)
@ -1062,74 +1059,137 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out
bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config << "' for <overlayable> tag");
<< "ignoring configuration '" << out_resource->config
<< "' for <overlayable> tag");
}
if (Maybe<StringPiece> maybe_policy = xml::FindNonEmptyAttribute(parser, "policy")) {
const StringPiece& policy = maybe_policy.value();
if (policy != "system") {
diag_->Error(DiagMessage(out_resource->source)
<< "<overlayable> has invalid policy '" << policy << "'");
return false;
}
}
std::string comment;
std::vector<Overlayable::Policy> policies;
bool error = false;
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Skip text/comments.
const size_t start_depth = parser->depth();
while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
xml::XmlPullParser::Event event = parser->event();
if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
// Break the loop when exiting the overyabale element
break;
} else if (event == xml::XmlPullParser::Event::kEndElement
&& parser->depth() == start_depth + 1) {
// Clear the current policies when exiting the policy element
policies.clear();
continue;
} else if (event == xml::XmlPullParser::Event::kComment) {
// Get the comment of individual item elements
comment = parser->comment();
continue;
} else if (event != xml::XmlPullParser::Event::kStartElement) {
// Skip to the next element
continue;
}
const Source item_source = source_.WithLine(parser->line_number());
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
const std::string& element_namespace = parser->element_namespace();
if (element_namespace.empty() && element_name == "item") {
Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
if (!maybe_name) {
diag_->Error(DiagMessage(item_source)
<< "<item> within an <overlayable> tag must have a 'name' attribute");
if (!ParseOverlayableItem(parser, policies, comment, out_resource)) {
error = true;
continue;
}
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
if (!maybe_type) {
diag_->Error(DiagMessage(item_source)
<< "<item> within an <overlayable> tag must have a 'type' attribute");
} else if (element_namespace.empty() && element_name == "policy") {
if (!policies.empty()) {
// If the policy list is not empty, then we are currently inside a policy element
diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested");
error = true;
continue;
break;
} else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
// Parse the polices separated by vertical bar characters to allow for specifying multiple
// policies at once
for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
if (trimmed_part == "public") {
policies.push_back(Overlayable::Policy::kPublic);
} else if (trimmed_part == "product") {
policies.push_back(Overlayable::Policy::kProduct);
} else if (trimmed_part == "product_services") {
policies.push_back(Overlayable::Policy::kProductServices);
} else if (trimmed_part == "system") {
policies.push_back(Overlayable::Policy::kSystem);
} else if (trimmed_part == "vendor") {
policies.push_back(Overlayable::Policy::kVendor);
} else {
diag_->Error(DiagMessage(out_resource->source)
<< "<policy> has unsupported type '" << trimmed_part << "'");
error = true;
continue;
}
}
}
const ResourceType* type = ParseResourceType(maybe_type.value());
if (type == nullptr) {
diag_->Error(DiagMessage(out_resource->source)
<< "invalid resource type '" << maybe_type.value()
<< "' in <item> within an <overlayable>");
error = true;
continue;
}
ParsedResource child_resource;
child_resource.name.type = *type;
child_resource.name.entry = maybe_name.value().to_string();
child_resource.source = item_source;
child_resource.overlayable = true;
if (options_.visibility) {
child_resource.visibility_level = options_.visibility.value();
}
out_resource->child_resources.push_back(std::move(child_resource));
xml::XmlPullParser::SkipCurrentElement(parser);
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> in "
<< " <overlayable>");
error = true;
break;
}
}
return !error;
}
bool ResourceParser::ParseOverlayableItem(xml::XmlPullParser* parser,
const std::vector<Overlayable::Policy>& policies,
const std::string& comment,
ParsedResource* out_resource) {
const Source item_source = source_.WithLine(parser->line_number());
Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
if (!maybe_name) {
diag_->Error(DiagMessage(item_source)
<< "<item> within an <overlayable> tag must have a 'name' attribute");
return false;
}
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
if (!maybe_type) {
diag_->Error(DiagMessage(item_source)
<< "<item> within an <overlayable> tag must have a 'type' attribute");
return false;
}
const ResourceType* type = ParseResourceType(maybe_type.value());
if (type == nullptr) {
diag_->Error(DiagMessage(out_resource->source)
<< "invalid resource type '" << maybe_type.value()
<< "' in <item> within an <overlayable>");
return false;
}
ParsedResource child_resource;
child_resource.name.type = *type;
child_resource.name.entry = maybe_name.value().to_string();
child_resource.source = item_source;
if (policies.empty()) {
Overlayable overlayable;
overlayable.source = item_source;
overlayable.comment = comment;
child_resource.overlayable_declarations.push_back(overlayable);
} else {
for (Overlayable::Policy policy : policies) {
Overlayable overlayable;
overlayable.policy = policy;
overlayable.source = item_source;
overlayable.comment = comment;
child_resource.overlayable_declarations.push_back(overlayable);
}
}
if (options_.visibility) {
child_resource.visibility_level = options_.visibility.value();
}
out_resource->child_resources.push_back(std::move(child_resource));
return true;
}
bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (ParseSymbolImpl(parser, out_resource)) {
out_resource->visibility_level = Visibility::Level::kUndefined;

View File

@ -96,6 +96,10 @@ class ResourceParser {
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayableItem(xml::XmlPullParser* parser,
const std::vector<Overlayable::Policy>& policies,
const std::string& comment,
ParsedResource* out_resource);
bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);

View File

@ -891,56 +891,162 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) {
ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)"));
}
TEST_F(ResourceParserTest, ParseOverlayableTagWithSystemPolicy) {
std::string input = R"(
<overlayable policy="illegal_policy">
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable policy="system">
<item name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable policy="system">
<item type="attr" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable policy="system">
<item type="bad_type" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(<overlayable policy="system" />)";
TEST_F(ResourceParserTest, ParseOverlayable) {
std::string input = R"(<overlayable />)";
EXPECT_TRUE(TestParse(input));
input = R"(<overlayable />)";
EXPECT_TRUE(TestParse(input));
input = R"(
<overlayable policy="system">
<item type="string" name="foo" />
<item type="dimen" name="foo" />
</overlayable>)";
ASSERT_TRUE(TestParse(input));
input = R"(
<overlayable>
<item type="string" name="bar" />
<item type="string" name="foo" />
<item type="drawable" name="bar" />
</overlayable>)";
ASSERT_TRUE(TestParse(input));
Maybe<ResourceTable::SearchResult> search_result =
table_.FindResource(test::ParseNameOrDie("string/bar"));
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_TRUE(search_result.value().entry->overlayable);
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
}
TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
std::string input = R"(<overlayable />)";
EXPECT_TRUE(TestParse(input));
input = R"(
<overlayable>
<item type="string" name="foo" />
<policy type="product">
<item type="string" name="bar" />
</policy>
<policy type="product_services">
<item type="string" name="baz" />
</policy>
<policy type="system">
<item type="string" name="fiz" />
</policy>
<policy type="vendor">
<item type="string" name="fuz" />
</policy>
<policy type="public">
<item type="string" name="faz" />
</policy>
</overlayable>)";
ASSERT_TRUE(TestParse(input));
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProduct));
search_result = table_.FindResource(test::ParseNameOrDie("string/baz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProductServices));
search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kSystem));
search_result = table_.FindResource(test::ParseNameOrDie("string/fuz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kVendor));
search_result = table_.FindResource(test::ParseNameOrDie("string/faz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kPublic));
}
TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) {
std::string input = R"(
<overlayable>
<policy type="illegal_policy">
<item type="string" name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable>
<policy type="product">
<item name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable>
<policy type="vendor">
<item type="string" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) {
std::string input = R"(
<overlayable>
<policy type="vendor|product_services">
<item type="string" name="foo" />
</policy>
<policy type="product|system">
<item type="string" name="bar" />
</policy>
</overlayable>)";
ASSERT_TRUE(TestParse(input));
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kVendor));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kProductServices));
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProduct));
EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kSystem));
}
TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
@ -950,6 +1056,85 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable>
<item type="string" name="foo" />
</overlayable>
<overlayable>
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable">
<policy type="product">
<item type="string" name="foo" />
<item type="string" name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable>
<policy type="product">
<item type="string" name="foo" />
</policy>
</overlayable>
<overlayable>
<policy type="product">
<item type="string" name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, PolicyAndNonPolicyOverlayableError) {
std::string input = R"(
<overlayable policy="product">
<item type="string" name="foo" />
</overlayable>
<overlayable policy="">
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable policy="">
<item type="string" name="foo" />
</overlayable>
<overlayable policy="product">
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, DuplicateOverlayableMultiplePolicyError) {
std::string input = R"(
<overlayable>
<policy type="vendor|product">
<item type="string" name="foo" />
</policy>
</overlayable>
<overlayable>
<policy type="product_services|vendor">
<item type="string" name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, NestPolicyInOverlayableError) {
std::string input = R"(
<overlayable>
<policy type="vendor|product">
<policy type="product_services">
<item type="string" name="foo" />
</policy>
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, ParseIdItem) {

View File

@ -625,17 +625,17 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew&
return true;
}
bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
bool ResourceTable::AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
IDiagnostics* diag) {
return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
return AddOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
}
bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
bool ResourceTable::AddOverlayableMangled(const ResourceNameRef& name,
const Overlayable& overlayable, IDiagnostics* diag) {
return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
return AddOverlayableImpl(name, overlayable, SkipNameValidator, diag);
}
bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
NameValidator name_validator, IDiagnostics* diag) {
CHECK(diag != nullptr);
@ -646,13 +646,28 @@ bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overla
ResourceTablePackage* package = FindOrCreatePackage(name.package);
ResourceTableType* type = package->FindOrCreateType(name.type);
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
if (entry->overlayable) {
diag->Error(DiagMessage(overlayable.source)
<< "duplicate overlayable declaration for resource '" << name << "'");
diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
return false;
for (auto& overlayable_declaration : entry->overlayable_declarations) {
// An overlayable resource cannot be declared twice with the same policy
if (overlayable.policy == overlayable_declaration.policy) {
diag->Error(DiagMessage(overlayable.source)
<< "duplicate overlayable declaration for resource '" << name << "'");
diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
return false;
}
// An overlayable resource cannot be declared once with a policy and without a policy because
// the policy becomes unused
if (!overlayable.policy || !overlayable_declaration.policy) {
diag->Error(DiagMessage(overlayable.source)
<< "overlayable resource '" << name << "'"
<< " declared once with a policy and once with no policy");
diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
return false;
}
}
entry->overlayable = overlayable;
entry->overlayable_declarations.push_back(overlayable);
return true;
}
@ -688,7 +703,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
new_entry->id = entry->id;
new_entry->visibility = entry->visibility;
new_entry->allow_new = entry->allow_new;
new_entry->overlayable = entry->overlayable;
new_entry->overlayable_declarations = entry->overlayable_declarations;
for (const auto& config_value : entry->values) {
ResourceConfigValue* new_value =

View File

@ -57,8 +57,27 @@ struct AllowNew {
std::string comment;
};
// The policy dictating whether an entry is overlayable at runtime by RROs.
// Represents a declaration that a resource is overayable at runtime.
struct Overlayable {
// Represents the types overlays that are allowed to overlay the resource.
enum class Policy {
// The resource can be overlaid by any overlay.
kPublic,
// The resource can be overlaid by any overlay on the system partition.
kSystem,
// The resource can be overlaid by any overlay on the vendor partition.
kVendor,
// The resource can be overlaid by any overlay on the product partition.
kProduct,
// The resource can be overlaid by any overlay on the product services partition.
kProductServices,
};
Maybe<Policy> policy;
Source source;
std::string comment;
};
@ -96,7 +115,8 @@ class ResourceEntry {
Maybe<AllowNew> allow_new;
Maybe<Overlayable> overlayable;
// The declarations of this resource as overlayable for RROs
std::vector<Overlayable> overlayable_declarations;
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
@ -226,9 +246,9 @@ class ResourceTable {
bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
const ResourceId& res_id, IDiagnostics* diag);
bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
bool AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
IDiagnostics* diag);
bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
bool AddOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
IDiagnostics* diag);
bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
@ -303,7 +323,7 @@ class ResourceTable {
bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
NameValidator name_validator, IDiagnostics* diag);
bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
bool AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
NameValidator name_validator, IDiagnostics* diag);
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,

View File

@ -242,21 +242,69 @@ TEST(ResourceTableTest, SetAllowNew) {
ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
}
TEST(ResourceTableTest, SetOverlayable) {
TEST(ResourceTableTest, AddOverlayable) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
Overlayable overlayable;
overlayable.policy = Overlayable::Policy::kProduct;
overlayable.comment = "first";
ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
ASSERT_TRUE(result);
ASSERT_TRUE(result.value().entry->overlayable);
ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProduct));
overlayable.comment = "second";
ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
Overlayable overlayable2;
overlayable2.comment = "second";
overlayable2.policy = Overlayable::Policy::kProductServices;
ASSERT_TRUE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
result = table.FindResource(name);
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProduct));
ASSERT_THAT(result.value().entry->overlayable_declarations[1].comment, StrEq("second"));
ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kProductServices));
}
TEST(ResourceTableTest, AddDuplicateOverlayableFail) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
Overlayable overlayable;
overlayable.policy = Overlayable::Policy::kProduct;
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
Overlayable overlayable2;
overlayable2.policy = Overlayable::Policy::kProduct;
ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
}
TEST(ResourceTableTest, AddOverlayablePolicyAndNoneFirstFail) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
ASSERT_TRUE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
Overlayable overlayable2;
overlayable2.policy = Overlayable::Policy::kProduct;
ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
}
TEST(ResourceTableTest, AddOverlayablePolicyAndNoneLastFail) {
ResourceTable table;
const ResourceName name = test::ParseNameOrDie("android:string/foo");
Overlayable overlayable;
overlayable.policy = Overlayable::Policy::kProduct;
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
ASSERT_FALSE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
}
TEST(ResourceTableTest, AllowDuplictaeResourcesNames) {

View File

@ -133,13 +133,25 @@ message AllowNew {
string comment = 2;
}
// Whether a resource is overlayable by runtime resource overlays (RRO).
// Represents a declaration that a resource is overayable at runtime.
message Overlayable {
enum Policy {
NONE = 0;
PUBLIC = 1;
SYSTEM = 2;
VENDOR = 3;
PRODUCT = 4;
PRODUCT_SERVICES = 5;
}
// Where this declaration was defined in source.
Source source = 1;
// Any comment associated with the declaration.
string comment = 2;
// The policy of the overlayable declaration
Policy policy = 3;
}
// An entry ID in the range [0x0000, 0xffff].
@ -169,7 +181,7 @@ message Entry {
AllowNew allow_new = 4;
// Whether this resource can be overlaid by a runtime resource overlay (RRO).
Overlayable overlayable = 5;
repeated Overlayable overlayable = 5;
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.

View File

@ -398,7 +398,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
Overlayable overlayable;
overlayable.source = source_.WithLine(0);
if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
if (!table_->AddOverlayableMangled(name, overlayable, diag_)) {
return false;
}
}

View File

@ -446,7 +446,7 @@ class PackageFlattener {
config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
}
if (entry->overlayable) {
if (!entry->overlayable_declarations.empty()) {
config_masks[entry->id.value()] |=
util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
}

View File

@ -634,7 +634,7 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) {
.AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
.Build();
ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
Overlayable{}, test::GetDiagnostics()));
ResTable res_table;

View File

@ -437,15 +437,37 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
entry->allow_new = std::move(allow_new);
}
if (pb_entry.has_overlayable()) {
const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
for (const pb::Overlayable& pb_overlayable : pb_entry.overlayable()) {
Overlayable overlayable;
switch (pb_overlayable.policy()) {
case pb::Overlayable::NONE:
overlayable.policy = {};
break;
case pb::Overlayable::PUBLIC:
overlayable.policy = Overlayable::Policy::kPublic;
break;
case pb::Overlayable::PRODUCT:
overlayable.policy = Overlayable::Policy::kProduct;
break;
case pb::Overlayable::PRODUCT_SERVICES:
overlayable.policy = Overlayable::Policy::kProductServices;
break;
case pb::Overlayable::SYSTEM:
overlayable.policy = Overlayable::Policy::kSystem;
break;
case pb::Overlayable::VENDOR:
overlayable.policy = Overlayable::Policy::kVendor;
break;
default:
*out_error = "unknown overlayable policy";
return false;
}
if (pb_overlayable.has_source()) {
DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
}
overlayable.comment = pb_overlayable.comment();
entry->overlayable = std::move(overlayable);
entry->overlayable_declarations.push_back(overlayable);
}
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),

View File

@ -310,11 +310,31 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
pb_allow_new->set_comment(entry->allow_new.value().comment);
}
if (entry->overlayable) {
pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
for (const Overlayable& overlayable : entry->overlayable_declarations) {
pb::Overlayable* pb_overlayable = pb_entry->add_overlayable();
if (overlayable.policy) {
switch (overlayable.policy.value()) {
case Overlayable::Policy::kPublic:
pb_overlayable->set_policy(pb::Overlayable::PUBLIC);
break;
case Overlayable::Policy::kProduct:
pb_overlayable->set_policy(pb::Overlayable::PRODUCT);
break;
case Overlayable::Policy::kProductServices:
pb_overlayable->set_policy(pb::Overlayable::PRODUCT_SERVICES);
break;
case Overlayable::Policy::kSystem:
pb_overlayable->set_policy(pb::Overlayable::SYSTEM);
break;
case Overlayable::Policy::kVendor:
pb_overlayable->set_policy(pb::Overlayable::VENDOR);
break;
}
}
SerializeSourceToPb(overlayable.source, &source_pool,
pb_overlayable->mutable_source());
pb_overlayable->set_comment(entry->overlayable.value().comment);
pb_overlayable->set_comment(overlayable.comment);
}
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {

View File

@ -93,7 +93,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
// Make an overlayable resource.
ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
Overlayable{}, test::GetDiagnostics()));
pb::ResourceTable pb_table;
@ -106,7 +106,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
ResourceTable new_table;
std::string error;
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
EXPECT_THAT(error, IsEmpty());
Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
@ -160,7 +160,8 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
EXPECT_TRUE(search_result.value().entry->overlayable);
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
}
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
@ -464,4 +465,59 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) {
"night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23");
}
TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kSystem)
.AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kProduct)
.AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kProductServices)
.AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kVendor)
.AddOverlayable("com.app.a:bool/baz", Overlayable::Policy::kPublic)
.AddOverlayable("com.app.a:bool/biz", {})
.AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
.Build();
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
MockFileCollection files;
ResourceTable new_table;
std::string error;
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
EXPECT_THAT(error, IsEmpty());
Maybe<ResourceTable::SearchResult> result =
new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kSystem));
EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kProduct));
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProductServices));
EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kVendor));
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kPublic));
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
EXPECT_FALSE(result.value().entry->overlayable_declarations[0].policy);
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
ASSERT_TRUE(result);
EXPECT_THAT(result.value().entry->overlayable_declarations.size(), Eq(0));
}
} // namespace aapt

View File

@ -101,7 +101,7 @@ static bool MergeType(IAaptContext* context, const Source& src, ResourceTableTyp
return true;
}
static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
static bool MergeEntry(IAaptContext* context, const Source& src,
ResourceEntry* dst_entry, ResourceEntry* src_entry,
bool strict_visibility) {
if (strict_visibility
@ -134,17 +134,35 @@ static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
dst_entry->allow_new = std::move(src_entry->allow_new);
}
if (src_entry->overlayable) {
if (dst_entry->overlayable && !overlay) {
context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
<< "duplicate overlayable declaration for resource '"
<< src_entry->name << "'");
context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
<< "previous declaration here");
return false;
for (auto& src_overlayable : src_entry->overlayable_declarations) {
for (auto& dst_overlayable : dst_entry->overlayable_declarations) {
// An overlayable resource cannot be declared twice with the same policy
if (src_overlayable.policy == dst_overlayable.policy) {
context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
<< "duplicate overlayable declaration for resource '"
<< src_entry->name << "'");
context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
<< "previous declaration here");
return false;
}
// An overlayable resource cannot be declared once with a policy and without a policy because
// the policy becomes unused
if (!src_overlayable.policy || !dst_overlayable.policy) {
context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
<< "overlayable resource '" << src_entry->name
<< "' declared once with a policy and once with no "
<< "policy");
context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
<< "previous declaration here");
return false;
}
}
dst_entry->overlayable = std::move(src_entry->overlayable);
}
dst_entry->overlayable_declarations.insert(dst_entry->overlayable_declarations.end(),
src_entry->overlayable_declarations.begin(),
src_entry->overlayable_declarations.end());
return true;
}
@ -244,7 +262,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
continue;
}
if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get(), options_.strict_visibility)) {
if (!MergeEntry(context_, src, dst_entry, src_entry.get(), options_.strict_visibility)) {
error = true;
continue;
}

View File

@ -436,4 +436,97 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
}
TEST_F(TableMergerTest, AddOverlayable) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProductServices)
.Build();
ResourceTable final_table;
TableMergerOptions options;
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
Maybe<ResourceTable::SearchResult> result = final_table.FindResource(name);
ASSERT_TRUE(result);
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
Eq(Overlayable::Policy::kProduct));
ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
Eq(Overlayable::Policy::kProductServices));
}
TEST_F(TableMergerTest, AddDuplicateOverlayableFail) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
.Build();
ResourceTable final_table;
TableMergerOptions options;
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", {})
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
.Build();
ResourceTable final_table;
TableMergerOptions options;
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneLastFail) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
.AddOverlayable("bool/foo", {})
.Build();
ResourceTable final_table;
TableMergerOptions options;
options.auto_add_overlay = true;
TableMerger merger(context_.get(), &final_table, options);
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
} // namespace aapt

View File

@ -135,6 +135,15 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na
return *this;
}
ResourceTableBuilder& ResourceTableBuilder::AddOverlayable(const StringPiece& name,
const Maybe<Overlayable::Policy> p) {
ResourceName res_name = ParseNameOrDie(name);
Overlayable overlayable;
overlayable.policy = p;
CHECK(table_->AddOverlayable(res_name, overlayable, GetDiagnostics()));
return *this;
}
StringPool* ResourceTableBuilder::string_pool() {
return &table_->string_pool;
}

View File

@ -73,6 +73,8 @@ class ResourceTableBuilder {
const ResourceId& id, std::unique_ptr<Value> value);
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
Visibility::Level level, bool allow_new = false);
ResourceTableBuilder& AddOverlayable(const android::StringPiece& name,
Maybe<Overlayable::Policy> policy);
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();