From 211bec2871da597f9f3fd81df7faffea1754437e Mon Sep 17 00:00:00 2001 From: Jeremy Meyer Date: Tue, 4 Jun 2024 14:22:03 -0700 Subject: 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 --- tools/aapt2/Resource.h | 2 + tools/aapt2/ResourceParser.cpp | 28 +++++++++++++- tools/aapt2/ResourceParser.h | 3 ++ tools/aapt2/ResourceTable.cpp | 55 ++++++++++++++++++++++++++- tools/aapt2/ResourceTable.h | 7 +++- tools/aapt2/Resources.proto | 1 + tools/aapt2/cmd/Compile.cpp | 1 + tools/aapt2/format/proto/ProtoDeserialize.cpp | 3 ++ tools/aapt2/format/proto/ProtoSerialize.cpp | 1 + tools/aapt2/xml/XmlPullParser.cpp | 9 ++++- tools/aapt2/xml/XmlPullParser.h | 7 ++++ 11 files changed, 112 insertions(+), 5 deletions(-) (limited to 'tools/aapt2') diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 7ba3277d2093..a274f047586c 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -69,6 +69,8 @@ enum class ResourceType { 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 6af39b739e9b..2df941834063 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 @@ -108,6 +107,7 @@ struct ParsedResource { Visibility::Level visibility_level = Visibility::Level::kUndefined; bool staged_api = false; bool allow_new = false; + FlagStatus flag_status; std::optional overlayable_item; std::optional staged_alias; @@ -161,6 +161,8 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, }); std::string resource_type = parser->element_name(); + std::optional 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 012a056dccf3..45d41c193cb4 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 @@ struct ResourceParserOptions { // If visibility was forced, we need to use it when creating a new resource and also error if we // try to parse the , , or tags. std::optional visibility; + + FeatureFlagValues feature_flag_values; }; struct FlattenedXmlSubTree { diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index a3b0b45df5c3..1cdb71551d5d 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -231,6 +231,47 @@ bool ResourceEntry::HasDefaultValue() const { 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 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) 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(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 @@ NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) { 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 61e399c7ab68..9530c1750c79 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -104,6 +104,8 @@ class ResourceConfigValue { // The actual Value. std::unique_ptr value; + FlagStatus flag_status; + ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product) : config(config), product(product) { } @@ -269,6 +271,7 @@ struct NewResource { std::optional allow_new; std::optional staged_id; bool allow_mangled = false; + FlagStatus flag_status; }; struct NewResourceBuilder { @@ -282,6 +285,7 @@ struct NewResourceBuilder { 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 @@ class ResourceTable { std::unique_ptr 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 1d7fd1d17dcd..2ecc82ae4792 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -246,6 +246,7 @@ message Entry { 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 9b8c3b3d549c..2a978a5153ca 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -171,6 +171,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, 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 , , or tags. diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index e1a3013c07bb..aaab3158f61e 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 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr 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 0903205b5eb2..c1e15bcf9f70 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -426,6 +426,7 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table 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 8abc26d67c1f..1527d68a6c3b 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -309,7 +309,14 @@ void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) { } std::optional FindAttribute(const XmlPullParser* parser, StringPiece name) { - auto iter = parser->FindAttribute("", name); + return FindAttribute(parser, "", name); +} + +std::optional 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 64274d032c61..d65ba6fb56e3 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -193,6 +193,13 @@ class XmlPullParser : public IPackageDeclStack { std::optional FindAttribute(const XmlPullParser* parser, android::StringPiece name); +/** + * Finds the attribute in the current element within the given namespace. + */ +std::optional 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 -- cgit v1.2.3-59-g8ed1b