diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 7e45f952d389..7d061fb27ff4 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -221,6 +221,16 @@ void AssetManager2::BuildDynamicRefTable() { for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()), iter->dynamic_ref_table->mAssignedPackageId); + + // Add the alias resources to the dynamic reference table of every package group. Since + // staging aliases can only be defined by the framework package (which is not a shared + // library), the compile-time package id of the framework is the same across all packages + // that compile against the framework. + for (const auto& package : iter2->packages_) { + for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) { + iter->dynamic_ref_table->addAlias(entry.first, entry.second); + } + } } } } diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index cb620cc475a9..d17c32817994 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -41,6 +41,7 @@ using android::base::StringPrintf; namespace android { +constexpr const static int kFrameworkPackageId = 0x01; constexpr const static int kAppPackageId = 0x7f; namespace { @@ -675,6 +676,42 @@ std::unique_ptr LoadedPackage::Load(const Chunk& chunk, } } break; + case RES_TABLE_STAGED_ALIAS_TYPE: { + if (loaded_package->package_id_ != kFrameworkPackageId) { + LOG(WARNING) << "Alias chunk ignored for non-framework package '" + << loaded_package->package_name_ << "'"; + break; + } + + std::unordered_set finalized_ids; + const auto lib_alias = child_chunk.header(); + if (!lib_alias) { + return {}; + } + const auto entry_begin = child_chunk.data_ptr().convert(); + const auto entry_end = entry_begin + dtohl(lib_alias->count); + for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) { + if (!entry_iter) { + return {}; + } + auto finalized_id = dtohl(entry_iter->finalizedResId); + if (!finalized_ids.insert(finalized_id).second) { + LOG(ERROR) << StringPrintf("Repeated finalized resource id '%08x' in staged aliases.", + finalized_id); + return {}; + } + + auto staged_id = dtohl(entry_iter->stagedResId); + auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id, + finalized_id)); + if (!success) { + LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.", + staged_id); + return {}; + } + } + } break; + default: LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 30500abc39c0..cae2d0bc16b3 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -7079,6 +7079,10 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI mLookupTable[buildPackageId] = runtimePackageId; } +void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) { + mAliasId[stagedId] = finalizedId; +} + status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; @@ -7088,8 +7092,16 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { return NO_ERROR; } - if (packageId == APP_PACKAGE_ID && !mAppAsLib) { - // No lookup needs to be done, app package IDs are absolute. + auto alias_id = mAliasId.find(res); + if (alias_id != mAliasId.end()) { + // Rewrite the resource id to its alias resource id. Since the alias resource id is a + // compile-time id, it still needs to be resolved further. + res = alias_id->second; + } + + if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) { + // No lookup needs to be done, app and framework package IDs are absolute. + *resId = res; return NO_ERROR; } diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 3b222c556bfa..9bbdede56293 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -17,6 +17,7 @@ #ifndef LOADEDARSC_H_ #define LOADEDARSC_H_ +#include #include #include #include @@ -171,51 +172,51 @@ class LoadedPackage { incfs::verified_map_ptr type_chunk, uint32_t offset); // Returns the string pool where type names are stored. - inline const ResStringPool* GetTypeStringPool() const { + const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; } // Returns the string pool where the names of resource entries are stored. - inline const ResStringPool* GetKeyStringPool() const { + const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; } - inline const std::string& GetPackageName() const { + const std::string& GetPackageName() const { return package_name_; } - inline int GetPackageId() const { + int GetPackageId() const { return package_id_; } // Returns true if this package is dynamic (shared library) and needs to have an ID assigned. - inline bool IsDynamic() const { + bool IsDynamic() const { return (property_flags_ & PROPERTY_DYNAMIC) != 0; } // Returns true if this package is a Runtime Resource Overlay. - inline bool IsOverlay() const { + bool IsOverlay() const { return (property_flags_ & PROPERTY_OVERLAY) != 0; } // Returns true if this package originates from a system provided resource. - inline bool IsSystem() const { + bool IsSystem() const { return (property_flags_ & PROPERTY_SYSTEM) != 0; } // Returns true if this package is a custom loader and should behave like an overlay. - inline bool IsCustomLoader() const { + bool IsCustomLoader() const { return (property_flags_ & PROPERTY_LOADER) != 0; } - inline package_property_t GetPropertyFlags() const { + package_property_t GetPropertyFlags() const { return property_flags_; } // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a // package could have been assigned a different package ID than what this LoadedPackage was // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime. - inline const std::vector& GetDynamicPackageMap() const { + const std::vector& GetDynamicPackageMap() const { return dynamic_package_map_; } @@ -270,6 +271,10 @@ class LoadedPackage { return overlayable_map_; } + const std::map& GetAliasResourceIdMap() const { + return alias_id_map_; + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); @@ -287,6 +292,7 @@ class LoadedPackage { ByteBucketArray resource_ids_; std::vector dynamic_package_map_; std::vector>> overlayable_infos_; + std::map alias_id_map_; // A map of overlayable name to actor std::unordered_map overlayable_map_; diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 17c1404c0d98..3d66244646d5 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -39,6 +39,7 @@ #include #include +#include #include namespace android { @@ -229,30 +230,31 @@ struct ResChunk_header }; enum { - RES_NULL_TYPE = 0x0000, - RES_STRING_POOL_TYPE = 0x0001, - RES_TABLE_TYPE = 0x0002, - RES_XML_TYPE = 0x0003, + RES_NULL_TYPE = 0x0000, + RES_STRING_POOL_TYPE = 0x0001, + RES_TABLE_TYPE = 0x0002, + RES_XML_TYPE = 0x0003, // Chunk types in RES_XML_TYPE - RES_XML_FIRST_CHUNK_TYPE = 0x0100, - RES_XML_START_NAMESPACE_TYPE= 0x0100, - RES_XML_END_NAMESPACE_TYPE = 0x0101, - RES_XML_START_ELEMENT_TYPE = 0x0102, - RES_XML_END_ELEMENT_TYPE = 0x0103, - RES_XML_CDATA_TYPE = 0x0104, - RES_XML_LAST_CHUNK_TYPE = 0x017f, + RES_XML_FIRST_CHUNK_TYPE = 0x0100, + RES_XML_START_NAMESPACE_TYPE = 0x0100, + RES_XML_END_NAMESPACE_TYPE = 0x0101, + RES_XML_START_ELEMENT_TYPE = 0x0102, + RES_XML_END_ELEMENT_TYPE = 0x0103, + RES_XML_CDATA_TYPE = 0x0104, + RES_XML_LAST_CHUNK_TYPE = 0x017f, // This contains a uint32_t array mapping strings in the string // pool back to resource identifiers. It is optional. - RES_XML_RESOURCE_MAP_TYPE = 0x0180, + RES_XML_RESOURCE_MAP_TYPE = 0x0180, // Chunk types in RES_TABLE_TYPE - RES_TABLE_PACKAGE_TYPE = 0x0200, - RES_TABLE_TYPE_TYPE = 0x0201, - RES_TABLE_TYPE_SPEC_TYPE = 0x0202, - RES_TABLE_LIBRARY_TYPE = 0x0203, - RES_TABLE_OVERLAYABLE_TYPE = 0x0204, + RES_TABLE_PACKAGE_TYPE = 0x0200, + RES_TABLE_TYPE_TYPE = 0x0201, + RES_TABLE_TYPE_SPEC_TYPE = 0x0202, + RES_TABLE_LIBRARY_TYPE = 0x0203, + RES_TABLE_OVERLAYABLE_TYPE = 0x0204, RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205, + RES_TABLE_STAGED_ALIAS_TYPE = 0x0206, }; /** @@ -1638,6 +1640,29 @@ struct ResTable_lib_entry uint16_t packageName[128]; }; +/** + * A map that allows rewriting staged (non-finalized) resource ids to their finalized counterparts. + */ +struct ResTable_staged_alias_header +{ + struct ResChunk_header header; + + // The number of ResTable_staged_alias_entry that follow this header. + uint32_t count; +}; + +/** + * Maps the staged (non-finalized) resource id to its finalized resource id. + */ +struct ResTable_staged_alias_entry +{ + // The compile-time staged resource id to rewrite. + uint32_t stagedResId; + + // The compile-time finalized resource id to which the staged resource id should be rewritten. + uint32_t finalizedResId; +}; + /** * Specifies the set of resources that are explicitly allowed to be overlaid by RROs. */ @@ -1751,6 +1776,8 @@ public: void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId); + void addAlias(uint32_t stagedId, uint32_t finalizedId); + // Returns whether or not the value must be looked up. bool requiresLookup(const Res_value* value) const; @@ -1768,6 +1795,7 @@ private: uint8_t mLookupTable[256]; KeyedVector mEntries; bool mAppAsLib; + std::map mAliasId; }; bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue); diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 77c0872037ac..ef3a62f4efcc 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -278,17 +278,19 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& printer->Println(StringPrintf(" entryCount=%zd", type.entries.size())); printer->Indent(); - for (const ResourceEntry* entry : type.entries) { + for (const ResourceTableEntryView& entry : type.entries) { printer->Print("resource "); - printer->Print(entry->id.value_or_default(0).to_string()); + printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0), + entry.id.value_or_default(0)) + .to_string()); printer->Print(" "); // Write the name without the package (this is obvious and too verbose). printer->Print(to_string(type.type)); printer->Print("/"); - printer->Print(entry->name); + printer->Print(entry.name); - switch (entry->visibility.level) { + switch (entry.visibility.level) { case Visibility::Level::kPublic: printer->Print(" PUBLIC"); break; @@ -300,19 +302,24 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& break; } - if (entry->visibility.staged_api) { + if (entry.visibility.staged_api) { printer->Print(" STAGED"); } - if (entry->overlayable_item) { + if (entry.overlayable_item) { printer->Print(" OVERLAYABLE"); } + if (entry.staged_id) { + printer->Print(" STAGED_ID="); + printer->Print(entry.staged_id.value().id.to_string()); + } + printer->Println(); if (options.show_values) { printer->Indent(); - for (const auto& value : entry->values) { + for (const auto& value : entry.values) { printer->Print("("); printer->Print(value->config.to_string()); printer->Print(") "); diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 1efabbb46fd5..f1e2da9f41e2 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -45,6 +45,7 @@ namespace aapt { namespace { constexpr const char* kPublicGroupTag = "public-group"; constexpr const char* kStagingPublicGroupTag = "staging-public-group"; +constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final"; } // namespace constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; @@ -109,6 +110,7 @@ struct ParsedResource { bool staged_api = false; bool allow_new = false; Maybe overlayable_item; + Maybe staged_alias; std::string comment; std::unique_ptr value; @@ -155,6 +157,10 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed res_builder.SetValue(std::move(res->value), res->config, res->product); } + if (res->staged_alias) { + res_builder.SetStagedId(res->staged_alias.value()); + } + bool error = false; if (!res->name.entry.empty()) { if (!table->AddResource(res_builder.Build(), diag)) { @@ -532,6 +538,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)}, + {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)}, {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle, std::placeholders::_2, std::placeholders::_3)}, @@ -671,7 +678,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a or ). if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag && - resource_type != "overlayable") { + resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); @@ -1034,7 +1041,6 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou 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), }); @@ -1060,6 +1066,14 @@ bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser, }); } +bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_, + [](ParsedResource& parsed_entry, ResourceId id) { + parsed_entry.staged_alias = StagedId{id, parsed_entry.source}; + }); +} + bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 5c92def50616..261499781638 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -115,6 +115,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 ParseStagingPublicGroupFinal(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/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 45ea65430bb6..8ab1493c6ab3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -83,6 +83,20 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu return action(found, iter); } +struct ConfigKey { + const ConfigDescription* config; + const StringPiece& product; +}; + +template +bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) { + int cmp = lhs->config.compare(*rhs.config); + if (cmp == 0) { + cmp = StringPiece(lhs->product).compare(rhs.product); + } + return cmp < 0; +} + } // namespace ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) { @@ -134,23 +148,10 @@ ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& }); } -struct ConfigKey { - const ConfigDescription* config; - const StringPiece& product; -}; - -bool lt_config_key_ref(const std::unique_ptr& lhs, const ConfigKey& rhs) { - int cmp = lhs->config.compare(*rhs.config); - if (cmp == 0) { - cmp = StringPiece(lhs->product).compare(rhs.product); - } - return cmp < 0; -} - ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, android::StringPiece product) { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, - lt_config_key_ref); + lt_config_key_ref>); if (iter != values.end()) { ResourceConfigValue* value = iter->get(); if (value->config == config && StringPiece(value->product) == product) { @@ -163,7 +164,7 @@ ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config, android::StringPiece product) const { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, - lt_config_key_ref); + lt_config_key_ref>); if (iter != values.end()) { ResourceConfigValue* value = iter->get(); if (value->config == config && StringPiece(value->product) == product) { @@ -176,7 +177,7 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config, const StringPiece& product) { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, - lt_config_key_ref); + lt_config_key_ref>); if (iter != values.end()) { ResourceConfigValue* value = iter->get(); if (value->config == config && StringPiece(value->product) == product) { @@ -296,6 +297,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } +namespace { template struct SortedVectorInserter : public Comparer { std::pair::iterator> LowerBound(std::vector& el, @@ -313,7 +315,7 @@ struct SortedVectorInserter : public Comparer { if (found) { return &*it; } - return &*el.insert(it, std::move(value)); + return &*el.insert(it, std::forward(value)); } }; @@ -331,35 +333,77 @@ struct TypeViewComparer { }; struct EntryViewComparer { - bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) { - return less_than_struct_with_name_and_id( - *lhs, std::make_pair(rhs->name, rhs->id)); + bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) { + return less_than_struct_with_name_and_id( + lhs, std::make_pair(rhs.name, rhs.id)); } }; -ResourceTableView ResourceTable::GetPartitionedView() const { - ResourceTableView view; +void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package, + const ResourceTableType* type, const std::string& entry_name, + const Maybe& id, const Visibility& visibility, + const Maybe& allow_new, + const Maybe& overlayable_item, + const Maybe& staged_id, + const std::vector>& values) { SortedVectorInserter package_inserter; SortedVectorInserter type_inserter; - SortedVectorInserter entry_inserter; + SortedVectorInserter entry_inserter; + ResourceTablePackageView new_package{package->name, + id ? id.value().package_id() : Maybe{}}; + auto view_package = package_inserter.Insert(table.packages, std::move(new_package)); + + ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe{}}; + auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); + + if (visibility.level == Visibility::Level::kPublic) { + // Only mark the type visibility level as public, it doesn't care about being private. + view_type->visibility_level = Visibility::Level::kPublic; + } + + ResourceTableEntryView new_entry{.name = entry_name, + .id = id ? id.value().entry_id() : Maybe{}, + .visibility = visibility, + .allow_new = allow_new, + .overlayable_item = overlayable_item, + .staged_id = staged_id}; + for (auto& value : values) { + new_entry.values.emplace_back(value.get()); + } + + entry_inserter.Insert(view_type->entries, std::move(new_entry)); +} +} // namespace + +const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config, + android::StringPiece product) const { + auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, + lt_config_key_ref); + if (iter != values.end()) { + const ResourceConfigValue* value = *iter; + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + return nullptr; +} + +ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const { + ResourceTableView view; for (const auto& package : packages) { for (const auto& type : package->types) { for (const auto& entry : type->entries) { - ResourceTablePackageView new_package{ - package->name, entry->id ? entry->id.value().package_id() : Maybe{}}; - auto view_package = package_inserter.Insert(view.packages, std::move(new_package)); + InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id, + entry->visibility, entry->allow_new, entry->overlayable_item, + entry->staged_id, entry->values); - ResourceTableTypeView new_type{type->type, - entry->id ? entry->id.value().type_id() : Maybe{}}; - 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. - view_type->visibility_level = Visibility::Level::kPublic; + if (options.create_alias_entries && entry->staged_id) { + auto alias_id = entry->staged_id.value().id; + InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id, + entry->visibility, entry->allow_new, entry->overlayable_item, {}, + entry->values); } - - entry_inserter.Insert(view_type->entries, entry.get()); } } } @@ -368,6 +412,8 @@ ResourceTableView ResourceTable::GetPartitionedView() const { // 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 new_packages; + SortedVectorInserter package_inserter; + SortedVectorInserter type_inserter; 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. @@ -498,6 +544,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { entry->allow_new = res.allow_new.value(); } + if (res.staged_id.has_value()) { + entry->staged_id = res.staged_id.value(); + } + if (res.value != nullptr) { auto config_value = entry->FindOrCreateValue(res.config, res.product); if (!config_value->value) { @@ -575,6 +625,28 @@ Maybe ResourceTable::FindResource(const ResourceNam return {}; } +bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const { + ResourceTablePackage* package = FindPackage(name.package); + if (package == nullptr) { + return {}; + } + + ResourceTableType* type = package->FindType(name.type); + if (type == nullptr) { + return {}; + } + + auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry, + NameEqualRange{}); + for (auto it = entry_it.first; it != entry_it.second; ++it) { + if ((*it)->id == id) { + type->entries.erase(it); + return true; + } + } + return false; +} + std::unique_ptr ResourceTable::Clone() const { std::unique_ptr new_table = util::make_unique(); CloningValueTransformer cloner(&new_table->string_pool); @@ -640,6 +712,11 @@ NewResourceBuilder& NewResourceBuilder::SetAllowNew(AllowNew allow_new) { return *this; } +NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) { + res_.staged_id = std::move(staged_alias); + return *this; +} + NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) { res_.allow_mangled = allow_mangled; return *this; diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 080ecc20558b..bae1d827a841 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -64,6 +64,12 @@ struct AllowNew { std::string comment; }; +// Represents the staged resource id of a finalized resource. +struct StagedId { + ResourceId id; + Source source; +}; + struct Overlayable { Overlayable() = default; Overlayable(const android::StringPiece& name, const android::StringPiece& actor) @@ -124,6 +130,9 @@ class ResourceEntry { // The declarations of this resource as overlayable for RROs Maybe overlayable_item; + // The staged resource id for a finalized resource. + Maybe staged_id; + // The resource's values for each configuration. std::vector> values; @@ -194,14 +203,27 @@ class ResourceTablePackage { DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; +struct ResourceTableEntryView { + std::string name; + Maybe id; + Visibility visibility; + Maybe allow_new; + Maybe overlayable_item; + Maybe staged_id; + std::vector values; + + const ResourceConfigValue* FindValue(const android::ConfigDescription& config, + android::StringPiece product = {}) const; +}; + struct ResourceTableTypeView { ResourceType type; Maybe id; Visibility::Level visibility_level = Visibility::Level::kUndefined; // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are - // // sorted lexicographically. - std::vector entries; + // sorted lexicographically. + std::vector entries; }; struct ResourceTablePackageView { @@ -212,6 +234,10 @@ struct ResourceTablePackageView { std::vector types; }; +struct ResourceTableViewOptions { + bool create_alias_entries = false; +}; + struct ResourceTableView { // Packages sorted in ascending package id order. If ids have not been assigned, the packages are // sorted lexicographically. @@ -237,6 +263,7 @@ struct NewResource { std::optional visibility; std::optional overlayable; std::optional allow_new; + std::optional staged_id; bool allow_mangled = false; }; @@ -249,6 +276,7 @@ struct NewResourceBuilder { NewResourceBuilder& SetVisibility(Visibility id); NewResourceBuilder& SetOverlayable(OverlayableItem overlayable); NewResourceBuilder& SetAllowNew(AllowNew allow_new); + NewResourceBuilder& SetStagedId(StagedId id); NewResourceBuilder& SetAllowMangled(bool allow_mangled); NewResource Build(); @@ -273,7 +301,7 @@ class ResourceTable { // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id // order. - ResourceTableView GetPartitionedView() const; + ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const; struct SearchResult { ResourceTablePackage* package; @@ -283,6 +311,7 @@ class ResourceTable { Maybe FindResource(const ResourceNameRef& name) const; Maybe FindResource(const ResourceNameRef& name, ResourceId id) const; + bool RemoveResource(const ResourceNameRef& name, ResourceId id) const; // Returns the package struct with the given name, or nullptr if such a package does not // exist. The empty string is a valid package and typically is used to represent the diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index b45c0401d19a..95b794964068 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -190,6 +190,12 @@ message OverlayableItem { uint32 overlayable_idx = 4; } +// The staged resource ID definition of a finalized resource. +message StagedId { + Source source = 1; + uint32 staged_id = 2; +} + // An entry ID in the range [0x0000, 0xffff]. message EntryId { uint32 id = 1; @@ -222,6 +228,9 @@ message Entry { // The set of values defined for this entry, each corresponding to a different // configuration/variant. repeated ConfigValue config_value = 6; + + // The staged resource ID of this finalized resource. + StagedId staged_id = 7; } // A Configuration/Value pair. diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index df31087c5078..3950f337b575 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -97,15 +97,15 @@ static bool IsIdDiff(const Visibility::Level& level_a, const Maybe& id_a, static bool EmitResourceConfigValueDiff( IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a, - const ResourceTableTypeView& type_a, const ResourceEntry* entry_a, + const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a, const ResourceConfigValue* config_value_a, LoadedApk* apk_b, const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b, - const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) { + const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) { Value* value_a = config_value_a->value.get(); Value* value_b = config_value_b->value.get(); if (!value_a->Equals(value_b)) { std::stringstream str_stream; - str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name + str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " config=" << config_value_a->config << " does not match:\n"; value_a->Print(&str_stream); str_stream << "\n vs \n"; @@ -118,32 +118,32 @@ static bool EmitResourceConfigValueDiff( static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a, - const ResourceTableTypeView& type_a, const ResourceEntry* entry_a, - LoadedApk* apk_b, const ResourceTablePackageView& pkg_b, + const ResourceTableTypeView& type_a, + const ResourceTableEntryView& entry_a, LoadedApk* apk_b, + const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b, - const ResourceEntry* entry_b) { + const ResourceTableEntryView& entry_b) { bool diff = false; - for (const std::unique_ptr& config_value_a : entry_a->values) { - auto config_value_b = entry_b->FindValue(config_value_a->config); + for (const ResourceConfigValue* config_value_a : entry_a.values) { + auto config_value_b = entry_b.FindValue(config_value_a->config); if (!config_value_b) { std::stringstream str_stream; - str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name + str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " config=" << config_value_a->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { - diff |= - EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), - apk_b, pkg_b, type_b, entry_b, config_value_b); + diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a, + apk_b, pkg_b, type_b, entry_b, config_value_b); } } // Check for any newly added config values. - for (const std::unique_ptr& config_value_b : entry_b->values) { - auto config_value_a = entry_a->FindValue(config_value_b->config); + for (const ResourceConfigValue* config_value_b : entry_b.values) { + auto config_value_a = entry_a.FindValue(config_value_b->config); if (!config_value_a) { std::stringstream str_stream; - str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name + str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name << " config=" << config_value_b->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; @@ -164,36 +164,35 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, if (entry_b_iter == type_b.entries.end()) { // Type A contains a type that type B does not have. std::stringstream str_stream; - str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name; + str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name; EmitDiffLine(apk_a->GetSource(), str_stream.str()); diff = true; } else if (entry_a_iter == type_a.entries.end()) { // Type B contains a type that type A does not have. std::stringstream str_stream; - str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" - << (*entry_b_iter)->name; + str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { const auto& entry_a = *entry_a_iter; const auto& entry_b = *entry_b_iter; - if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) { + if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name + str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " has different visibility ("; - if (entry_b->visibility.staged_api) { + if (entry_b.visibility.staged_api) { str_stream << "STAGED "; } - if (entry_b->visibility.level == Visibility::Level::kPublic) { + if (entry_b.visibility.level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; } str_stream << " vs "; - if (entry_a->visibility.staged_api) { + if (entry_a.visibility.staged_api) { str_stream << "STAGED "; } - if (entry_a->visibility.level == Visibility::Level::kPublic) { + if (entry_a.visibility.level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; @@ -201,19 +200,19 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, str_stream << ")"; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; - } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level, - entry_b->id)) { + } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level, + entry_b.id)) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name + str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " has different public ID ("; - if (entry_b->id) { - str_stream << "0x" << std::hex << entry_b->id.value(); + if (entry_b.id) { + str_stream << "0x" << std::hex << entry_b.id.value(); } else { str_stream << "none"; } str_stream << " vs "; - if (entry_a->id) { - str_stream << "0x " << std::hex << entry_a->id.value(); + if (entry_a.id) { + str_stream << "0x " << std::hex << entry_a.id.value(); } else { str_stream << "none"; } diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 3118eb8f7731..430c184ef87d 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -402,8 +402,39 @@ TEST_F(LinkTest, SharedLibraryAttributeRJava) { EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000")); } -TEST_F(LinkTest, StagedAndroidApi) { - StdErrDiagnostics diag; +struct SourceXML { + std::string res_file_path; + std::string file_contents; +}; + +static void BuildApk(const std::vector& source_files, const std::string& apk_path, + LinkCommandBuilder&& link_args, CommandTestFixture* fixture, + IDiagnostics* diag) { + TemporaryDir res_dir; + TemporaryDir compiled_res_dir; + for (auto& source_file : source_files) { + ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path, + source_file.file_contents, compiled_res_dir.path, diag)); + } + ASSERT_TRUE(fixture->Link( + link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag)); +} + +static void BuildSDK(const std::vector& source_files, const std::string& apk_path, + const std::string& java_root_path, CommandTestFixture* fixture, + IDiagnostics* diag) { + auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build(); + + auto android_link_args = LinkCommandBuilder(fixture) + .SetManifestFile(android_manifest) + .AddParameter("--private-symbols", "com.android.internal") + .AddParameter("--java", java_root_path); + + BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag); +} + +static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path, + CommandTestFixture* fixture, IDiagnostics* diag) { const std::string android_values = R"( @@ -413,6 +444,10 @@ TEST_F(LinkTest, StagedAndroidApi) { + + + + @@ -423,46 +458,90 @@ TEST_F(LinkTest, StagedAndroidApi) { - - + + + + + Hello + )"; + + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values}; + BuildSDK({source_xml}, apk_path, java_path, fixture, diag); +} + +static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path, + CommandTestFixture* fixture, IDiagnostics* diag) { + const std::string android_values = + R"( + + + + + + + + + + + + + + + + + + + + + + - Hello + Hello )"; + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values}; + BuildSDK({source_xml}, apk_path, java_path, fixture, diag); +} + +static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path, + const std::string& sdk_path, CommandTestFixture* fixture, + IDiagnostics* diag) { const std::string app_values = R"( + + )"; - const std::string android_res = GetTestPath("android-res"); - ASSERT_TRUE( - CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag)); + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values}; + auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build(); + + auto app_link_args = LinkCommandBuilder(fixture) + .SetManifestFile(app_manifest) + .AddParameter("--java", java_path) + .AddParameter("-I", sdk_path); + + BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag); +} + +TEST_F(LinkTest, StagedAndroidApi) { + StdErrDiagnostics 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_java = GetTestPath("android-java"); + BuildNonFinalizedSDK(android_apk, android_java, this, &diag); const std::string android_r_java = android_java + "/android/R.java"; std::string android_r_contents; @@ -471,35 +550,19 @@ TEST_F(LinkTest, StagedAndroidApi) { EXPECT_THAT( android_r_contents, HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }")); EXPECT_THAT( android_r_contents, HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }")); EXPECT_THAT( android_r_contents, HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }")); - EXPECT_THAT( - android_r_contents, - HasSubstr("public static final int staged_t_string; static { 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 app_java = GetTestPath("app-java"); + BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag); const std::string client_r_java = app_java + "/com/example/app/R.java"; std::string client_r_contents; @@ -520,6 +583,10 @@ TEST_F(LinkTest, StagedAndroidApi) { ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, Eq(0x01010050)); + result = am.GetResourceId("android:string/staged_s_string"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fd0080)); + result = am.GetResourceId("android:attr/staged_s2_res"); ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, Eq(0x01ff0049)); @@ -527,10 +594,88 @@ TEST_F(LinkTest, StagedAndroidApi) { 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"); +TEST_F(LinkTest, FinalizedAndroidApi) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildFinalizedSDK(android_apk, android_java, this, &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 final int staged_s_res=0x01010002;")); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;")); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }")); + ; + + // Build an application against the non-finalized SDK and then load it into an AssetManager with + // the finalized SDK. + const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk"); + const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java"); + BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag); + + const std::string app_apk = GetTestPath("app.apk"); + const std::string app_java = GetTestPath("app-java"); + BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag); + + android::AssetManager2 am; + auto android_asset = android::ApkAssets::Load(android_apk); + auto app_against_non_final = android::ApkAssets::Load(app_apk); + ASSERT_THAT(android_asset, NotNull()); + ASSERT_THAT(app_against_non_final, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()})); + + auto result = am.GetResourceId("android:attr/finalized_res"); ASSERT_TRUE(result.has_value()); - EXPECT_THAT(*result, Eq(0x01fd0072)); + EXPECT_THAT(*result, Eq(0x01010001)); + + result = am.GetResourceId("android:attr/staged_s_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010002)); + + result = am.GetResourceId("android:string/staged_s_string"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01020000)); + + result = am.GetResourceId("android:attr/staged_s2_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010003)); + + { + auto style = am.GetBag(0x7f020000); + ASSERT_TRUE(style.has_value()); + + auto& entry = (*style)->entries[0]; + EXPECT_THAT(entry.key, Eq(0x01010002)); + EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(entry.value.data, Eq(0x01020000)); + } + + // Re-compile the application against the finalized SDK and then load it into an AssetManager with + // the finalized SDK. + const std::string app_apk_respin = GetTestPath("app-respin.apk"); + const std::string app_java_respin = GetTestPath("app-respin-java"); + BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag); + + auto app_against_final = android::ApkAssets::Load(app_apk_respin); + ASSERT_THAT(app_against_final, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()})); + + { + auto style = am.GetBag(0x7f020000); + ASSERT_TRUE(style.has_value()); + + auto& entry = (*style)->entries[0]; + EXPECT_THAT(entry.key, Eq(0x01010002)); + EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(entry.value.data, Eq(0x01020000)); + } } TEST_F(LinkTest, MacroSubstitution) { diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index 9a50b263c006..339b8af5d536 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -129,11 +129,16 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { for (auto& type : package->types) { 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(), entry->visibility, - context->GetDiagnostics())) { - return false; - } + if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility, + context->GetDiagnostics())) { + return false; + } + + auto v = entry->visibility; + v.staged_api = true; + if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v, + context->GetDiagnostics())) { + return false; } if (assigned_id_map_) { @@ -237,7 +242,7 @@ Result 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 " << std::hex << (int)id.type_id(); + error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_; return unexpected(error.str()); } diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index f1b350fe90f7..2ec01cd1f4b8 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -254,6 +254,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { } break; + case android::RES_TABLE_STAGED_ALIAS_TYPE: + if (!ParseOverlayable(parser.chunk())) { + return false; + } + break; + default: diag_->Warn(DiagMessage(source_) << "unexpected chunk type " @@ -489,6 +495,52 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return true; } +bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) { + auto header = ConvertTo(chunk); + if (!header) { + diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk"); + return false; + } + + const auto ref_begin = reinterpret_cast( + ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize)); + const auto ref_end = ref_begin + util::DeviceToHost32(header->count); + for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { + const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId)); + const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId)); + + // If the staged alias chunk comes before the type chunks, the resource ids and resource name + // pairing will not exist at this point. + const auto iter = id_index_.find(finalized_id); + if (iter == id_index_.cend()) { + diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized" + << " resource ID " << finalized_id); + return false; + } + + // Set the staged if of the finalized resource. + const auto& resource_name = iter->second; + const StagedId staged_id_def{.id = staged_id}; + if (!table_->AddResource(NewResourceBuilder(resource_name) + .SetId(finalized_id, OnIdConflict::CREATE_ENTRY) + .SetStagedId(staged_id_def) + .SetAllowMangled(true) + .Build(), + diag_)) { + return false; + } + + // Since a the finalized resource entry is cloned and added to the resource table under the + // staged resource id, remove the cloned resource entry from the table. + if (!table_->RemoveResource(resource_name, staged_id)) { + diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged " + << " resource ID " << staged_id); + return false; + } + } + return true; +} + std::unique_ptr BinaryResourceParser::ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value) { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h index 13dd9828b911..cd71d160703a 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -57,6 +57,7 @@ class BinaryResourceParser { uint8_t package_id); bool ParseLibrary(const android::ResChunk_header* chunk); bool ParseOverlayable(const android::ResChunk_header* chunk); + bool ParseStagedAliases(const android::ResChunk_header* chunk); std::unique_ptr ParseValue(const ResourceNameRef& name, const android::ConfigDescription& config, diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 74ecf47cae4c..a9192e889c17 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -72,7 +72,7 @@ static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) { } struct FlatEntry { - const ResourceEntry* entry; + const ResourceTableEntryView* entry; const Value* value; // The entry string pool index to the entry's name. @@ -286,6 +286,10 @@ class PackageFlattener { return false; } + if (!FlattenAliases(buffer)) { + return false; + } + pkg_writer.Finish(); return true; } @@ -351,8 +355,8 @@ class PackageFlattener { BigBuffer values_buffer(512); for (FlatEntry& flat_entry : *entries) { - CHECK(static_cast(flat_entry.entry->id.value().entry_id()) < num_total_entries); - offsets[flat_entry.entry->id.value().entry_id()] = values_buffer.size(); + CHECK(static_cast(flat_entry.entry->id.value()) < num_total_entries); + offsets[flat_entry.entry->id.value()] = values_buffer.size(); if (!FlattenValue(&flat_entry, &values_buffer)) { diag_->Error(DiagMessage() << "failed to flatten resource '" @@ -404,6 +408,26 @@ class PackageFlattener { return true; } + bool FlattenAliases(BigBuffer* buffer) { + if (aliases_.empty()) { + return true; + } + + ChunkWriter alias_writer(buffer); + auto header = + alias_writer.StartChunk(RES_TABLE_STAGED_ALIAS_TYPE); + header->count = util::HostToDevice32(aliases_.size()); + + auto mapping = alias_writer.NextBlock(aliases_.size()); + for (auto& p : aliases_) { + mapping->stagedResId = util::HostToDevice32(p.first); + mapping->finalizedResId = util::HostToDevice32(p.second); + ++mapping; + } + alias_writer.Finish(); + return true; + } + bool FlattenOverlayable(BigBuffer* buffer) { std::set seen_ids; std::map overlayable_chunks; @@ -413,18 +437,17 @@ class PackageFlattener { CHECK(bool(type.id)) << "type must have an ID set when flattening "; for (auto& entry : type.entries) { CHECK(bool(type.id)) << "entry must have an ID set when flattening "; - if (!entry->overlayable_item) { + if (!entry.overlayable_item) { continue; } - const OverlayableItem& item = entry->overlayable_item.value(); + const OverlayableItem& item = entry.overlayable_item.value(); // Resource ids should only appear once in the resource table - ResourceId id = - android::make_resid(package_.id.value(), type.id.value(), entry->id.value().entry_id()); + ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value()); CHECK(seen_ids.find(id) == seen_ids.end()) << "multiple overlayable definitions found for resource " - << ResourceName(package_.name, type.type, entry->name).to_string(); + << ResourceName(package_.name, type.type, entry.name).to_string(); seen_ids.insert(id); // Find the overlayable chunk with the specified name @@ -452,9 +475,8 @@ class PackageFlattener { if (item.policies == 0) { context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source) - << "overlayable " - << entry->name - << " does not specify policy"); + << "overlayable " << entry.name + << " does not specify policy"); return false; } @@ -520,7 +542,8 @@ class PackageFlattener { } bool FlattenTypeSpec(const ResourceTableTypeView& type, - const std::vector& sorted_entries, BigBuffer* buffer) { + const std::vector& sorted_entries, + BigBuffer* buffer) { ChunkWriter type_spec_writer(buffer); ResTable_typeSpec* spec_header = type_spec_writer.StartChunk(RES_TABLE_TYPE_SPEC_TYPE); @@ -534,7 +557,7 @@ class PackageFlattener { // We can't just take the size of the vector. There may be holes in the // entry ID space. // Since the entries are sorted by ID, the last one will be the biggest. - const size_t num_entries = sorted_entries.back()->id.value().entry_id() + 1; + const size_t num_entries = sorted_entries.back().id.value() + 1; spec_header->entryCount = util::HostToDevice32(num_entries); @@ -542,23 +565,23 @@ class PackageFlattener { // show for which configuration axis the resource changes. uint32_t* config_masks = type_spec_writer.NextBlock(num_entries); - for (const ResourceEntry* entry : sorted_entries) { - const uint16_t entry_id = entry->id.value().entry_id(); + for (const ResourceTableEntryView& entry : sorted_entries) { + const uint16_t entry_id = entry.id.value(); // Populate the config masks for this entry. uint32_t& entry_config_masks = config_masks[entry_id]; - if (entry->visibility.level == Visibility::Level::kPublic) { + if (entry.visibility.level == Visibility::Level::kPublic) { entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); } - if (entry->visibility.staged_api) { + if (entry.visibility.staged_api) { entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API); } - const size_t config_count = entry->values.size(); + const size_t config_count = entry.values.size(); for (size_t i = 0; i < config_count; i++) { - const ConfigDescription& config = entry->values[i]->config; + const ConfigDescription& config = entry.values[i]->config; for (size_t j = i + 1; j < config_count; j++) { - config_masks[entry_id] |= util::HostToDevice32(config.diff(entry->values[j]->config)); + config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config)); } } } @@ -590,7 +613,7 @@ class PackageFlattener { } // Since the entries are sorted by ID, the last ID will be the largest. - const size_t num_entries = type.entries.back()->id.value().entry_id() + 1; + const size_t num_entries = type.entries.back().id.value() + 1; // The binary resource table lists resource entries for each // configuration. @@ -603,20 +626,26 @@ class PackageFlattener { // hardcoded string uses characters which make it an invalid resource name const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; - for (const ResourceEntry* entry : type.entries) { + for (const ResourceTableEntryView& entry : type.entries) { + if (entry.staged_id) { + aliases_.insert(std::make_pair( + entry.staged_id.value().id.id, + ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id)); + } + uint32_t local_key_index; - ResourceName resource_name({}, type.type, entry->name); + ResourceName resource_name({}, type.type, entry.name); if (!collapse_key_stringpool_ || name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { - local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); } else { // resource isn't exempt from collapse, add it as obfuscated value local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); } // Group values by configuration. - for (auto& config_value : entry->values) { + for (auto& config_value : entry.values) { config_to_entry_list_map[config_value->config].push_back( - FlatEntry{entry, config_value->value.get(), local_key_index}); + FlatEntry{&entry, config_value->value.get(), local_key_index}); } } @@ -667,6 +696,7 @@ class PackageFlattener { StringPool key_pool_; bool collapse_key_stringpool_; const std::set& name_collapse_exemptions_; + std::map aliases_; }; } // namespace @@ -684,7 +714,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { }); // Write the ResTable header. - const auto& table_view = table->GetPartitionedView(); + const auto& table_view = + table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true}); ChunkWriter table_writer(buffer_); ResTable_header* table_header = table_writer.StartChunk(RES_TABLE_TYPE); table_header->packageCount = util::HostToDevice32(table_view.packages.size()); diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index ec331df480cd..236c38167545 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -498,10 +498,20 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr out_error)) { return false; } - entry->overlayable_item = std::move(overlayable_item); } + if (pb_entry.has_staged_id()) { + const pb::StagedId& pb_staged_id = pb_entry.staged_id(); + + StagedId staged_id; + if (pb_staged_id.has_source()) { + DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source); + } + staged_id.id = pb_staged_id.staged_id(); + entry->staged_id = std::move(staged_id); + } + ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), pb_entry.entry_id().id()); if (resid.is_valid()) { diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index d2f033683cc5..6042ba89bf8a 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -364,43 +364,52 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table static const char* obfuscated_resource_name = "0_resource_name_obfuscated"; for (const auto& entry : type.entries) { pb::Entry* pb_entry = pb_type->add_entry(); - if (entry->id) { - pb_entry->mutable_entry_id()->set_id(entry->id.value().entry_id()); + if (entry.id) { + pb_entry->mutable_entry_id()->set_id(entry.id.value()); } - ResourceName resource_name({}, type.type, entry->name); + ResourceName resource_name({}, type.type, entry.name); if (options.collapse_key_stringpool && options.name_collapse_exemptions.find(resource_name) == options.name_collapse_exemptions.end()) { pb_entry->set_name(obfuscated_resource_name); } else { - pb_entry->set_name(entry->name); + pb_entry->set_name(entry.name); } // 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)); + 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(), + SerializeSourceToPb(entry.visibility.source, source_pool.get(), pb_visibility->mutable_source()); } - pb_visibility->set_comment(entry->visibility.comment); + pb_visibility->set_comment(entry.visibility.comment); - if (entry->allow_new) { + if (entry.allow_new) { pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new(); if (source_pool != nullptr) { - SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(), + SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(), pb_allow_new->mutable_source()); } - pb_allow_new->set_comment(entry->allow_new.value().comment); + pb_allow_new->set_comment(entry.allow_new.value().comment); } - if (entry->overlayable_item) { - SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, + if (entry.overlayable_item) { + SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables, source_pool.get(), pb_entry, out_table); } - for (const std::unique_ptr& config_value : entry->values) { + if (entry.staged_id) { + pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id(); + if (source_pool != nullptr) { + SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(), + pb_staged_id->mutable_source()); + } + pb_staged_id->set_staged_id(entry.staged_id.value().id.id); + } + + for (const ResourceConfigValue* config_value : entry.values) { pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); SerializeConfig(config_value->config, pb_config_value->mutable_config()); pb_config_value->mutable_config()->set_product(config_value->product); diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index e563eda93e20..38c811fe3619 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -928,4 +928,27 @@ TEST(ProtoSerializeTest, SerializeMacro) { EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces)); } +TEST(ProtoSerializeTest, StagedId) { + CloningValueTransformer cloner(nullptr); + std::unique_ptr context = test::ContextBuilder().Build(); + std::unique_ptr table = test::ResourceTableBuilder() + .Add(NewResourceBuilder("com.app.a:string/foo") + .SetStagedId(StagedId{.id = 0x01ff0001}) + .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 result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo")); + ASSERT_TRUE(result); + ASSERT_TRUE(result.value().entry->staged_id); + EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001))); +} + } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index bc93ec6908e7..22f4d18dc3ca 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -151,6 +151,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->overlayable_item = std::move(src_entry->overlayable_item); } + if (src_entry->staged_id) { + if (dst_entry->staged_id && + dst_entry->staged_id.value().id != src_entry->staged_id.value().id) { + context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source) + << "conflicting staged id declaration for resource '" + << src_entry->name << "'"); + context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source) + << "previous declaration here"); + } + dst_entry->staged_id = std::move(src_entry->staged_id); + } + return true; } diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index f94f0fe1144a..285e5a11b4c0 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -80,9 +80,6 @@ void TestDirectoryFixture::TearDown() { } void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) { - CHECK(util::StartsWith(path, temp_dir_)) - << "Attempting to create a file outside of test temporary directory."; - // Create any intermediate directories specified in the path auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep); if (pos != path.rend()) {