From 2fedba9a32d9e92344eaf6e9faf5b43e1bc2ae70 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 23 Apr 2021 07:47:38 -0700 Subject: [PATCH] Add to aapt2 To allow apps that compiled against a pre-release SDK to continue working for a period of time after API finalization, a new tag, , has been added to aapt2. When finalizing the framework resource API, converting tags to will cause aapt2 to generate the resource table so that there is a resource entry for the old non-finalized (staged) resource ID and another entry for the finalized resource ID of newly finalized resources. This allows an application that compiled against the pre-release SDK to continue resolving resources using pre-release resource IDs. All references to pre-release resource IDs will be rewritten to their finalized resource IDs through the information stored in the new staged alias chunk. This allows applications compiled against resources to use the newly finalized resource ID without re-compilation. When an application is re-compiled against the SDK with tags, the application will use the finalized resource IDs. This change limits the use of the alias chunk to the framework for S. Bug: 183411356 Test: aapt2_test Change-Id: Iba1c3033c3c2f32de8e4a19b58d3921c971092c4 --- libs/androidfw/AssetManager2.cpp | 10 + libs/androidfw/LoadedArsc.cpp | 37 +++ libs/androidfw/ResourceTypes.cpp | 16 +- libs/androidfw/include/androidfw/LoadedArsc.h | 26 +- .../include/androidfw/ResourceTypes.h | 62 +++-- tools/aapt2/Debug.cpp | 21 +- tools/aapt2/ResourceParser.cpp | 18 +- tools/aapt2/ResourceParser.h | 1 + tools/aapt2/ResourceTable.cpp | 147 ++++++++--- tools/aapt2/ResourceTable.h | 35 ++- tools/aapt2/Resources.proto | 9 + tools/aapt2/cmd/Diff.cpp | 61 +++-- tools/aapt2/cmd/Link_test.cpp | 235 ++++++++++++++---- tools/aapt2/compile/IdAssigner.cpp | 17 +- .../format/binary/BinaryResourceParser.cpp | 52 ++++ .../format/binary/BinaryResourceParser.h | 1 + tools/aapt2/format/binary/TableFlattener.cpp | 85 +++++-- tools/aapt2/format/proto/ProtoDeserialize.cpp | 12 +- tools/aapt2/format/proto/ProtoSerialize.cpp | 37 +-- .../format/proto/ProtoSerialize_test.cpp | 23 ++ tools/aapt2/link/TableMerger.cpp | 12 + tools/aapt2/test/Fixture.cpp | 3 - 22 files changed, 717 insertions(+), 203 deletions(-) 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()) {