diff options
author | 2021-03-22 09:31:00 -0700 | |
---|---|---|
committer | 2021-03-29 16:33:16 -0700 | |
commit | 2e9bec1154b8342ae6914498edd2e0fb15e36957 (patch) | |
tree | f605a39a176afd39c9ffcc18fd71b4c8c74e75a9 | |
parent | 1d008d1d2a73a8b796add4e18924fcc99220a839 (diff) |
Add staging-public-group to aapt2
staging-public-group is a tag for putting resources that have been
added during platform development, but have not yet been finalized,
into a separate resource id namespace.
R.java fields of staged resources are non-final, so when the SDK is
finalized, applications using the android R.java will automatically
use the new finalized resource id without having to recompile.
Staged resources can exist either in the same type id as the type's
non-staged counterpart or in a separate type id. Multiple
staging-public-group tags each with a different type id can exist
simultaneously, which allows for multiple versions of the platform
to be developed at once.
Bug: 183411093
Test: aapt2_tests
Change-Id: Ibb6c84c3626751e33c6097f35a03e306bb85616a
-rw-r--r-- | libs/androidfw/include/androidfw/ResourceTypes.h | 5 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser.cpp | 112 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser.h | 1 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser_test.cpp | 25 | ||||
-rw-r--r-- | tools/aapt2/ResourceTable.cpp | 139 | ||||
-rw-r--r-- | tools/aapt2/ResourceTable.h | 5 | ||||
-rw-r--r-- | tools/aapt2/Resources.proto | 5 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link_test.cpp | 124 | ||||
-rw-r--r-- | tools/aapt2/compile/IdAssigner.cpp | 81 | ||||
-rw-r--r-- | tools/aapt2/compile/IdAssigner_test.cpp | 79 | ||||
-rw-r--r-- | tools/aapt2/format/binary/TableFlattener.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/format/proto/ProtoDeserialize.cpp | 1 | ||||
-rw-r--r-- | tools/aapt2/format/proto/ProtoSerialize.cpp | 1 | ||||
-rw-r--r-- | tools/aapt2/format/proto/ProtoSerialize_test.cpp | 47 | ||||
-rw-r--r-- | tools/aapt2/java/ClassDefinition.h | 13 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator.cpp | 3 | ||||
-rw-r--r-- | tools/aapt2/process/SymbolTable.cpp | 9 |
17 files changed, 504 insertions, 152 deletions
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 168a863df2bc..1e90b7c71376 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1379,6 +1379,11 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, + + // Additional flag indicating the resource id for this resource may change in a future + // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be + // public to be exposed as an API to other applications. + SPEC_STAGED_API = 0x20000000u, }; }; diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a447cef6dbbb..24c60b740bc3 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -42,6 +42,10 @@ using ::android::StringPiece; using android::idmap2::policy::kPolicyStringToFlag; namespace aapt { +namespace { +constexpr const char* kPublicGroupTag = "public-group"; +constexpr const char* kStagingPublicGroupTag = "staging-public-group"; +} // namespace constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; @@ -102,6 +106,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; + bool staged_api = false; bool allow_new = false; Maybe<OverlayableItem> overlayable_item; @@ -122,6 +127,7 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed if (res->visibility_level != Visibility::Level::kUndefined) { Visibility visibility; visibility.level = res->visibility_level; + visibility.staged_api = res->staged_api; visibility.source = res->source; visibility.comment = res->comment; res_builder.SetVisibility(visibility); @@ -525,6 +531,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, + {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)}, {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle, std::placeholders::_2, std::placeholders::_3)}, @@ -653,7 +660,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, const auto bag_iter = elToBagMap.find(resource_type); if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a <public-group> or <overlayable>). - if (resource_type != "public-group" && resource_type != "overlayable") { + if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag && + resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); @@ -890,54 +898,45 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out return true; } -bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { - if (options_.visibility) { - diag_->Error(DiagMessage(out_resource->source) - << "<public-group> tag not allowed with --visibility flag"); - return false; - } - +template <typename Func> +bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, + const char* tag_name, IDiagnostics* diag, Func&& func) { if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for <public-group> tag"); + diag->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <" << tag_name + << "> tag"); } Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { - diag_->Error(DiagMessage(out_resource->source) - << "<public-group> must have a 'type' attribute"); + diag->Error(DiagMessage(out_resource->source) + << "<" << tag_name << "> must have a 'type' attribute"); return false; } const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); if (!parsed_type) { - diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" - << maybe_type.value() - << "' in <public-group>"); + diag->Error(DiagMessage(out_resource->source) + << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">"); return false; } - Maybe<StringPiece> maybe_id_str = - xml::FindNonEmptyAttribute(parser, "first-id"); + Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); if (!maybe_id_str) { - diag_->Error(DiagMessage(out_resource->source) - << "<public-group> must have a 'first-id' attribute"); + diag->Error(DiagMessage(out_resource->source) + << "<" << tag_name << "> must have a 'first-id' attribute"); return false; } - Maybe<ResourceId> maybe_id = - ResourceUtils::ParseResourceId(maybe_id_str.value()); + Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { - diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" - << maybe_id_str.value() - << "' in <public-group>"); + diag->Error(DiagMessage(out_resource->source) + << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">"); return false; } - ResourceId next_id = maybe_id.value(); - std::string comment; + ResourceId next_id = maybe_id.value(); bool error = false; const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { @@ -949,53 +948,72 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const Source item_source = out_resource->source.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "public") { - Maybe<StringPiece> maybe_name = - xml::FindNonEmptyAttribute(parser, "name"); + auto maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { - diag_->Error(DiagMessage(item_source) - << "<public> must have a 'name' attribute"); + diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "id")) { - diag_->Error(DiagMessage(item_source) - << "'id' is ignored within <public-group>"); + diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "type")) { - diag_->Error(DiagMessage(item_source) - << "'type' is ignored within <public-group>"); + diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">"); error = true; continue; } - ParsedResource child_resource; - child_resource.name.type = *parsed_type; - child_resource.name.entry = maybe_name.value().to_string(); - child_resource.id = next_id; - // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment - child_resource.comment = std::move(comment); - child_resource.source = item_source; - child_resource.visibility_level = Visibility::Level::kPublic; - out_resource->child_resources.push_back(std::move(child_resource)); + ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ + .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()}, + .source = item_source, + .id = next_id, + .comment = std::move(comment), + }); - next_id.id += 1; + // Execute group specific code. + func(entry_res, next_id); + next_id.id++; } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + diag->Error(DiagMessage(item_source) << ":" << element_name << ">"); error = true; } } return !error; } +bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_, + [](ParsedResource& parsed_entry, ResourceId id) { + parsed_entry.id = id; + parsed_entry.staged_api = true; + parsed_entry.visibility_level = Visibility::Level::kPublic; + }); +} + +bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (options_.visibility) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag"); + return false; + } + + return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_, + [](ParsedResource& parsed_entry, ResourceId id) { + parsed_entry.id = id; + parsed_entry.visibility_level = Visibility::Level::kPublic; + }); +} + bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource) { Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 9d3ecc866c5d..af0db8c0ba2c 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -99,6 +99,7 @@ class ResourceParser { bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 53bfa0b37475..4a509be56776 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -840,6 +840,31 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041))); } +TEST_F(ResourceParserTest, StagingPublicGroup) { + std::string input = R"( + <staging-public-group type="attr" first-id="0x01ff0049"> + <public name="foo" /> + <public name="bar" /> + </staging-public-group>)"; + ASSERT_TRUE(TestParse(input)); + + Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); + ASSERT_TRUE(result); + + ASSERT_TRUE(result.value().entry->id); + EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049))); + EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_TRUE(result.value().entry->visibility.staged_api); + + result = table_.FindResource(test::ParseNameOrDie("attr/bar")); + ASSERT_TRUE(result); + + ASSERT_TRUE(result.value().entry->id); + EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a))); + EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_TRUE(result.value().entry->visibility.staged_api); +} + TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) { std::string input = R"( <!-- private --> diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index cff98728c325..27f7bdd83c9e 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -47,11 +47,6 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType } template <typename T> -bool less_than_type_and_id(const T& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { - return lhs.id != rhs.second ? lhs.id < rhs.second : lhs.type < rhs.first; -} - -template <typename T> bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } @@ -80,12 +75,6 @@ bool less_than_struct_with_name_and_id(const T& lhs, return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0; } -template <typename T, typename U> -bool less_than_struct_with_name_and_id_pointer(const T* lhs, - const std::pair<std::string_view, Maybe<U>>& rhs) { - return less_than_struct_with_name_and_id(*lhs, rhs); -} - template <typename T, typename Func, typename Elements> T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { const auto iter = @@ -307,51 +296,115 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } +template <typename T, typename Comparer> +struct SortedVectorInserter : public Comparer { + std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el, + const T& value) { + auto it = std::lower_bound(el.begin(), el.end(), value, [&](auto& lhs, auto& rhs) { + return Comparer::operator()(lhs, rhs); + }); + bool found = + it != el.end() && !Comparer::operator()(*it, value) && !Comparer::operator()(value, *it); + return std::make_pair(found, it); + } + + T* Insert(std::vector<T>& el, T&& value) { + auto [found, it] = LowerBound(el, value); + if (found) { + return &*it; + } + return &*el.insert(it, std::move(value)); + } +}; + +struct PackageViewComparer { + bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) { + return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>( + lhs, std::make_pair(rhs.name, rhs.id)); + } +}; + +struct TypeViewComparer { + bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) { + return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type; + } +}; + +struct EntryViewComparer { + bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) { + return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>( + *lhs, std::make_pair(rhs->name, rhs->id)); + } +}; + ResourceTableView ResourceTable::GetPartitionedView() const { ResourceTableView view; + SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; + SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; + SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter; + for (const auto& package : packages) { for (const auto& type : package->types) { for (const auto& entry : type->entries) { - std::pair<std::string_view, Maybe<uint8_t>> package_key(package->name, {}); - std::pair<std::string_view, Maybe<ResourceId>> entry_key(entry->name, {}); - std::pair<ResourceType, Maybe<uint8_t>> type_key(type->type, {}); - if (entry->id) { - // If the entry has a defined id, use the id to determine insertion position. - package_key.second = entry->id.value().package_id(); - type_key.second = entry->id.value().type_id(); - entry_key.second = entry->id.value(); - } + ResourceTablePackageView new_package{ + package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}}; + auto view_package = package_inserter.Insert(view.packages, std::move(new_package)); - auto package_it = - std::lower_bound(view.packages.begin(), view.packages.end(), package_key, - less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>); - if (package_it == view.packages.end() || package_it->name != package_key.first || - package_it->id != package_key.second) { - ResourceTablePackageView new_package{std::string(package_key.first), package_key.second}; - package_it = view.packages.insert(package_it, new_package); - } - - auto type_it = std::lower_bound(package_it->types.begin(), package_it->types.end(), - type_key, less_than_type_and_id<ResourceTableTypeView>); - if (type_it == package_it->types.end() || type_key.first != type_it->type || - type_it->id != type_key.second) { - ResourceTableTypeView new_type{type_key.first, type_key.second}; - type_it = package_it->types.insert(type_it, new_type); - } + ResourceTableTypeView new_type{type->type, + entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}}; + auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); if (entry->visibility.level == Visibility::Level::kPublic) { // Only mark the type visibility level as public, it doesn't care about being private. - type_it->visibility_level = Visibility::Level::kPublic; + view_type->visibility_level = Visibility::Level::kPublic; } - auto entry_it = - std::lower_bound(type_it->entries.begin(), type_it->entries.end(), entry_key, - less_than_struct_with_name_and_id_pointer<ResourceEntry, ResourceId>); - type_it->entries.insert(entry_it, entry.get()); + entry_inserter.Insert(view_type->entries, entry.get()); + } + } + } + + // The android runtime does not support querying resources when the there are multiple type ids + // for the same resource type within the same package. For this reason, if there are types with + // multiple type ids, each type needs to exist in its own package in order to be queried by name. + std::vector<ResourceTablePackageView> new_packages; + for (auto& package : view.packages) { + // If a new package was already created for a different type within this package, then + // we can reuse those packages for other types that need to be extracted from this package. + // `start_index` is the index of the first newly created package that can be reused. + const size_t start_index = new_packages.size(); + std::map<ResourceType, size_t> type_new_package_index; + for (auto type_it = package.types.begin(); type_it != package.types.end();) { + auto& type = *type_it; + auto type_index_iter = type_new_package_index.find(type.type); + if (type_index_iter == type_new_package_index.end()) { + // First occurrence of the resource type in this package. Keep it in this package. + type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index)); + ++type_it; + continue; } + + // The resource type has already been seen for this package, so this type must be extracted to + // a new separate package. + const size_t index = type_index_iter->second; + if (new_packages.size() == index) { + new_packages.emplace_back(ResourceTablePackageView{package.name, package.id}); + type_new_package_index[type.type] = index + 1; + } + + // Move the type into a new package + auto& other_package = new_packages[index]; + type_inserter.Insert(other_package.types, std::move(type)); + type_it = package.types.erase(type_it); } } + for (auto& new_package : new_packages) { + // Insert newly created packages after their original packages + auto [_, it] = package_inserter.LowerBound(view.packages, new_package); + view.packages.insert(++it, std::move(new_package)); + } + return view; } @@ -424,6 +477,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { // This symbol definition takes precedence, replace. entry->visibility = res.visibility.value(); } + + if (res.visibility->staged_api) { + entry->visibility.staged_api = entry->visibility.staged_api; + } } if (res.overlayable.has_value()) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 49392a5db830..080ecc20558b 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -51,6 +51,11 @@ struct Visibility { Level level = Level::kUndefined; Source source; std::string comment; + + // Indicates that the resource id may change across builds and that the public R.java identifier + // for this resource should not be final. This is set to `true` for resources in `staging-group` + // tags. + bool staged_api = false; }; // Represents <add-resource> in an overlay. diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index b1e1a77e1224..4247ec5a3495 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -132,6 +132,11 @@ message Visibility { // The comment associated with the <public> tag. string comment = 3; + + // Indicates that the resource id may change across builds and that the public R.java identifier + // for this resource should not be final. This is set to `true` for resources in `staging-group` + // tags. + bool staged_api = 4; } // Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 27cbe88fde38..dfdac6b9d93e 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -25,6 +25,7 @@ using testing::Eq; using testing::HasSubstr; using testing::Ne; +using testing::NotNull; namespace aapt { @@ -400,4 +401,127 @@ TEST_F(LinkTest, SharedLibraryAttributeRJava) { EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000")); } +TEST_F(LinkTest, StagedAndroidApi) { + StdErrDiagnostics diag; + const std::string android_values = + R"(<resources> + <public type="attr" name="finalized_res" id="0x01010001"/> + + <!-- S staged attributes (support staged resources in the same type id) --> + <staging-public-group type="attr" first-id="0x01010050"> + <public name="staged_s_res" /> + </staging-public-group> + + <!-- SV2 staged attributes (support staged resources in a separate type id) --> + <staging-public-group type="attr" first-id="0x01ff0049"> + <public name="staged_s2_res" /> + </staging-public-group> + + <!-- T staged attributes (support staged resources in multiple separate type ids) --> + <staging-public-group type="attr" first-id="0x01fe0063"> + <public name="staged_t_res" /> + </staging-public-group> + + <staging-public-group type="string" first-id="0x01fd0072"> + <public name="staged_t_string" /> + </staging-public-group> + + <attr name="finalized_res" /> + <attr name="staged_s_res" /> + <attr name="staged_s2_res" /> + <attr name="staged_t_res" /> + <string name="staged_t_string">Hello</string> + </resources>)"; + + const std::string app_values = + R"(<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <attr name="bar" /> + <declare-styleable name="ClientStyleable"> + <attr name="android:finalized_res" /> + <attr name="android:staged_s_res" /> + <attr name="bar" /> + </declare-styleable> + </resources>)"; + + const std::string android_res = GetTestPath("android-res"); + ASSERT_TRUE( + CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag)); + + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android_java"); + // clang-format off + auto android_manifest = ManifestBuilder(this) + .SetPackageName("android") + .Build(); + + auto android_link_args = LinkCommandBuilder(this) + .SetManifestFile(android_manifest) + .AddParameter("--private-symbols", "com.android.internal") + .AddParameter("--java", android_java) + .AddCompiledResDir(android_res, &diag) + .Build(android_apk); + // clang-format on + ASSERT_TRUE(Link(android_link_args, &diag)); + + const std::string android_r_java = android_java + "/android/R.java"; + std::string android_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents)); + EXPECT_THAT(android_r_contents, HasSubstr(" public static final int finalized_res=0x01010001;")); + EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_s_res=0x01010050;")); + EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_s2_res=0x01ff0049;")); + EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_res=0x01fe0063;")); + EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_string=0x01fd0072;")); + + // Build an app that uses the framework attribute in a declare-styleable + const std::string client_res = GetTestPath("app-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag)); + + const std::string app_apk = GetTestPath("app.apk"); + const std::string app_java = GetTestPath("app_java"); + // clang-format off + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("--java", app_java) + .AddParameter("-I", android_apk) + .AddCompiledResDir(client_res, &diag) + .Build(app_apk); + // clang-format on + ASSERT_TRUE(Link(app_link_args, &diag)); + + const std::string client_r_java = app_java + "/com/example/app/R.java"; + std::string client_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" 0x01010001, android.R.attr.staged_s_res, 0x7f010000")); + + // Test that the resource ids of staged and non-staged resource can be retrieved + android::AssetManager2 am; + auto android_asset = android::ApkAssets::Load(android_apk); + ASSERT_THAT(android_asset, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get()})); + + auto result = am.GetResourceId("android:attr/finalized_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010001)); + + result = am.GetResourceId("android:attr/staged_s_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010050)); + + result = am.GetResourceId("android:attr/staged_s2_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01ff0049)); + + result = am.GetResourceId("android:attr/staged_t_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fe0063)); + + result = am.GetResourceId("android:string/staged_t_string"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fd0072)); +} + } // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index 07db73dff2c5..9a50b263c006 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -77,6 +77,27 @@ struct TypeGroup { NextIdFinder<uint16_t, ResourceName> next_entry_id_; }; +struct ResourceTypeKey { + ResourceType type; + uint8_t id; + + bool operator<(const ResourceTypeKey& other) const { + return (type != other.type) ? type < other.type : id < other.id; + } + + bool operator==(const ResourceTypeKey& other) const { + return type == other.type && id == other.id; + } + + bool operator!=(const ResourceTypeKey& other) const { + return !(*this == other); + } +}; + +::std::ostream& operator<<(::std::ostream& out, const ResourceTypeKey& type) { + return out << type.type; +} + struct IdAssignerContext { IdAssignerContext(std::string package_name, uint8_t package_id) : package_name_(std::move(package_name)), package_id_(package_id) { @@ -85,7 +106,8 @@ struct IdAssignerContext { // Attempts to reserve the resource id for the specified resource name. // Returns whether the id was reserved successfully. // Reserving identifiers must be completed before `NextId` is called for the first time. - bool ReserveId(const ResourceName& name, ResourceId id, IDiagnostics* diag); + bool ReserveId(const ResourceName& name, ResourceId id, const Visibility& visibility, + IDiagnostics* diag); // Retrieves the next available resource id that has not been reserved. std::optional<ResourceId> NextId(const ResourceName& name, IDiagnostics* diag); @@ -93,8 +115,10 @@ struct IdAssignerContext { private: std::string package_name_; uint8_t package_id_; - std::map<ResourceType, TypeGroup> types_; - NextIdFinder<uint8_t, ResourceType> type_id_finder_ = NextIdFinder<uint8_t, ResourceType>(1); + std::map<ResourceTypeKey, TypeGroup> types_; + std::map<ResourceType, uint8_t> non_staged_type_ids_; + NextIdFinder<uint8_t, ResourceTypeKey> type_id_finder_ = + NextIdFinder<uint8_t, ResourceTypeKey>(1); }; } // namespace @@ -106,7 +130,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { for (auto& entry : type->entries) { const ResourceName name(package->name, type->type, entry->name); if (entry->id) { - if (!assigned_ids.ReserveId(name, entry->id.value(), context->GetDiagnostics())) { + if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility, + context->GetDiagnostics())) { return false; } } @@ -116,7 +141,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { const auto iter = assigned_id_map_->find(name); if (iter != assigned_id_map_->end()) { const ResourceId assigned_id = iter->second; - if (!assigned_ids.ReserveId(name, assigned_id, context->GetDiagnostics())) { + if (!assigned_ids.ReserveId(name, assigned_id, entry->visibility, + context->GetDiagnostics())) { return false; } entry->id = assigned_id; @@ -132,7 +158,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { for (const auto& stable_id_entry : *assigned_id_map_) { const ResourceName& pre_assigned_name = stable_id_entry.first; const ResourceId& pre_assigned_id = stable_id_entry.second; - if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, context->GetDiagnostics())) { + if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, {}, + context->GetDiagnostics())) { return false; } } @@ -165,7 +192,7 @@ Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) { auto assign_result = pre_assigned_ids_.emplace(id, key); if (!assign_result.second && assign_result.first->second != key) { std::stringstream error; - error << "ID " << id << " is already assigned to " << assign_result.first->second; + error << "ID is already assigned to " << assign_result.first->second; return unexpected(error.str()); } return id; @@ -210,7 +237,7 @@ Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId if (type_id_ != id.type_id()) { // Currently there cannot be multiple type ids for a single type. std::stringstream error; - error << "type '" << name.type << "' already has ID " << id.type_id(); + error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id(); return unexpected(error.str()); } @@ -234,24 +261,38 @@ Result<ResourceId> TypeGroup::NextId() { return ResourceId(package_id_, type_id_, entry_id.value()); } -bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, IDiagnostics* diag) { +bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, + const Visibility& visibility, IDiagnostics* diag) { if (package_id_ != id.package_id()) { diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name - << " because package already has ID " << id.package_id()); + << " because package already has ID " << std::hex + << (int)id.package_id()); return false; } - auto type = types_.find(name.type); + auto key = ResourceTypeKey{name.type, id.type_id()}; + auto type = types_.find(key); if (type == types_.end()) { // The type has not been assigned an id yet. Ensure that the specified id is not being used by // another type. - auto assign_result = type_id_finder_.ReserveId(name.type, id.type_id()); + auto assign_result = type_id_finder_.ReserveId(key, id.type_id()); if (!assign_result.has_value()) { diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name << " because type " << assign_result.error()); return false; } - type = types_.emplace(name.type, TypeGroup(package_id_, id.type_id())).first; + type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first; + } + + if (!visibility.staged_api) { + // Ensure that non-staged resources can only exist in one type ID. + auto non_staged_type = non_staged_type_ids_.emplace(name.type, id.type_id()); + if (!non_staged_type.second && non_staged_type.first->second != id.type_id()) { + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because type already has ID " << std::hex + << (int)id.type_id()); + return false; + } } auto assign_result = type->second.ReserveId(name, id); @@ -268,11 +309,19 @@ std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, ID // The package name is not known during the compile stage. // Resources without a package name are considered a part of the app being linked. CHECK(name.package.empty() || name.package == package_name_); - auto type = types_.find(name.type); - if (type == types_.end()) { + + // Find the type id for non-staged resources of this type. + auto non_staged_type = non_staged_type_ids_.find(name.type); + if (non_staged_type == non_staged_type_ids_.end()) { auto next_type_id = type_id_finder_.NextId(); CHECK(next_type_id.has_value()) << "resource type IDs allocated have exceeded maximum (256)"; - type = types_.emplace(name.type, TypeGroup(package_id_, next_type_id.value())).first; + non_staged_type = non_staged_type_ids_.emplace(name.type, *next_type_id).first; + } + + ResourceTypeKey key{name.type, non_staged_type->second}; + auto type = types_.find(key); + if (type == types_.end()) { + type = types_.emplace(key, TypeGroup(package_id_, key.id)).first; } auto assign_result = type->second.NextId(); diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index 0065a224bfa7..663776645990 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -98,6 +98,37 @@ TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) { ASSERT_FALSE(assigner.Consume(context.get(), table.get())); } +TEST_F(IdAssignerTests, FailWhenNonUniqueTypeIdsAssigned) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:string/foo", ResourceId(0x01040000)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} + +TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01050000)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} + +TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01050000)) + .AddSimple("android:attr/bar", ResourceId(0x01ff0006)) + .Add(NewResourceBuilder("android:attr/staged_baz") + .SetId(0x01ff0000) + .SetVisibility({.staged_api = true}) + .Build()) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} + TEST_F(IdAssignerTests, AssignIdsWithIdMap) { auto table = test::ResourceTableBuilder() .AddSimple("android:attr/foo") @@ -154,52 +185,24 @@ TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) { } ::testing::AssertionResult VerifyIds(ResourceTable* table) { - std::set<uint8_t> package_ids; - auto table_view = table->GetPartitionedView(); - for (auto& package : table_view.packages) { - if (!package.id) { - return ::testing::AssertionFailure() << "package " << package.name << " has no ID"; - } - - if (!package_ids.insert(package.id.value()).second) { - return ::testing::AssertionFailure() << "package " << package.name << " has non-unique ID " - << std::hex << (int)package.id.value() << std::dec; - } - } - - for (auto& package : table_view.packages) { - std::set<uint8_t> type_ids; - for (auto& type : package.types) { - if (!type.id) { - return ::testing::AssertionFailure() - << "type " << type.type << " of package " << package.name << " has no ID"; - } - - if (!type_ids.insert(type.id.value()).second) { - return ::testing::AssertionFailure() - << "type " << type.type << " of package " << package.name << " has non-unique ID " - << std::hex << (int)type.id.value() << std::dec; - } - } - - for (auto& type : package.types) { - std::set<ResourceId> entry_ids; - for (auto& entry : type.entries) { + std::set<ResourceId> seen_ids; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { if (!entry->id) { return ::testing::AssertionFailure() - << "entry " << entry->name << " of type " << type.type << " of package " - << package.name << " has no ID"; + << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << " has no ID"; } - - if (!entry_ids.insert(entry->id.value()).second) { + if (!seen_ids.insert(entry->id.value()).second) { return ::testing::AssertionFailure() - << "entry " << entry->name << " of type " << type.type << " of package " - << package.name << " has non-unique ID " << std::hex << entry->id.value() - << std::dec; + << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << " has a non-unique ID" << std::hex << entry->id.value() << std::dec; } } } } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 5fea897d46e7..17d11a63553a 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -546,8 +546,12 @@ class PackageFlattener { const uint16_t entry_id = entry->id.value().entry_id(); // Populate the config masks for this entry. + uint32_t& entry_config_masks = config_masks[entry_id]; if (entry->visibility.level == Visibility::Level::kPublic) { - config_masks[entry_id] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } + if (entry->visibility.staged_api) { + entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API); } const size_t config_count = entry->values.size(); diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index bfb92da27e4d..498d5a27d69d 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -456,6 +456,7 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source); } entry->visibility.comment = pb_visibility.comment(); + entry->visibility.staged_api = pb_visibility.staged_api(); const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level()); entry->visibility.level = level; diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 9842e253f063..f13f82da3102 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -378,6 +378,7 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); + pb_visibility->set_staged_api(entry->visibility.staged_api); pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level)); if (source_pool != nullptr) { SerializeSourceToPb(entry->visibility.source, source_pool.get(), diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index ad5ed4db55b6..591ba14942cd 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -50,6 +50,53 @@ ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name, R return (result) ? result.value().entry : nullptr; } +TEST(ProtoSerializeTest, SerializeVisibility) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .Add(NewResourceBuilder("com.app.a:bool/foo") + .SetVisibility({Visibility::Level::kUndefined}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/bar") + .SetVisibility({Visibility::Level::kPrivate}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/baz") + .SetVisibility({Visibility::Level::kPublic}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/fiz") + .SetVisibility({.level = Visibility::Level::kPublic, .staged_api = true}) + .Build()) + .Build(); + + ResourceTable new_table; + pb::ResourceTable pb_table; + MockFileCollection files; + std::string error; + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)); + EXPECT_THAT(error, IsEmpty()); + + auto search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPrivate)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_TRUE(search_result.value().entry->visibility.staged_api); +} + TEST(ProtoSerializeTest, SerializeSinglePackage) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 995495ac56a8..d3648c8aae52 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -59,8 +59,9 @@ class ClassMember { template <typename T> class PrimitiveMember : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const T& val) - : name_(name.to_string()), val_(val) {} + PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false) + : name_(name.to_string()), val_(val), staged_api_(staged_api) { + } bool empty() const override { return false; @@ -77,7 +78,7 @@ class PrimitiveMember : public ClassMember { ClassMember::Print(final, printer, strip_api_annotations); printer->Print("public static "); - if (final) { + if (final && !staged_api_) { printer->Print("final "); } printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";"); @@ -88,14 +89,16 @@ class PrimitiveMember : public ClassMember { std::string name_; T val_; + bool staged_api_; }; // Specialization for strings so they get the right type and are quoted with "". template <> class PrimitiveMember<std::string> : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const std::string& val) - : name_(name.to_string()), val_(val) {} + PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false) + : name_(name.to_string()), val_(val) { + } bool empty() const override { return false; diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 039448e1b3e9..e1e2e0135cf7 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -460,7 +460,8 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso const std::string field_name = TransformToFieldName(name.entry); if (out_class_def != nullptr) { - auto resource_member = util::make_unique<ResourceMember>(field_name, real_id); + auto resource_member = + util::make_unique<ResourceMember>(field_name, real_id, entry.visibility.staged_api); // Build the comments and annotations for this entry. AnnotationProcessor* processor = resource_member->GetCommentBuilder(); diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index de72334e65a9..d385267fe5ed 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -199,7 +199,8 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( if (sr.entry->id) { symbol->id = sr.entry->id.value(); - symbol->is_dynamic = (sr.entry->id.value().package_id() == 0); + symbol->is_dynamic = + (sr.entry->id.value().package_id() == 0) || sr.entry->visibility.staged_api; } if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { @@ -374,7 +375,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( if (s) { s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package) || + (type_spec_flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0; return s; } return {}; @@ -421,7 +423,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( if (s) { s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package) || + (*flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0; return s; } return {}; |