First pass at flagged resources

This gets the main parts of resource flagging in place and the basic use
case of flagging with an xml attribute working.

Test: Automated
Bug: 329436914
Flag: EXEMPT Aconfig not supported on host tools
Change-Id: Id2b5ba450d05da00a922e98ca204b6e5aa6c6c24
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 7ba3277..a274f04 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -69,6 +69,8 @@
   kXml,
 };
 
+enum class FlagStatus { NoFlag = 0, Disabled = 1, Enabled = 2 };
+
 android::StringPiece to_string(ResourceType type);
 
 /**
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 6af39b7..2df9418 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #include "ResourceParser.h"
 
 #include <functional>
@@ -108,6 +107,7 @@
   Visibility::Level visibility_level = Visibility::Level::kUndefined;
   bool staged_api = false;
   bool allow_new = false;
+  FlagStatus flag_status;
   std::optional<OverlayableItem> overlayable_item;
   std::optional<StagedId> staged_alias;
 
@@ -161,6 +161,8 @@
     res_builder.SetStagedId(res->staged_alias.value());
   }
 
+  res_builder.SetFlagStatus(res->flag_status);
+
   bool error = false;
   if (!res->name.entry.empty()) {
     if (!table->AddResource(res_builder.Build(), diag)) {
@@ -544,6 +546,30 @@
   });
 
   std::string resource_type = parser->element_name();
+  std::optional<StringPiece> flag =
+      xml::FindAttribute(parser, "http://schemas.android.com/apk/res/android", "featureFlag");
+  out_resource->flag_status = FlagStatus::NoFlag;
+  if (flag) {
+    auto flag_it = options_.feature_flag_values.find(flag.value());
+    if (flag_it == options_.feature_flag_values.end()) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Resource flag value undefined");
+      return false;
+    }
+    const auto& flag_properties = flag_it->second;
+    if (!flag_properties.read_only) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Only read only flags may be used with resources");
+      return false;
+    }
+    if (!flag_properties.enabled.has_value()) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Only flags with a value may be used with resources");
+      return false;
+    }
+    out_resource->flag_status =
+        flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
+  }
 
   // The value format accepted for this resource.
   uint32_t resource_format = 0u;
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 012a056..45d41c1 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -27,6 +27,7 @@
 #include "androidfw/IDiagnostics.h"
 #include "androidfw/StringPiece.h"
 #include "androidfw/StringPool.h"
+#include "cmd/Util.h"
 #include "xml/XmlPullParser.h"
 
 namespace aapt {
@@ -54,6 +55,8 @@
   // If visibility was forced, we need to use it when creating a new resource and also error if we
   // try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
   std::optional<Visibility::Level> visibility;
+
+  FeatureFlagValues feature_flag_values;
 };
 
 struct FlattenedXmlSubTree {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index a3b0b45..1cdb715 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -231,6 +231,47 @@
   return false;
 }
 
+ResourceTable::CollisionResult ResourceTable::ResolveFlagCollision(FlagStatus existing,
+                                                                   FlagStatus incoming) {
+  switch (existing) {
+    case FlagStatus::NoFlag:
+      switch (incoming) {
+        case FlagStatus::NoFlag:
+          return CollisionResult::kConflict;
+        case FlagStatus::Disabled:
+          return CollisionResult::kKeepOriginal;
+        case FlagStatus::Enabled:
+          return CollisionResult::kTakeNew;
+        default:
+          return CollisionResult::kConflict;
+      }
+    case FlagStatus::Disabled:
+      switch (incoming) {
+        case FlagStatus::NoFlag:
+          return CollisionResult::kTakeNew;
+        case FlagStatus::Disabled:
+          return CollisionResult::kKeepOriginal;
+        case FlagStatus::Enabled:
+          return CollisionResult::kTakeNew;
+        default:
+          return CollisionResult::kConflict;
+      }
+    case FlagStatus::Enabled:
+      switch (incoming) {
+        case FlagStatus::NoFlag:
+          return CollisionResult::kKeepOriginal;
+        case FlagStatus::Disabled:
+          return CollisionResult::kKeepOriginal;
+        case FlagStatus::Enabled:
+          return CollisionResult::kConflict;
+        default:
+          return CollisionResult::kConflict;
+      }
+    default:
+      return CollisionResult::kConflict;
+  }
+}
+
 // The default handler for collisions.
 //
 // Typically, a weak value will be overridden by a strong value. An existing weak
@@ -564,16 +605,21 @@
     if (!config_value->value) {
       // Resource does not exist, add it now.
       config_value->value = std::move(res.value);
+      config_value->flag_status = res.flag_status;
     } else {
       // When validation is enabled, ensure that a resource cannot have multiple values defined for
-      // the same configuration.
-      auto result = validate ? ResolveValueCollision(config_value->value.get(), res.value.get())
+      // the same configuration unless protected by flags.
+      auto result = validate ? ResolveFlagCollision(config_value->flag_status, res.flag_status)
                              : CollisionResult::kKeepBoth;
+      if (result == CollisionResult::kConflict) {
+        result = ResolveValueCollision(config_value->value.get(), res.value.get());
+      }
       switch (result) {
         case CollisionResult::kKeepBoth:
           // Insert the value ignoring for duplicate configurations
           entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
           entry->values.back()->value = std::move(res.value);
+          entry->values.back()->flag_status = res.flag_status;
           break;
 
         case CollisionResult::kTakeNew:
@@ -735,6 +781,11 @@
   return *this;
 }
 
+NewResourceBuilder& NewResourceBuilder::SetFlagStatus(FlagStatus flag_status) {
+  res_.flag_status = flag_status;
+  return *this;
+}
+
 NewResource NewResourceBuilder::Build() {
   return std::move(res_);
 }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 61e399c..9530c17 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -104,6 +104,8 @@
   // The actual Value.
   std::unique_ptr<Value> value;
 
+  FlagStatus flag_status;
+
   ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
       : config(config), product(product) {
   }
@@ -269,6 +271,7 @@
   std::optional<AllowNew> allow_new;
   std::optional<StagedId> staged_id;
   bool allow_mangled = false;
+  FlagStatus flag_status;
 };
 
 struct NewResourceBuilder {
@@ -282,6 +285,7 @@
   NewResourceBuilder& SetAllowNew(AllowNew allow_new);
   NewResourceBuilder& SetStagedId(StagedId id);
   NewResourceBuilder& SetAllowMangled(bool allow_mangled);
+  NewResourceBuilder& SetFlagStatus(FlagStatus flag_status);
   NewResource Build();
 
  private:
@@ -330,7 +334,8 @@
 
   std::unique_ptr<ResourceTable> Clone() const;
 
-  // When a collision of resources occurs, this method decides which value to keep.
+  // When a collision of resources occurs, these methods decide which value to keep.
+  static CollisionResult ResolveFlagCollision(FlagStatus existing, FlagStatus incoming);
   static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
 
   // The string pool used by this resource table. Values that reference strings must use
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 1d7fd1d..2ecc82a 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -246,6 +246,7 @@
 message ConfigValue {
   Configuration config = 1;
   Value value = 2;
+  uint32 flag_status = 3;
 }
 
 // The generic meta-data for every value in a resource table.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 9b8c3b3..2a978a5 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -171,6 +171,7 @@
     parser_options.error_on_positional_arguments = !options.legacy_mode;
     parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables;
     parser_options.translatable = translatable_file;
+    parser_options.feature_flag_values = options.feature_flag_values;
 
     // If visibility was forced, we need to use it when creating a new resource and also error if
     // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index e1a3013..aaab315 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -16,6 +16,7 @@
 
 #include "format/proto/ProtoDeserialize.h"
 
+#include "Resource.h"
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
@@ -533,6 +534,8 @@
           return false;
         }
 
+        config_value->flag_status = (FlagStatus)pb_config_value.flag_status();
+
         config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
                                                      &out_table->string_pool, files, out_error);
         if (config_value->value == nullptr) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 0903205..c1e15bc 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -426,6 +426,7 @@
           pb_config_value->mutable_config()->set_product(config_value->product);
           SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
                              source_pool.get());
+          pb_config_value->set_flag_status((uint32_t)config_value->flag_status);
         }
       }
     }
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 8abc26d..1527d68 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -309,7 +309,14 @@
 }
 
 std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) {
-  auto iter = parser->FindAttribute("", name);
+  return FindAttribute(parser, "", name);
+}
+
+std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
+                                                  android::StringPiece namespace_uri,
+                                                  android::StringPiece name) {
+  auto iter = parser->FindAttribute(namespace_uri, name);
+
   if (iter != parser->end_attributes()) {
     return StringPiece(util::TrimWhitespace(iter->value));
   }
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 64274d0..d65ba6f 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -194,6 +194,13 @@
                                                   android::StringPiece name);
 
 /**
+ * Finds the attribute in the current element within the given namespace.
+ */
+std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
+                                                  android::StringPiece namespace_uri,
+                                                  android::StringPiece name);
+
+/**
  * Finds the attribute in the current element within the global namespace. The
  * attribute's value
  * must not be the empty string.