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