AAPT2: Add <overlayable> tag support
This doesn't actually do anything yet, but makes sure
it is not a syntax error and allows teams to start marking
their resources as overlayable.
The syntax form marking a set of resources as overlayable
looks like:
<overlayable policy="system">
<item type="string" name="foo" />
<item type="style" name="bar" />
</overlayable>
Currently, the only supported policy is "system", meaning only
the system will be able to overlay the resources. Leaving
out the policy attribute defaults to "system".
Bug: 64980941
Test: make aapt2_tests
Change-Id: Ied7a9ddae87a4a0af6a0f4d1c213bfce8a0ed612
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1c3ac2a..ee49ba5 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -392,6 +392,7 @@
{"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)},
{"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)},
{"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
+ {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)},
{"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
{"public", std::mem_fn(&ResourceParser::ParsePublic)},
{"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
@@ -498,7 +499,7 @@
const auto bag_iter = elToBagMap.find(resource_type);
if (bag_iter != elToBagMap.end()) {
// Ensure we have a name (unless this is a <public-group>).
- if (resource_type != "public-group") {
+ if (resource_type != "public-group" && resource_type != "overlayable") {
if (!maybe_name) {
diag_->Error(DiagMessage(out_resource->source)
<< "<" << parser->element_name() << "> missing 'name' attribute");
@@ -690,6 +691,11 @@
bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
ParsedResource* out_resource) {
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config << "' for <public> tag");
+ }
+
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
if (!maybe_type) {
diag_->Error(DiagMessage(out_resource->source)
@@ -726,8 +732,13 @@
return true;
}
-bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
+bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config
+ << "' for <public-group> tag");
+ }
+
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
if (!maybe_type) {
diag_->Error(DiagMessage(out_resource->source)
@@ -842,13 +853,83 @@
return true;
}
-bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser,
- ParsedResource* out_resource) {
- if (ParseSymbolImpl(parser, out_resource)) {
- out_resource->symbol_state = SymbolState::kPrivate;
- return true;
+bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config << "' for <"
+ << parser->element_name() << "> tag");
}
- return false;
+
+ if (!ParseSymbolImpl(parser, out_resource)) {
+ return false;
+ }
+
+ out_resource->symbol_state = SymbolState::kPrivate;
+ return true;
+}
+
+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");
+ }
+
+ 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;
+ }
+ }
+
+ 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.
+ 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();
+ 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");
+ 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");
+ 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;
+ }
+
+ // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay
+ // the resource (system/app).
+
+ xml::XmlPullParser::SkipCurrentElement(parser);
+ } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
+ diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ error = true;
+ }
+ }
+ return !error;
}
bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5631dc2..fb9dbd0 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -91,6 +91,7 @@
bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
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 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);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 144ebd2..f08b03e 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -790,4 +790,49 @@
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" />)";
+ 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" />
+ </overlayable>)";
+ ASSERT_TRUE(TestParse(input));
+}
+
} // namespace aapt