Handle new manifest tag <property>

Bug: 169258655
Test: aapt2_tests
Change-Id: Ia1c8e7c38fb882c3ce0aa2913b844cccdc5b8749
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 8862405..cc1cf7f 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1851,6 +1851,41 @@
   }
 };
 
+/** Represents <property> elements. **/
+class Property : public ManifestExtractor::Element {
+ public:
+  Property() = default;
+  std::string name;
+  std::string value;
+  const int* value_int;
+  std::string resource;
+  const int* resource_int;
+
+  void Extract(xml::Element* element) override {
+    name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+    value = GetAttributeStringDefault(FindAttribute(element, VALUE_ATTR), "");
+    value_int = GetAttributeInteger(FindAttribute(element, VALUE_ATTR));
+    resource = GetAttributeStringDefault(FindAttribute(element, RESOURCE_ATTR), "");
+    resource_int = GetAttributeInteger(FindAttribute(element, RESOURCE_ATTR));
+  }
+
+  void Print(text::Printer* printer) override {
+    printer->Print(StringPrintf("property: name='%s' ", name.data()));
+    if (!value.empty()) {
+      printer->Print(StringPrintf("value='%s' ", value.data()));
+    } else if (value_int) {
+      printer->Print(StringPrintf("value='%d' ", *value_int));
+    } else {
+      if (!resource.empty()) {
+        printer->Print(StringPrintf("resource='%s' ", resource.data()));
+      } else if (resource_int) {
+        printer->Print(StringPrintf("resource='%d' ", *resource_int));
+      }
+    }
+    printer->Print("\n");
+  }
+};
+
 /** Recursively prints the extracted badging element. */
 static void Print(ManifestExtractor::Element* el, text::Printer* printer) {
   el->Print(printer);
@@ -2291,6 +2326,7 @@
       {"overlay", std::is_base_of<Overlay, T>::value},
       {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
       {"permission", std::is_base_of<Permission, T>::value},
+      {"property", std::is_base_of<Property, T>::value},
       {"provider", std::is_base_of<Provider, T>::value},
       {"receiver", std::is_base_of<Receiver, T>::value},
       {"required-feature", std::is_base_of<RequiredFeature, T>::value},
@@ -2344,6 +2380,7 @@
           {"overlay", &CreateType<Overlay>},
           {"package-verifier", &CreateType<PackageVerifier>},
           {"permission", &CreateType<Permission>},
+          {"property", &CreateType<Property>},
           {"provider", &CreateType<Provider>},
           {"receiver", &CreateType<Receiver>},
           {"required-feature", &CreateType<RequiredFeature>},
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 3d8c25e..fd3a4c0 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -112,6 +112,27 @@
   };
 }
 
+static xml::XmlNodeAction::ActionFuncWithDiag RequiredOneAndroidAttribute(
+    const std::string& attrName1, const std::string& attrName2) {
+  return [=](xml::Element* el, SourcePathDiagnostics* diag) -> bool {
+    xml::Attribute* attr1 = el->FindAttribute(xml::kSchemaAndroid, attrName1);
+    xml::Attribute* attr2 = el->FindAttribute(xml::kSchemaAndroid, attrName2);
+    if (attr1 == nullptr && attr2 == nullptr) {
+      diag->Error(DiagMessage(el->line_number)
+                  << "<" << el->name << "> is missing required attribute 'android:" << attrName1
+                  << "' or 'android:" << attrName2 << "'");
+      return false;
+    }
+    if (attr1 != nullptr && attr2 != nullptr) {
+      diag->Error(DiagMessage(el->line_number)
+                  << "<" << el->name << "> can only specify one of attribute 'android:" << attrName1
+                  << "' or 'android:" << attrName2 << "'");
+      return false;
+    }
+    return true;
+  };
+}
+
 static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) {
   constexpr const char* kFeatureSplit = "featureSplit";
   constexpr const char* kIsFeatureSplit = "isFeatureSplit";
@@ -282,6 +303,10 @@
   // Common <meta-data> actions.
   xml::XmlNodeAction meta_data_action;
 
+  // Common <property> actions.
+  xml::XmlNodeAction property_action;
+  property_action.Action(RequiredOneAndroidAttribute("resource", "value"));
+
   // Common <uses-feature> actions.
   xml::XmlNodeAction uses_feature_action;
   uses_feature_action.Action(VerifyUsesFeature);
@@ -292,6 +317,7 @@
   component_action["intent-filter"] = intent_filter_action;
   component_action["preferred"] = intent_filter_action;
   component_action["meta-data"] = meta_data_action;
+  component_action["property"] = property_action;
 
   // Manifest actions.
   xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
@@ -427,6 +453,7 @@
   application_action["uses-native-library"].Action(RequiredNameIsNotEmpty);
   application_action["library"].Action(RequiredNameIsNotEmpty);
   application_action["profileable"];
+  application_action["property"] = property_action;
 
   xml::XmlNodeAction& static_library_action = application_action["static-library"];
   static_library_action.Action(RequiredNameIsJavaPackage);
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 0791805..781ff7b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -886,4 +886,78 @@
   EXPECT_THAT(Verify(input), NotNull());
 }
 
+TEST_F(ManifestFixerTest, ApplicationPropertyAttributeRequired) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <property android:name="" />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), IsNull());
+}
+
+TEST_F(ManifestFixerTest, ApplicationPropertyOnlyOneAttributeDefined) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <property android:name="" android:value="" android:resource="" />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), IsNull());
+
+  input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <property android:name="" android:resource="" />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
+
+  input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <property android:name="" android:value="" />
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <activity android:name=".MyActivity">
+            <property android:name="" android:value="" android:resource="" />
+          </activity>
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), IsNull());
+
+  input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <activity android:name=".MyActivity">
+            <property android:name="" android:value="" />
+          </activity>
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
+
+  input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <application>
+          <activity android:name=".MyActivity">
+            <property android:name="" android:resource="" />
+          </activity>
+        </application>
+      </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
+}
 }  // namespace aapt