Verify the locale format within a localeConfig file

Leverage the existing mechanism for checking the locale format of the
resources to check whether the inputs in the localeConfig file conforms
to the BCP47 regular expressions.

Bug: 208943132
Test: AAPT2 test
Change-Id: If972b8cc89b1e5bab422be16cc13d471e39f036f
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 790f2b3..1efe6c2 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1057,6 +1057,83 @@
     return true;
   }
 
+  bool VerifyLocaleFormat(xml::XmlResource* manifest, IDiagnostics* diag) {
+    // Skip it if the Manifest doesn't declare the localeConfig attribute within the <application>
+    // element.
+    const xml::Element* application = manifest->root->FindChild("", "application");
+    if (!application) {
+      return true;
+    }
+    const xml::Attribute* localeConfig =
+        application->FindAttribute(xml::kSchemaAndroid, "localeConfig");
+    if (!localeConfig) {
+      return true;
+    }
+
+    if (localeConfig->compiled_value) {
+      const auto localeconfig_reference = ValueCast<Reference>(localeConfig->compiled_value.get());
+      const auto localeconfig_entry =
+          ResolveTableEntry(context_, &final_table_, localeconfig_reference);
+      if (!localeconfig_entry) {
+        return true;
+      }
+
+      for (const auto& value : localeconfig_entry->values) {
+        // Load an XML file which is linked from the localeConfig attribute.
+        const std::string& path = value->value->GetSource().path;
+        std::unique_ptr<xml::XmlResource> localeConfig_xml = LoadXml(path, diag);
+        if (!localeConfig_xml) {
+          diag->Error(DiagMessage(path) << "can't load the XML");
+          return false;
+        }
+
+        xml::Element* localeConfig_el = xml::FindRootElement(localeConfig_xml->root.get());
+        if (!localeConfig_el) {
+          diag->Error(DiagMessage(path) << "no root tag defined");
+          return false;
+        }
+        if (localeConfig_el->name != "locale-config") {
+          diag->Error(DiagMessage(path) << "invalid element name: " << localeConfig_el->name
+                                        << ", expected: locale-config");
+          return false;
+        }
+
+        for (const xml::Element* child_el : localeConfig_el->GetChildElements()) {
+          if (child_el->name == "locale") {
+            if (const xml::Attribute* locale_name_attr =
+                    child_el->FindAttribute(xml::kSchemaAndroid, "name")) {
+              const std::string& locale_name = locale_name_attr->value;
+              const std::string valid_name = ConvertToBCP47Tag(locale_name);
+
+              // Start to verify the locale format
+              ConfigDescription config;
+              if (!ConfigDescription::Parse(valid_name, &config)) {
+                diag->Error(DiagMessage(path) << "invalid configuration: " << locale_name);
+                return false;
+              }
+            } else {
+              diag->Error(DiagMessage(path) << "the attribute android:name is not found");
+              return false;
+            }
+          } else {
+            diag->Error(DiagMessage(path)
+                        << "invalid element name: " << child_el->name << ", expected: locale");
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  }
+
+  std::string ConvertToBCP47Tag(const std::string& locale) {
+    std::string bcp47tag = "b+";
+    bcp47tag += locale;
+    std::replace(bcp47tag.begin(), bcp47tag.end(), '-', '+');
+
+    return bcp47tag;
+  }
+
   std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
     if (options_.output_to_directory) {
       return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
@@ -2180,6 +2257,10 @@
       return 1;
     }
 
+    if (!VerifyLocaleFormat(manifest_xml.get(), context_->GetDiagnostics())) {
+      return 1;
+    };
+
     if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
       return 1;
     }