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.