resource flagging on xml elements

This removes xml elements that are behind disabled flags

Test: Automated
Bug: 329436914
Flag: EXEMPT Aconfig not supported on host tools
Change-Id: Ie8ede5796f9fd0cbaa7f6aa337ddbba1a5588dca
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index be63f82..498e431 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -306,6 +306,7 @@
   OutputFormat output_format = OutputFormat::kApk;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
+  FeatureFlagValues feature_flag_values;
 };
 
 // A sampling of public framework resource IDs.
@@ -672,6 +673,13 @@
               }
             }
 
+            FeatureFlagsFilterOptions flags_filter_options;
+            flags_filter_options.flags_must_be_readonly = true;
+            FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+            if (!flags_filter.Consume(context_, doc.get())) {
+              return 1;
+            }
+
             error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
                                  false /*utf16*/, options_.output_format, archive_writer);
           }
@@ -1926,6 +1934,7 @@
         static_cast<bool>(options_.generate_proguard_rules_path);
     file_flattener_options.output_format = options_.output_format;
     file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only;
+    file_flattener_options.feature_flag_values = options_.feature_flag_values;
 
     ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
     if (!file_flattener.Flatten(table, writer)) {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index 4866d2c..c456e5c 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -30,12 +30,14 @@
         "res/values/bools2.xml",
         "res/values/ints.xml",
         "res/values/strings.xml",
+        "res/layout/layout1.xml",
     ],
     out: [
         "values_bools.arsc.flat",
         "values_bools2.arsc.flat",
         "values_ints.arsc.flat",
         "values_strings.arsc.flat",
+        "layout_layout1.xml.flat",
     ],
     cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
         "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
@@ -52,7 +54,10 @@
     out: [
         "resapp.apk",
     ],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in) " +
+        "-I $(location :current_android_jar) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+    tool_files: [":current_android_jar"],
 }
 
 genrule {
@@ -66,7 +71,10 @@
     out: [
         "resource-flagging-java/com/android/intenal/flaggedresources/R.java",
     ],
-    cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)",
+    cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in) " +
+        "-I $(location :current_android_jar) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+    tool_files: [":current_android_jar"],
 }
 
 java_genrule {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml
new file mode 100644
index 0000000..8b9ce13
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView android:id="@+id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView android:id="@+id/disabled_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:featureFlag="test.package.falseFlag" />
+    <TextView android:id="@+id/text2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:featureFlag="test.package.trueFlag" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 3e094fb..1ed0c8a 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <bool name="res1">true</bool>
-    <bool name="res1" android:featureFlag="test.package.falseFlag">false</bool>
+    <bool name="bool1">true</bool>
+    <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
 
-    <bool name="res2">false</bool>
-    <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
+    <bool name="bool2">false</bool>
+    <bool name="bool2" android:featureFlag="test.package.trueFlag">true</bool>
 
-    <bool name="res3">false</bool>
+    <bool name="bool3">false</bool>
 
-    <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool>
+    <bool name="bool4" android:featureFlag="test.package.falseFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
index e7563aa..248c45f 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <bool name="res3" android:featureFlag="test.package.trueFlag">true</bool>
+    <bool name="bool3" android:featureFlag="test.package.trueFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
index 9d40db5..4e7c1b4 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.cpp
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -65,6 +65,13 @@
 
       if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
         if (it->second.enabled.has_value()) {
+          if (options_.flags_must_be_readonly && !it->second.read_only) {
+            diagnostics_->Error(android::DiagMessage(node->line_number)
+                                << "attribute 'android:featureFlag' has flag '" << flag_name
+                                << "' which must be readonly but is not");
+            has_error_ = true;
+            return false;
+          }
           if (options_.remove_disabled_elements) {
             // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
             return *it->second.enabled == negated;
diff --git a/tools/aapt2/link/FeatureFlagsFilter.h b/tools/aapt2/link/FeatureFlagsFilter.h
index 1d342a7..61e4c80 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.h
+++ b/tools/aapt2/link/FeatureFlagsFilter.h
@@ -38,6 +38,10 @@
   // If true, `Consume()` will return false (error) if a flag was found whose value in
   // `feature_flag_values` is not defined (std::nullopt).
   bool flags_must_have_value = true;
+
+  // If true, `Consume()` will return false (error) if a flag was found whose value in
+  // `feature_flag_values` is not readonly.
+  bool flags_must_be_readonly = false;
 };
 
 // Looks for the `android:featureFlag` attribute in each XML element, validates the flag names and
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index c901b58..3db37c2 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -84,7 +84,7 @@
   std::string output;
   DumpChunksToString(loaded_apk.get(), &output);
 
-  ASSERT_EQ(output.find("res4"), std::string::npos);
+  ASSERT_EQ(output.find("bool4"), std::string::npos);
   ASSERT_EQ(output.find("str1"), std::string::npos);
 }
 
@@ -94,7 +94,7 @@
   std::string r_contents;
   ::android::base::ReadFileToString(r_path, &r_contents);
 
-  ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos);
+  ASSERT_NE(r_contents.find("public static final int bool4"), std::string::npos);
   ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
 }