diff options
author | 2016-12-29 16:08:16 -0500 | |
---|---|---|
committer | 2017-01-11 17:20:36 -0800 | |
commit | da431a22da38f9c4085b5d71ed9a9c6122c6a5a6 (patch) | |
tree | 7fc684c1ec3c653ff98bdc8eff50addc081a02e1 | |
parent | 7ad1110ecd6a840fcd2895c62668828a1ca029c6 (diff) |
libandroidfw: Add new support for shared libraries
This adds support for shared resource libraries in the new
ResTable/AssetManager implementation.
The dynamic package map encoded in resources.arsc is parsed
and stored with LoadedArsc, and combined to form a resolved table
in AssetManager2.
Benchmarks show that this implementation is an order of magnitude
faster on angler-userdebug (make libandroidfw_benchmarks).
Test: libandroidfw_tests
Change-Id: I57c80248728b63b162bf8269ac9495b53c3e7fa0
34 files changed, 1069 insertions, 221 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index fb898355db92..ecf6bd4a0cf1 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -38,6 +38,7 @@ cc_library { "ResourceTypes.cpp", "StreamingZipInflater.cpp", "TypeWrappers.cpp", + "Util.cpp", "ZipFileRO.cpp", "ZipUtils.cpp", ], @@ -82,6 +83,9 @@ cc_library { }, windows: { enabled: true, + cppflags: ["-Wno-missing-field-initializers"], // The Windows compiler warns + // incorrectly for value + // initialization with {}. }, }, } diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 55f4c3ce6e76..9a08f638f230 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -28,7 +28,16 @@ namespace android { std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path) { - ATRACE_NAME("ApkAssets::Load"); + return ApkAssets::LoadImpl(path, false /*load_as_shared_library*/); +} + +std::unique_ptr<ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path) { + return ApkAssets::LoadImpl(path, true /*load_as_shared_library*/); +} + +std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(const std::string& path, + bool load_as_shared_library) { + ATRACE_CALL(); ::ZipArchiveHandle unmanaged_handle; int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle); if (result != 0) { @@ -61,7 +70,7 @@ std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path) { loaded_apk->path_ = path; loaded_apk->loaded_arsc_ = LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/), - loaded_apk->resources_asset_->getLength()); + loaded_apk->resources_asset_->getLength(), load_as_shared_library); if (loaded_apk->loaded_arsc_ == nullptr) { return {}; } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 8d65925a218e..d2eff65106cd 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -36,13 +36,84 @@ AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches) { apk_assets_ = apk_assets; + BuildDynamicRefTable(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } return true; } -const std::vector<const ApkAssets*> AssetManager2::GetApkAssets() const { return apk_assets_; } +void AssetManager2::BuildDynamicRefTable() { + package_groups_.clear(); + package_ids_.fill(0xff); + + // 0x01 is reserved for the android package. + int next_package_id = 0x02; + const size_t apk_assets_count = apk_assets_.size(); + for (size_t i = 0; i < apk_assets_count; i++) { + const ApkAssets* apk_asset = apk_assets_[i]; + for (const std::unique_ptr<const LoadedPackage>& package : + apk_asset->GetLoadedArsc()->GetPackages()) { + // Get the package ID or assign one if a shared library. + int package_id; + if (package->IsDynamic()) { + package_id = next_package_id++; + } else { + package_id = package->GetPackageId(); + } + + // Add the mapping for package ID to index if not present. + uint8_t idx = package_ids_[package_id]; + if (idx == 0xff) { + package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size()); + package_groups_.push_back({}); + package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id; + } + PackageGroup* package_group = &package_groups_[idx]; + + // Add the package and to the set of packages with the same ID. + package_group->packages_.push_back(package.get()); + package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i)); + + // Add the package name -> build time ID mappings. + for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) { + String16 package_name(entry.package_name.c_str(), entry.package_name.size()); + package_group->dynamic_ref_table.mEntries.replaceValueFor( + package_name, static_cast<uint8_t>(entry.package_id)); + } + } + } + + // Now assign the runtime IDs so that we have a build-time to runtime ID map. + const auto package_groups_end = package_groups_.end(); + for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { + const std::string& package_name = iter->packages_[0]->GetPackageName(); + 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); + } + } +} + +void AssetManager2::DumpToLog() const { + base::ScopedLogSeverity _log(base::INFO); + + std::string list; + for (size_t i = 0; i < package_ids_.size(); i++) { + if (package_ids_[i] != 0xff) { + base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]); + } + } + LOG(INFO) << "Package ID map: " << list; + + for (const auto& package_group: package_groups_) { + list = ""; + for (const auto& package : package_group.packages_) { + base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId()); + } + LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + } +} const ResStringPool* AssetManager2::GetStringPoolForCookie(ApkAssetsCookie cookie) const { if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) { @@ -51,6 +122,18 @@ const ResStringPool* AssetManager2::GetStringPoolForCookie(ApkAssetsCookie cooki return apk_assets_[cookie]->GetLoadedArsc()->GetStringPool(); } +const DynamicRefTable* AssetManager2::GetDynamicRefTableForPackage(uint32_t package_id) const { + if (package_id >= package_ids_.size()) { + return nullptr; + } + + const size_t idx = package_ids_[package_id]; + if (idx == 0xff) { + return nullptr; + } + return &package_groups_[idx].dynamic_ref_table; +} + void AssetManager2::SetConfiguration(const ResTable_config& configuration) { const int diff = configuration_.diff(configuration); configuration_ = configuration; @@ -60,8 +143,6 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { } } -const ResTable_config& AssetManager2::GetConfiguration() const { return configuration_; } - std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, mode); @@ -106,7 +187,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, - bool stop_at_first_match, LoadedArsc::Entry* out_entry, + bool stop_at_first_match, LoadedArscEntry* out_entry, ResTable_config* out_selected_config, uint32_t* out_flags) { ATRACE_CALL(); @@ -122,48 +203,66 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri desired_config = &density_override_config; } - LoadedArsc::Entry best_entry; - ResTable_config best_config; - int32_t best_index = -1; - uint32_t cumulated_flags = 0; + const uint32_t package_id = util::get_package_id(resid); + const uint8_t type_id = util::get_type_id(resid); + const uint16_t entry_id = util::get_entry_id(resid); - const size_t apk_asset_count = apk_assets_.size(); - for (size_t i = 0; i < apk_asset_count; i++) { - const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc(); + if (type_id == 0) { + LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid); + return kInvalidCookie; + } - LoadedArsc::Entry current_entry; + const uint8_t idx = package_ids_[package_id]; + if (idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); + return kInvalidCookie; + } + + LoadedArscEntry best_entry; + ResTable_config best_config; + ApkAssetsCookie best_cookie = kInvalidCookie; + uint32_t cumulated_flags = 0u; + + const PackageGroup& package_group = package_groups_[idx]; + const size_t package_count = package_group.packages_.size(); + for (size_t i = 0; i < package_count; i++) { + LoadedArscEntry current_entry; ResTable_config current_config; - uint32_t flags = 0; - if (!loaded_arsc->FindEntry(resid, *desired_config, ¤t_entry, ¤t_config, &flags)) { + uint32_t current_flags = 0; + + const LoadedPackage* loaded_package = package_group.packages_[i]; + if (!loaded_package->FindEntry(type_id - 1, entry_id, *desired_config, ¤t_entry, + ¤t_config, ¤t_flags)) { continue; } - cumulated_flags |= flags; + cumulated_flags |= current_flags; - if (best_index == -1 || current_config.isBetterThan(best_config, desired_config)) { + if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) { best_entry = current_entry; best_config = current_config; - best_index = static_cast<int32_t>(i); + best_cookie = package_group.cookies_[i]; if (stop_at_first_match) { break; } } } - if (best_index == -1) { + if (best_cookie == kInvalidCookie) { return kInvalidCookie; } *out_entry = best_entry; + out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; *out_selected_config = best_config; *out_flags = cumulated_flags; - return best_index; + return best_cookie; } bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { ATRACE_CALL(); - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config config; uint32_t flags = 0u; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, @@ -172,14 +271,13 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return false; } - const std::string* package_name = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageNameForId(resid); - if (package_name == nullptr) { + const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid); + if (package == nullptr) { return false; } - out_name->package = package_name->data(); - out_name->package_len = package_name->size(); + out_name->package = package->GetPackageName().data(); + out_name->package_len = package->GetPackageName().size(); out_name->type = entry.type_string_ref.string8(&out_name->type_len); out_name->type16 = nullptr; @@ -202,7 +300,7 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { } bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config config; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry, &config, out_flags); @@ -215,7 +313,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, uint32_t* out_flags) { ATRACE_CALL(); - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config config; uint32_t flags = 0u; ApkAssetsCookie cookie = @@ -234,6 +332,10 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, const Res_value* device_value = reinterpret_cast<const Res_value*>( reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size)); out_value->copyFrom_dtoh(*device_value); + + // Convert the package ID to the runtime assigned package ID. + entry.dynamic_ref_table->lookupResourceValue(out_value); + *out_selected_config = config; *out_flags = flags; return cookie; @@ -247,7 +349,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { return cached_iter->second.get(); } - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config config; uint32_t flags = 0u; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, @@ -270,8 +372,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size); const ResTable_map* const map_entry_end = map_entry + dtohl(map->count); - const uint32_t parent = dtohl(map->parent.ident); - if (parent == 0) { + uint32_t parent_resid = dtohl(map->parent.ident); + if (parent_resid == 0) { // There is no parent, meaning there is nothing to inherit and we can do a simple // copy of the entries in the map. const size_t entry_count = map_entry_end - map_entry; @@ -279,9 +381,18 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))}; ResolvedBag::Entry* new_entry = new_bag->entries; for (; map_entry != map_entry_end; ++map_entry) { + uint32_t new_key = dtohl(map_entry->name.ident); + if (!util::is_internal_resid(new_key)) { + // Attributes, arrays, etc don't have a resource id as the name. They specify + // other data, which would be wrong to change via a lookup. + if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + return nullptr; + } + } new_entry->cookie = cookie; new_entry->value.copyFrom_dtoh(map_entry->value); - new_entry->key = dtohl(map_entry->name.ident); + new_entry->key = new_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; ++new_entry; @@ -293,10 +404,14 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { return result; } + // In case the parent is a dynamic reference, resolve it. + entry.dynamic_ref_table->lookupResourceId(&parent_resid); + // Get the parent and do a merge of the keys. - const ResolvedBag* parent_bag = GetBag(parent); + const ResolvedBag* parent_bag = GetBag(parent_resid); if (parent_bag == nullptr) { // Failed to get the parent that should exist. + LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid); return nullptr; } @@ -315,7 +430,14 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // The keys are expected to be in sorted order. Merge the two bags. while (map_entry != map_entry_end && parent_entry != parent_entry_end) { - const uint32_t child_key = dtohl(map_entry->name.ident); + uint32_t child_key = dtohl(map_entry->name.ident); + if (!util::is_internal_resid(child_key)) { + if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid); + return nullptr; + } + } + if (child_key <= parent_entry->key) { // Use the child key if it comes before the parent // or is equal to the parent (overrides). @@ -340,9 +462,16 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Finish the child entries if they exist. while (map_entry != map_entry_end) { + uint32_t new_key = dtohl(map_entry->name.ident); + if (!util::is_internal_resid(new_key)) { + if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + return nullptr; + } + } new_entry->cookie = cookie; new_entry->value.copyFrom_dtoh(map_entry->value); - new_entry->key = dtohl(map_entry->name.ident); + new_entry->key = new_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; ++map_entry; @@ -511,12 +640,43 @@ ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value, type_spec_flags |= entry.type_spec_flags; switch (entry.value.dataType) { + case Res_value::TYPE_NULL: + return kInvalidCookie; + case Res_value::TYPE_ATTRIBUTE: resid = entry.value.data; break; - case Res_value::TYPE_NULL: - return kInvalidCookie; + case Res_value::TYPE_DYNAMIC_ATTRIBUTE: { + // Resolve the dynamic attribute to a normal attribute + // (with the right package ID). + resid = entry.value.data; + const DynamicRefTable* ref_table = + asset_manager_->GetDynamicRefTableForPackage(package_idx); + if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid); + return kInvalidCookie; + } + } break; + + case Res_value::TYPE_DYNAMIC_REFERENCE: { + // Resolve the dynamic reference to a normal reference + // (with the right package ID). + out_value->dataType = Res_value::TYPE_REFERENCE; + out_value->data = entry.value.data; + const DynamicRefTable* ref_table = + asset_manager_->GetDynamicRefTableForPackage(package_idx); + if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x", + out_value->data); + return kInvalidCookie; + } + + if (out_flags != nullptr) { + *out_flags = type_spec_flags; + } + return entry.cookie; + } default: *out_value = entry.value; @@ -550,14 +710,14 @@ bool Theme::SetTo(const Theme& o) { type_spec_flags_ = o.type_spec_flags_; - for (size_t p = 0; p < arraysize(packages_); p++) { + for (size_t p = 0; p < packages_.size(); p++) { const Package* package = o.packages_[p].get(); if (package == nullptr) { packages_[p].reset(); continue; } - for (size_t t = 0; t < arraysize(package->types); t++) { + for (size_t t = 0; t < package->types.size(); t++) { const Type* type = package->types[t].get(); if (type == nullptr) { packages_[p]->types[t].reset(); diff --git a/libs/androidfw/ChunkIterator.cpp b/libs/androidfw/ChunkIterator.cpp index 747aa4ad20e6..d8adbe5ac85d 100644 --- a/libs/androidfw/ChunkIterator.cpp +++ b/libs/androidfw/ChunkIterator.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "Chunk.h" +#include "androidfw/Chunk.h" #include "android-base/logging.h" diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 94d0d4638ba8..e17a3a6ff0a1 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -32,15 +32,15 @@ #endif #endif -#include "Chunk.h" #include "androidfw/ByteBucketArray.h" +#include "androidfw/Chunk.h" #include "androidfw/Util.h" using android::base::StringPrintf; namespace android { -namespace { +constexpr const static int kAppPackageId = 0x7f; // Element of a TypeSpec array. See TypeSpec. struct Type { @@ -76,6 +76,8 @@ struct TypeSpec { // itself. using TypeSpecPtr = util::unique_cptr<TypeSpec>; +namespace { + // Builder that helps accumulate Type structs and then create a single // contiguous block of memory to store both the TypeSpec struct and // the Type structs. @@ -110,37 +112,18 @@ class TypeSpecPtrBuilder { } // namespace -class LoadedPackage { - public: - LoadedPackage() = default; - - bool FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config, - LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config, - uint32_t* out_flags) const; - - ResStringPool type_string_pool_; - ResStringPool key_string_pool_; - std::string package_name_; - int package_id_ = -1; - - ByteBucketArray<TypeSpecPtr> type_specs_; - - private: - DISALLOW_COPY_AND_ASSIGN(LoadedPackage); -}; - -bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config, - LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config, +bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, + LoadedArscEntry* out_entry, ResTable_config* out_selected_config, uint32_t* out_flags) const { - ATRACE_NAME("LoadedPackage::FindEntry"); - const TypeSpecPtr& ptr = type_specs_[type_id]; + ATRACE_CALL(); + const TypeSpecPtr& ptr = type_specs_[type_idx]; if (ptr == nullptr) { return false; } // Don't bother checking if the entry ID is larger than // the number of entries. - if (entry_id >= dtohl(ptr->type_spec->entryCount)) { + if (entry_idx >= dtohl(ptr->type_spec->entryCount)) { return false; } @@ -156,10 +139,10 @@ bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable // The configuration matches and is better than the previous selection. // Find the entry value if it exists for this configuration. size_t entry_count = dtohl(type->type->entryCount); - if (entry_id < entry_count) { + if (entry_idx < entry_count) { const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); - const uint32_t offset = dtohl(entry_offsets[entry_id]); + const uint32_t offset = dtohl(entry_offsets[entry_idx]); if (offset != ResTable_type::NO_ENTRY) { // There is an entry for this resource, record it. best_config = &type->configuration; @@ -175,7 +158,7 @@ bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable } const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1); - *out_flags = dtohl(flags[entry_id]); + *out_flags = dtohl(flags[entry_idx]); *out_selected_config = *best_config; const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>( @@ -191,9 +174,10 @@ bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable // forward declarations and incomplete types. LoadedArsc::~LoadedArsc() {} -bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry, - ResTable_config* out_selected_config, uint32_t* out_flags) const { - ATRACE_NAME("LoadedArsc::FindEntry"); +bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, + LoadedArscEntry* out_entry, ResTable_config* out_selected_config, + uint32_t* out_flags) const { + ATRACE_CALL(); const uint8_t package_id = util::get_package_id(resid); const uint8_t type_id = util::get_type_id(resid); const uint16_t entry_id = util::get_entry_id(resid); @@ -212,11 +196,11 @@ bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, Entry* return false; } -const std::string* LoadedArsc::GetPackageNameForId(uint32_t resid) const { +const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const { const uint8_t package_id = util::get_package_id(resid); for (const auto& loaded_package : packages_) { if (loaded_package->package_id_ == package_id) { - return &loaded_package->package_name_; + return loaded_package.get(); } } return nullptr; @@ -334,15 +318,24 @@ static bool VerifyType(const Chunk& chunk) { return true; } -static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { +std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { ATRACE_CALL(); + std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()}; + const ResTable_package* header = chunk.header<ResTable_package>(); if (header == nullptr) { LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small."; - return false; + return {}; } loaded_package->package_id_ = dtohl(header->id); + if (loaded_package->package_id_ == 0) { + // Package ID of 0 means this is a shared library. + loaded_package->dynamic_ = true; + } + + util::ReadUtf16StringFromDevice(header->name, arraysize(header->name), + &loaded_package->package_name_); // A TypeSpec builder. We use this to accumulate the set of Types // available for a TypeSpec, and later build a single, contiguous block @@ -367,7 +360,7 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { child_chunk.header<ResStringPool_header>(), child_chunk.size()); if (err != NO_ERROR) { LOG(ERROR) << "Corrupt package type string pool."; - return false; + return {}; } } else if (pool_address == header_address + dtohl(header->keyStrings)) { // This string pool is the key string pool. @@ -375,7 +368,7 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { child_chunk.header<ResStringPool_header>(), child_chunk.size()); if (err != NO_ERROR) { LOG(ERROR) << "Corrupt package key string pool."; - return false; + return {}; } } else { LOG(WARNING) << "Too many string pool chunks found in package."; @@ -390,7 +383,7 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { TypeSpecPtr type_spec_ptr = types_builder->Build(); if (type_spec_ptr == nullptr) { LOG(ERROR) << "Too many type configurations, overflow detected."; - return false; + return {}; } loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr); @@ -402,12 +395,12 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>(); if (type_spec == nullptr) { LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE is too small."; - return false; + return {}; } if (type_spec->id == 0) { LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0."; - return false; + return {}; } // The data portion of this chunk contains entry_count 32bit entries, @@ -419,12 +412,12 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { // space for entries (EEEE) in the resource ID 0xPPTTEEEE. if (entry_count > std::numeric_limits<uint16_t>::max()) { LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_SPEC_TYPE: " << entry_count << "."; - return false; + return {}; } if (entry_count * sizeof(uint32_t) > chunk.data_size()) { LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_TYPE_SPEC_TYPE."; - return false; + return {}; } last_type_idx = type_spec->id - 1; @@ -435,28 +428,63 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { const ResTable_type* type = child_chunk.header<ResTable_type>(); if (type == nullptr) { LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small."; - return false; + return {}; } if (type->id == 0) { LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0."; - return false; + return {}; } // Type chunks must be preceded by their TypeSpec chunks. if (!types_builder || type->id - 1 != last_type_idx) { LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without " "RES_TABLE_TYPE_SPEC_TYPE."; - return false; + return {}; } if (!VerifyType(child_chunk)) { - return false; + return {}; } types_builder->AddType(type); } break; + case RES_TABLE_LIBRARY_TYPE: { + const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>(); + if (lib == nullptr) { + LOG(ERROR) << "Chunk RES_TABLE_LIBRARY_TYPE is too small."; + return {}; + } + + if (child_chunk.data_size() / sizeof(ResTable_lib_entry) < dtohl(lib->count)) { + LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_LIBRARY_TYPE."; + return {}; + } + + loaded_package->dynamic_package_map_.reserve(dtohl(lib->count)); + + const ResTable_lib_entry* const entry_begin = + reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr()); + const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count); + for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) { + std::string package_name; + util::ReadUtf16StringFromDevice(entry_iter->packageName, + arraysize(entry_iter->packageName), &package_name); + + if (dtohl(entry_iter->packageId) >= std::numeric_limits<uint8_t>::max()) { + LOG(ERROR) << base::StringPrintf( + "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.", + dtohl(entry_iter->packageId), package_name.c_str()); + return {}; + } + + loaded_package->dynamic_package_map_.emplace_back(std::move(package_name), + dtohl(entry_iter->packageId)); + } + + } break; + default: LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; @@ -468,19 +496,19 @@ static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) { TypeSpecPtr type_spec_ptr = types_builder->Build(); if (type_spec_ptr == nullptr) { LOG(ERROR) << "Too many type configurations, overflow detected."; - return false; + return {}; } loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr); } if (iter.HadError()) { LOG(ERROR) << iter.GetLastError(); - return false; + return {}; } - return true; + return loaded_package; } -bool LoadedArsc::LoadTable(const Chunk& chunk) { +bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) { ATRACE_CALL(); const ResTable_header* header = chunk.header<ResTable_header>(); if (header == nullptr) { @@ -520,10 +548,15 @@ bool LoadedArsc::LoadTable(const Chunk& chunk) { } packages_seen++; - std::unique_ptr<LoadedPackage> loaded_package = util::make_unique<LoadedPackage>(); - if (!LoadPackage(child_chunk, loaded_package.get())) { + std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk); + if (!loaded_package) { return false; } + + // Mark the package as dynamic if we are forcefully loading the Apk as a shared library. + if (loaded_package->package_id_ == kAppPackageId) { + loaded_package->dynamic_ = load_as_shared_library; + } packages_.push_back(std::move(loaded_package)); } break; @@ -540,7 +573,8 @@ bool LoadedArsc::LoadTable(const Chunk& chunk) { return true; } -std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len) { +std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len, + bool load_as_shared_library) { ATRACE_CALL(); // Not using make_unique because the constructor is private. @@ -551,7 +585,7 @@ std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len) { const Chunk chunk = iter.Next(); switch (chunk.type()) { case RES_TABLE_TYPE: - if (!loaded_arsc->LoadTable(chunk)) { + if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) { return {}; } break; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp new file mode 100644 index 000000000000..202bc8e5743a --- /dev/null +++ b/libs/androidfw/Util.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "androidfw/Util.h" + +#include <string> + +#include "utils/ByteOrder.h" +#include "utils/Unicode.h" + +#ifdef _WIN32 +#ifdef ERROR +#undef ERROR +#endif +#endif + +namespace android { +namespace util { + +void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) { + char buf[5]; + while (*src && len != 0) { + char16_t c = static_cast<char16_t>(dtohs(*src)); + utf16_to_utf8(&c, 1, buf, sizeof(buf)); + out->append(buf, strlen(buf)); + ++src; + --len; + } +} + +} // namespace util +} // namespace android diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index a3d67f04eb90..9d4fd29d30d7 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -32,6 +32,7 @@ namespace android { class ApkAssets { public: static std::unique_ptr<ApkAssets> Load(const std::string& path); + static std::unique_ptr<ApkAssets> LoadAsSharedLibrary(const std::string& path); std::unique_ptr<Asset> Open(const std::string& path, Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; @@ -43,6 +44,8 @@ class ApkAssets { private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); + static std::unique_ptr<ApkAssets> LoadImpl(const std::string& path, bool load_as_shared_library); + ApkAssets() = default; struct ZipArchivePtrCloser { diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 66d5034463a5..86553390dadf 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -19,6 +19,7 @@ #include "android-base/macros.h" +#include <array> #include <limits> #include <unordered_map> @@ -95,18 +96,21 @@ class AssetManager2 : public ::AAssetManager { // new resource IDs. bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true); - const std::vector<const ApkAssets*> GetApkAssets() const; + inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; } // Returns the string pool for the given asset cookie. // Use the string pool returned here with a valid Res_value object of // type Res_value::TYPE_STRING. const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const; + // Returns the DynamicRefTable for the given package ID. + const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const; + // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. void SetConfiguration(const ResTable_config& configuration); - const ResTable_config& GetConfiguration() const; + inline const ResTable_config& GetConfiguration() const { return configuration_; } // Searches the set of APKs loaded by this AssetManager and opens the first one found located // in the assets/ directory. @@ -173,6 +177,8 @@ class AssetManager2 : public ::AAssetManager { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); + void DumpToLog() const; + private: DISALLOW_COPY_AND_ASSIGN(AssetManager2); @@ -189,9 +195,13 @@ class AssetManager2 : public ::AAssetManager { // `out_flags` stores the resulting bitmask of configuration axis with which the resource // value varies. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config, + LoadedArscEntry* out_entry, ResTable_config* out_selected_config, uint32_t* out_flags); + // Assigns package IDs to all shared library ApkAssets. + // Should be called whenever the ApkAssets are changed. + void BuildDynamicRefTable(); + // Purge all resources that are cached and vary by the configuration axis denoted by the // bitmask `diff`. void InvalidateCaches(uint32_t diff); @@ -200,6 +210,22 @@ class AssetManager2 : public ::AAssetManager { // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; + struct PackageGroup { + std::vector<const LoadedPackage*> packages_; + std::vector<ApkAssetsCookie> cookies_; + DynamicRefTable dynamic_ref_table; + }; + + // DynamicRefTables for shared library package resolution. + // These are ordered according to apk_assets_. The mappings may change depending on what is + // in apk_assets_, therefore they must be stored in the AssetManager and not in the + // immutable ApkAssets class. + std::vector<PackageGroup> package_groups_; + + // An array mapping package ID to index into package_groups. This keeps the lookup fast + // without taking too much memory. + std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_; + // The current configuration set for this AssetManager. When this changes, cached resources // may need to be purged. ResTable_config configuration_; @@ -279,12 +305,12 @@ class Theme { struct Package { // Each element of Type will be a dynamically sized object // allocated to have the entries stored contiguously with the Type. - util::unique_cptr<Type> types[kTypeCount]; + std::array<util::unique_cptr<Type>, kTypeCount> types; }; AssetManager2* asset_manager_; uint32_t type_spec_flags_ = 0u; - std::unique_ptr<Package> packages_[kPackageCount]; + std::array<std::unique_ptr<Package>, kPackageCount> packages_; }; inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; } diff --git a/libs/androidfw/Chunk.h b/libs/androidfw/include/androidfw/Chunk.h index e87b94087450..e87b94087450 100644 --- a/libs/androidfw/Chunk.h +++ b/libs/androidfw/include/androidfw/Chunk.h diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index e2e56c88ee1d..8362008eccc7 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -22,12 +22,82 @@ #include "android-base/macros.h" +#include "androidfw/ByteBucketArray.h" +#include "androidfw/Chunk.h" #include "androidfw/ResourceTypes.h" +#include "androidfw/Util.h" namespace android { -class Chunk; -class LoadedPackage; +class DynamicPackageEntry { + public: + DynamicPackageEntry() = default; + DynamicPackageEntry(std::string&& package_name, int package_id) + : package_name(std::move(package_name)), package_id(package_id) {} + + std::string package_name; + int package_id = 0; +}; + +struct LoadedArscEntry { + // A pointer to the resource table entry for this resource. + // If the size of the entry is > sizeof(ResTable_entry), it can be cast to + // a ResTable_map_entry and processed as a bag/map. + const ResTable_entry* entry = nullptr; + + // The dynamic package ID map for the package from which this resource came from. + const DynamicRefTable* dynamic_ref_table = nullptr; + + // The string pool reference to the type's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef type_string_ref; + + // The string pool reference to the entry's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef entry_string_ref; +}; + +struct TypeSpec; +class LoadedArsc; + +class LoadedPackage { + friend class LoadedArsc; + + public: + bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, + LoadedArscEntry* out_entry, ResTable_config* out_selected_config, + uint32_t* out_flags) const; + + inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; } + + inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; } + + inline const std::string& GetPackageName() const { return package_name_; } + + inline int GetPackageId() const { return package_id_; } + + inline bool IsDynamic() const { return dynamic_; } + + inline const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const { + return dynamic_package_map_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(LoadedPackage); + + static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk); + + LoadedPackage() = default; + + ResStringPool type_string_pool_; + ResStringPool key_string_pool_; + std::string package_name_; + int package_id_ = -1; + bool dynamic_ = false; + + ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_; + std::vector<DynamicPackageEntry> dynamic_package_map_; +}; // Read-only view into a resource table. This class validates all data // when loading, including offsets and lengths. @@ -35,7 +105,8 @@ class LoadedArsc { public: // Load the resource table from memory. The data's lifetime must out-live the // object returned from this method. - static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len); + static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len, + bool load_as_shared_library = false); ~LoadedArsc(); @@ -43,39 +114,28 @@ class LoadedArsc { // (Res_value::dataType == Res_value::TYPE_STRING) are indexed. inline const ResStringPool* GetStringPool() const { return &global_string_pool_; } - struct Entry { - // A pointer to the resource table entry for this resource. - // If the size of the entry is > sizeof(ResTable_entry), it can be cast to - // a ResTable_map_entry and processed as a bag/map. - const ResTable_entry* entry = nullptr; - - // The string pool reference to the type's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef type_string_ref; - - // The string pool reference to the entry's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef entry_string_ref; - }; - // Finds the resource with ID `resid` with the best value for configuration `config`. // The parameter `out_entry` will be filled with the resulting resource entry. // The resource entry can be a simple entry (ResTable_entry) or a complex bag // (ResTable_entry_map). - bool FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry, + bool FindEntry(uint32_t resid, const ResTable_config& config, LoadedArscEntry* out_entry, ResTable_config* selected_config, uint32_t* out_flags) const; // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist. - const std::string* GetPackageNameForId(uint32_t resid) const; + const LoadedPackage* GetPackageForId(uint32_t resid) const; + + inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { + return packages_; + } private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); LoadedArsc() = default; - bool LoadTable(const Chunk& chunk); + bool LoadTable(const Chunk& chunk, bool load_as_shared_library); ResStringPool global_string_pool_; - std::vector<std::unique_ptr<LoadedPackage>> packages_; + std::vector<std::unique_ptr<const LoadedPackage>> packages_; }; } // namespace android diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index dadfe89d043f..cdcf971fb060 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1521,6 +1521,8 @@ struct ResTable_lib_entry uint16_t packageName[128]; }; +class AssetManager2; + /** * Holds the shared library ID table. Shared libraries are assigned package IDs at * build time, but they may be loaded in a different order, so we need to maintain @@ -1531,7 +1533,9 @@ struct ResTable_lib_entry */ class DynamicRefTable { + friend class AssetManager2; public: + DynamicRefTable() = default; DynamicRefTable(uint8_t packageId, bool appAsLib); // Loads an unmapped reference table from the package. @@ -1546,18 +1550,18 @@ public: // Performs the actual conversion of build-time resource ID to run-time // resource ID. - inline status_t lookupResourceId(uint32_t* resId) const; - inline status_t lookupResourceValue(Res_value* value) const; + status_t lookupResourceId(uint32_t* resId) const; + status_t lookupResourceValue(Res_value* value) const; inline const KeyedVector<String16, uint8_t>& entries() const { return mEntries; } private: - const uint8_t mAssignedPackageId; + uint8_t mAssignedPackageId = 0; uint8_t mLookupTable[256]; KeyedVector<String16, uint8_t> mEntries; - bool mAppAsLib; + bool mAppAsLib = false; }; bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue); diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index 5266d09e84e3..fd96730d2398 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -102,6 +102,10 @@ class unique_cptr { pointer ptr_; }; +inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) { + return resid | (static_cast<uint32_t>(package_id) << 24); +} + inline uint8_t get_package_id(uint32_t resid) { return static_cast<uint8_t>((resid >> 24) & 0x000000ffu); } @@ -113,7 +117,7 @@ inline uint8_t get_type_id(uint32_t resid) { inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); } -inline bool is_internal_id(uint32_t resid) { +inline bool is_internal_resid(uint32_t resid) { return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0; } @@ -121,6 +125,8 @@ inline bool is_valid_resid(uint32_t resid) { return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0; } +void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out); + } // namespace util } // namespace android diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 3a1fc8fa0d3f..02037120f098 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -26,9 +26,28 @@ namespace android { TEST(ApkAssetsTest, LoadApk) { std::unique_ptr<ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); ASSERT_NE(nullptr, loaded_apk); + EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc()); std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); ASSERT_NE(nullptr, asset); } +TEST(ApkAssetsTest, LoadApkAsSharedLibrary) { + std::unique_ptr<ApkAssets> loaded_apk = + ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); + ASSERT_NE(nullptr, loaded_apk); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); + ASSERT_NE(nullptr, loaded_arsc); + ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic()); + + loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); + ASSERT_NE(nullptr, loaded_apk); + + loaded_arsc = loaded_apk->GetLoadedArsc(); + ASSERT_NE(nullptr, loaded_arsc); + ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic()); +} + } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 9ff947807a1e..b3c2dc34cdcf 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -16,6 +16,7 @@ #include "benchmark/benchmark.h" +#include "android-base/stringprintf.h" #include "androidfw/ApkAssets.h" #include "androidfw/AssetManager.h" #include "androidfw/AssetManager2.h" @@ -23,10 +24,12 @@ #include "TestHelpers.h" #include "data/basic/R.h" +#include "data/libclient/R.h" #include "data/styles/R.h" -namespace basic = com::android::basic; namespace app = com::android::app; +namespace basic = com::android::basic; +namespace libclient = com::android::libclient; namespace android { @@ -78,101 +81,108 @@ static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld); -static void BM_AssetManagerGetResource(benchmark::State& state) { - std::unique_ptr<ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - if (apk == nullptr) { - state.SkipWithError("Failed to load assets"); - return; +static void GetResourceBenchmark(const std::vector<std::string>& paths, + const ResTable_config* config, uint32_t resid, + benchmark::State& state) { + std::vector<std::unique_ptr<ApkAssets>> apk_assets; + std::vector<const ApkAssets*> apk_assets_ptrs; + for (const std::string& path : paths) { + std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path); + if (apk == nullptr) { + state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); + return; + } + apk_assets_ptrs.push_back(apk.get()); + apk_assets.push_back(std::move(apk)); } - AssetManager2 assets; - assets.SetApkAssets({apk.get()}); + AssetManager2 assetmanager; + assetmanager.SetApkAssets(apk_assets_ptrs); + if (config != nullptr) { + assetmanager.SetConfiguration(*config); + } Res_value value; ResTable_config selected_config; uint32_t flags; while (state.KeepRunning()) { - assets.GetResource(basic::R::integer::number1, false /* may_be_bag */, - 0u /* density_override */, &value, &selected_config, &flags); + assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value, + &selected_config, &flags); } } -BENCHMARK(BM_AssetManagerGetResource); -static void BM_AssetManagerGetResourceOld(benchmark::State& state) { - AssetManager assets; - if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()), - nullptr /* cookie */, false /* appAsLib */, - false /* isSystemAssets */)) { - state.SkipWithError("Failed to load assets"); - return; +static void GetResourceBenchmarkOld(const std::vector<std::string>& paths, + const ResTable_config* config, uint32_t resid, + benchmark::State& state) { + AssetManager assetmanager; + for (const std::string& path : paths) { + if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */, + false /* appAsLib */, false /* isSystemAssets */)) { + state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); + return; + } } - const ResTable& table = assets.getResources(true); + if (config != nullptr) { + assetmanager.setConfiguration(*config); + } + + const ResTable& table = assetmanager.getResources(true); Res_value value; ResTable_config selected_config; uint32_t flags; while (state.KeepRunning()) { - table.getResource(basic::R::integer::number1, &value, false /* may_be_bag */, - 0u /* density_override */, &flags, &selected_config); + table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, + &selected_config); } } + +static void BM_AssetManagerGetResource(benchmark::State& state) { + GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, + basic::R::integer::number1, state); +} +BENCHMARK(BM_AssetManagerGetResource); + +static void BM_AssetManagerGetResourceOld(benchmark::State& state) { + GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, + basic::R::integer::number1, state); +} BENCHMARK(BM_AssetManagerGetResourceOld); -constexpr static const uint32_t kStringOkId = 0x0104000au; +static void BM_AssetManagerGetLibraryResource(benchmark::State& state) { + GetResourceBenchmark( + {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk", + GetTestDataPath() + "/libclient/libclient.apk"}, + nullptr /*config*/, libclient::R::string::foo_one, state); +} +BENCHMARK(BM_AssetManagerGetLibraryResource); -static void BM_AssetManagerGetResourceFrameworkLocale(benchmark::State& state) { - std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath); - if (apk == nullptr) { - state.SkipWithError("Failed to load assets"); - return; - } +static void BM_AssetManagerGetLibraryResourceOld(benchmark::State& state) { + GetResourceBenchmarkOld( + {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk", + GetTestDataPath() + "/libclient/libclient.apk"}, + nullptr /*config*/, libclient::R::string::foo_one, state); +} +BENCHMARK(BM_AssetManagerGetLibraryResourceOld); - AssetManager2 assets; - assets.SetApkAssets({apk.get()}); +constexpr static const uint32_t kStringOkId = 0x0104000au; +static void BM_AssetManagerGetResourceFrameworkLocale(benchmark::State& state) { ResTable_config config; memset(&config, 0, sizeof(config)); memcpy(config.language, "fr", 2); - assets.SetConfiguration(config); - - Res_value value; - ResTable_config selected_config; - uint32_t flags; - - while (state.KeepRunning()) { - assets.GetResource(kStringOkId, false /* may_be_bag */, 0u /* density_override */, &value, - &selected_config, &flags); - } + GetResourceBenchmark({kFrameworkPath}, &config, kStringOkId, state); } BENCHMARK(BM_AssetManagerGetResourceFrameworkLocale); static void BM_AssetManagerGetResourceFrameworkLocaleOld(benchmark::State& state) { - AssetManager assets; - if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()), - nullptr /* cookie */, false /* appAsLib */, - false /* isSystemAssets */)) { - state.SkipWithError("Failed to load assets"); - return; - } - ResTable_config config; memset(&config, 0, sizeof(config)); memcpy(config.language, "fr", 2); - assets.setConfiguration(config, nullptr); - - const ResTable& table = assets.getResources(true); - - Res_value value; - ResTable_config selected_config; - uint32_t flags; - - while (state.KeepRunning()) { - table.getResource(kStringOkId, &value, false /* may_be_bag */, 0u /* density_override */, - &flags, &selected_config); - } + GetResourceBenchmarkOld({kFrameworkPath}, &config, kStringOkId, state); } BENCHMARK(BM_AssetManagerGetResourceFrameworkLocaleOld); @@ -202,8 +212,7 @@ BENCHMARK(BM_AssetManagerGetBag); static void BM_AssetManagerGetBagOld(benchmark::State& state) { AssetManager assets; if (!assets.addAssetPath(String8((GetTestDataPath() + "/styles/styles.apk").data()), - nullptr /* cookie */, false /* appAsLib */, - false /* isSystemAssets */)) { + nullptr /*cookie*/, false /*appAsLib*/, false /*isSystemAssets*/)) { state.SkipWithError("Failed to load assets"); return; } diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 39c5381feb04..543456afa2f4 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -20,11 +20,19 @@ #include "android-base/logging.h" #include "TestHelpers.h" +#include "data/appaslib/R.h" #include "data/basic/R.h" +#include "data/lib_one/R.h" +#include "data/lib_two/R.h" +#include "data/libclient/R.h" #include "data/styles/R.h" -namespace basic = com::android::basic; namespace app = com::android::app; +namespace appaslib = com::android::appaslib::app; +namespace basic = com::android::basic; +namespace lib_one = com::android::lib_one; +namespace lib_two = com::android::lib_two; +namespace libclient = com::android::libclient; namespace android { @@ -39,15 +47,31 @@ class AssetManager2Test : public ::testing::Test { style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); ASSERT_NE(nullptr, style_assets_); + + lib_one_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_one/lib_one.apk"); + ASSERT_NE(nullptr, lib_one_assets_); + + lib_two_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_two/lib_two.apk"); + ASSERT_NE(nullptr, lib_two_assets_); + + libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk"); + ASSERT_NE(nullptr, libclient_assets_); + + appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); + ASSERT_NE(nullptr, appaslib_assets_); } protected: std::unique_ptr<ApkAssets> basic_assets_; std::unique_ptr<ApkAssets> basic_de_fr_assets_; std::unique_ptr<ApkAssets> style_assets_; + std::unique_ptr<ApkAssets> lib_one_assets_; + std::unique_ptr<ApkAssets> lib_two_assets_; + std::unique_ptr<ApkAssets> libclient_assets_; + std::unique_ptr<ApkAssets> appaslib_assets_; }; -TEST_F(AssetManager2Test, FindsResourcesFromSingleApkAssets) { +TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) { ResTable_config desired_config; memset(&desired_config, 0, sizeof(desired_config)); desired_config.language[0] = 'd'; @@ -77,7 +101,7 @@ TEST_F(AssetManager2Test, FindsResourcesFromSingleApkAssets) { EXPECT_EQ(Res_value::TYPE_STRING, value.dataType); } -TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) { +TEST_F(AssetManager2Test, FindsResourceFromMultipleApkAssets) { ResTable_config desired_config; memset(&desired_config, 0, sizeof(desired_config)); desired_config.language[0] = 'd'; @@ -99,7 +123,7 @@ TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) { // Came from our de_fr ApkAssets. EXPECT_EQ(1, cookie); - // The configuration is german. + // The configuration is German. EXPECT_EQ('d', selected_config.language[0]); EXPECT_EQ('e', selected_config.language[1]); @@ -107,7 +131,72 @@ TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) { EXPECT_EQ(Res_value::TYPE_STRING, value.dataType); } -TEST_F(AssetManager2Test, FindsBagResourcesFromSingleApkAssets) { +TEST_F(AssetManager2Test, FindsResourceFromSharedLibrary) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + Res_value value; + ResTable_config selected_config; + uint32_t flags; + + ApkAssetsCookie cookie = + assetmanager.GetResource(libclient::R::string::foo_one, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + // Reference comes from libclient. + EXPECT_EQ(2, cookie); + EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); + + // Lookup the reference. + cookie = assetmanager.GetResource(value.data, false /* may_be_bag */, 0 /* density_override*/, + &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(1, cookie); + EXPECT_EQ(Res_value::TYPE_STRING, value.dataType); + EXPECT_EQ(std::string("Foo from lib_one"), + GetStringFromPool(assetmanager.GetStringPoolForCookie(cookie), value.data)); + + cookie = assetmanager.GetResource(libclient::R::string::foo_two, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + // Reference comes from libclient. + EXPECT_EQ(2, cookie); + EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); + + // Lookup the reference. + cookie = assetmanager.GetResource(value.data, false /* may_be_bag */, 0 /* density_override*/, + &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(0, cookie); + EXPECT_EQ(Res_value::TYPE_STRING, value.dataType); + EXPECT_EQ(std::string("Foo from lib_two"), + GetStringFromPool(assetmanager.GetStringPoolForCookie(cookie), value.data)); +} + +TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({appaslib_assets_.get()}); + + // The appaslib package will have been assigned the package ID 0x02. + + Res_value value; + ResTable_config selected_config; + uint32_t flags; + ApkAssetsCookie cookie = assetmanager.GetResource( + util::fix_package_id(appaslib::R::integer::number1, 0x02), false /*may_be_bag*/, + 0u /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); + EXPECT_EQ(util::fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); +} + +TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); @@ -128,6 +217,27 @@ TEST_F(AssetManager2Test, FindsBagResourcesFromSingleApkAssets) { EXPECT_EQ(0, bag->entries[2].cookie); } +TEST_F(AssetManager2Test, FindsBagResourceFromMultipleApkAssets) {} + +TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme); + ASSERT_NE(nullptr, bag); + ASSERT_GE(bag->entry_count, 2u); + + // First two attributes come from lib_one. + EXPECT_EQ(1, bag->entries[0].cookie); + EXPECT_EQ(0x03, util::get_package_id(bag->entries[0].key)); + EXPECT_EQ(1, bag->entries[1].cookie); + EXPECT_EQ(0x03, util::get_package_id(bag->entries[1].key)); +} + TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({style_assets_.get()}); @@ -181,8 +291,6 @@ TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) { EXPECT_EQ(0, bag_two->entries[4].cookie); } -TEST_F(AssetManager2Test, FindsBagResourcesFromMultipleApkAssets) {} - TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {} TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {} diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 47b3894f0398..045507e2e277 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -16,21 +16,18 @@ #include "androidfw/LoadedArsc.h" -#include "android-base/file.h" -#include "android-base/logging.h" -#include "android-base/macros.h" - #include "TestHelpers.h" #include "data/basic/R.h" +#include "data/libclient/R.h" #include "data/styles/R.h" namespace app = com::android::app; namespace basic = com::android::basic; +namespace libclient = com::android::libclient; namespace android { TEST(LoadedArscTest, LoadSinglePackageArsc) { - base::ScopedLogSeverity _log(base::LogSeverity::DEBUG); std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); @@ -38,11 +35,16 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size()); ASSERT_NE(nullptr, loaded_arsc); + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName()); + EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + ResTable_config config; memset(&config, 0, sizeof(config)); config.sdkVersion = 24; - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config selected_config; uint32_t flags; @@ -52,7 +54,6 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { } TEST(LoadedArscTest, FindDefaultEntry) { - base::ScopedLogSeverity _log(base::LogSeverity::DEBUG); std::string contents; ASSERT_TRUE( ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); @@ -65,7 +66,7 @@ TEST(LoadedArscTest, FindDefaultEntry) { desired_config.language[0] = 'd'; desired_config.language[1] = 'e'; - LoadedArsc::Entry entry; + LoadedArscEntry entry; ResTable_config selected_config; uint32_t flags; @@ -74,6 +75,70 @@ TEST(LoadedArscTest, FindDefaultEntry) { ASSERT_NE(nullptr, entry.entry); } +TEST(LoadedArscTest, LoadSharedLibrary) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", + &contents)); + + std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size()); + ASSERT_NE(nullptr, loaded_arsc); + + const auto& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + + EXPECT_TRUE(packages[0]->IsDynamic()); + EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName()); + EXPECT_EQ(0, packages[0]->GetPackageId()); + + const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); + + // The library has no dependencies. + ASSERT_TRUE(dynamic_pkg_map.empty()); +} + +TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk", + "resources.arsc", &contents)); + + std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size()); + ASSERT_NE(nullptr, loaded_arsc); + + const auto& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + + EXPECT_FALSE(packages[0]->IsDynamic()); + EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName()); + EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + + const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); + + // The library has two dependencies. + ASSERT_EQ(2u, dynamic_pkg_map.size()); + + EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name); + EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id); + + EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name); + EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id); +} + +TEST(LoadedArscTest, LoadAppAsSharedLibrary) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk", + "resources.arsc", &contents)); + + std::unique_ptr<LoadedArsc> loaded_arsc = + LoadedArsc::Load(contents.data(), contents.size(), true /*load_as_shared_library*/); + ASSERT_NE(nullptr, loaded_arsc); + + const auto& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + + EXPECT_TRUE(packages[0]->IsDynamic()); + EXPECT_EQ(0x7f, packages[0]->GetPackageId()); +} + // structs with size fields (like Res_value, ResTable_entry) should be // backwards and forwards compatible (aka checking the size field against // sizeof(Res_value) might not be backwards compatible. diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index b151f3f96496..ad1cd2b289d6 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -25,10 +25,10 @@ #include "TestHelpers.h" #include "data/basic/R.h" -#include "data/lib/R.h" +#include "data/lib_one/R.h" namespace basic = com::android::basic; -namespace lib = com::android::lib; +namespace lib = com::android::lib_one; namespace android { @@ -119,7 +119,7 @@ TEST(ResTableTest, ParentThemeIsAppliedCorrectly) { TEST(ResTableTest, LibraryThemeIsAppliedCorrectly) { std::string contents; - ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib/lib.apk", + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", &contents)); ResTable table; diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index c0011b6d6e89..59cb18a36e6f 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -19,9 +19,13 @@ #include "android-base/logging.h" #include "TestHelpers.h" +#include "data/lib_one/R.h" +#include "data/libclient/R.h" #include "data/styles/R.h" namespace app = com::android::app; +namespace lib_one = com::android::lib_one; +namespace libclient = com::android::libclient; namespace android { @@ -30,10 +34,22 @@ class ThemeTest : public ::testing::Test { void SetUp() override { style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); ASSERT_NE(nullptr, style_assets_); + + libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk"); + ASSERT_NE(nullptr, libclient_assets_); + + lib_one_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_one/lib_one.apk"); + ASSERT_NE(nullptr, lib_one_assets_); + + lib_two_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_two/lib_two.apk"); + ASSERT_NE(nullptr, lib_two_assets_); } protected: std::unique_ptr<ApkAssets> style_assets_; + std::unique_ptr<ApkAssets> libclient_assets_; + std::unique_ptr<ApkAssets> lib_one_assets_; + std::unique_ptr<ApkAssets> lib_two_assets_; }; TEST_F(ThemeTest, EmptyTheme) { @@ -174,6 +190,36 @@ TEST_F(ThemeTest, MultipleThemesOverlaidForced) { EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); } +TEST_F(ThemeTest, ResolveDynamicAttributesAndReferencesToSharedLibrary) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(libclient::R::style::Theme, false /*force*/)); + + Res_value value; + uint32_t flags; + ApkAssetsCookie cookie; + + // The attribute should be resolved to the final value. + cookie = theme->GetAttribute(libclient::R::attr::foo, &value, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType); + EXPECT_EQ(700u, value.data); + EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); + + // The reference should be resolved to a TYPE_REFERENCE. + cookie = theme->GetAttribute(libclient::R::attr::bar, &value, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); + + // lib_one is assigned package ID 0x03. + EXPECT_EQ(3u, util::get_package_id(value.data)); + EXPECT_EQ(util::get_type_id(lib_one::R::string::foo), util::get_type_id(value.data)); + EXPECT_EQ(util::get_entry_id(lib_one::R::string::foo), util::get_entry_id(value.data)); +} + TEST_F(ThemeTest, CopyThemeSameAssetManager) { AssetManager2 assetmanager; assetmanager.SetApkAssets({style_assets_.get()}); diff --git a/libs/androidfw/tests/data/lib/lib.apk b/libs/androidfw/tests/data/lib/lib.apk Binary files differdeleted file mode 100644 index 44c27c79ae7c..000000000000 --- a/libs/androidfw/tests/data/lib/lib.apk +++ /dev/null diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/lib_one/AndroidManifest.xml index 02f5d3efabea..860adf758f92 100644 --- a/libs/androidfw/tests/data/lib/AndroidManifest.xml +++ b/libs/androidfw/tests/data/lib_one/AndroidManifest.xml @@ -15,6 +15,6 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.lib"> + package="com.android.lib_one"> <application /> </manifest> diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib_one/R.h index bb22d22f90e1..fcaeb8dceffb 100644 --- a/libs/androidfw/tests/data/lib/R.h +++ b/libs/androidfw/tests/data/lib_one/R.h @@ -14,14 +14,14 @@ * limitations under the License. */ -#ifndef TEST_DATA_LIB_R_H_ -#define TEST_DATA_LIB_R_H_ +#ifndef TEST_DATA_LIB_ONE_R_H_ +#define TEST_DATA_LIB_ONE_R_H_ #include <cstdint> namespace com { namespace android { -namespace lib { +namespace lib_one { struct R { struct attr { @@ -36,10 +36,16 @@ struct R { Theme = 0x02020000, // default }; }; + + struct string { + enum : uint32_t { + foo = 0x02030000, // default + }; + }; }; -} // namespace lib +} // namespace lib_one } // namespace android } // namespace com -#endif // TEST_DATA_R_H_ +#endif // TEST_DATA_LIB_ONE_R_H_ diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib_one/build index 5c3d02c850bf..c6adf0b1dda6 100755 --- a/libs/androidfw/tests/data/lib/build +++ b/libs/androidfw/tests/data/lib_one/build @@ -17,4 +17,4 @@ set -e -aapt package -M AndroidManifest.xml -S res -F lib.apk -f --shared-lib +aapt package -M AndroidManifest.xml -S res -F lib_one.apk -f --shared-lib diff --git a/libs/androidfw/tests/data/lib_one/lib_one.apk b/libs/androidfw/tests/data/lib_one/lib_one.apk Binary files differnew file mode 100644 index 000000000000..f287554d674d --- /dev/null +++ b/libs/androidfw/tests/data/lib_one/lib_one.apk diff --git a/libs/androidfw/tests/data/lib_one/res/values/values.xml b/libs/androidfw/tests/data/lib_one/res/values/values.xml new file mode 100644 index 000000000000..752b7e912ab6 --- /dev/null +++ b/libs/androidfw/tests/data/lib_one/res/values/values.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <public type="attr" name="attr1" id="0x00010000" /> + <attr name="attr1" format="integer" /> + + <public type="attr" name="attr2" id="0x00010001" /> + <attr name="attr2" format="integer" /> + + <public type="style" name="Theme" id="0x00020000" /> + <style name="Theme"> + <item name="com.android.lib_one:attr1">700</item> + <item name="com.android.lib_one:attr2">?com.android.lib_one:attr1</item> + </style> + + <public type="string" name="foo" id="0x00030000" /> + <string name="foo">Foo from lib_one</string> +</resources> diff --git a/libs/androidfw/tests/data/lib_two/AndroidManifest.xml b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml new file mode 100644 index 000000000000..4b131e55a8a4 --- /dev/null +++ b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest package="com.android.lib_two"> + <application /> +</manifest> diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h new file mode 100644 index 000000000000..c04a9d3b4de0 --- /dev/null +++ b/libs/androidfw/tests/data/lib_two/R.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_DATA_LIB_TWO_R_H_ +#define TEST_DATA_LIB_TWO_R_H_ + +#include <cstdint> + +namespace com { +namespace android { +namespace lib_two { + +struct R { + struct string { + enum : uint32_t { + LibraryString = 0x02020000, // default + foo = 0x02020001, // default + }; + }; +}; + +} // namespace lib_two +} // namespace android +} // namespace com + +#endif // TEST_DATA_LIB_TWO_R_H_ diff --git a/libs/androidfw/tests/data/lib_two/build b/libs/androidfw/tests/data/lib_two/build new file mode 100755 index 000000000000..fd75e1dc7428 --- /dev/null +++ b/libs/androidfw/tests/data/lib_two/build @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +aapt package -M AndroidManifest.xml -S res -F lib_two.apk -f --shared-lib diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk Binary files differnew file mode 100644 index 000000000000..ad44f9c21e31 --- /dev/null +++ b/libs/androidfw/tests/data/lib_two/lib_two.apk diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml index 51e3a407c538..f4eea2610cab 100644 --- a/libs/androidfw/tests/data/lib/res/values/values.xml +++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml @@ -15,11 +15,9 @@ --> <resources> - <attr name="attr1" format="integer" /> - <attr name="attr2" format="integer" /> + <public type="string" name="LibraryString" id="0x00020000" /> + <string name="LibraryString">Hi from library two</string> - <style name="Theme"> - <item name="com.android.lib:attr1">700</item> - <item name="com.android.lib:attr2">?com.android.lib:attr1</item> - </style> + <public type="string" name="foo" id="0x00020001" /> + <string name="foo">Foo from lib_two</string> </resources> diff --git a/libs/androidfw/tests/data/libclient/AndroidManifest.xml b/libs/androidfw/tests/data/libclient/AndroidManifest.xml new file mode 100644 index 000000000000..8436383dbd81 --- /dev/null +++ b/libs/androidfw/tests/data/libclient/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest package="com.android.libclient"> + <application /> +</manifest> diff --git a/libs/androidfw/tests/data/libclient/R.h b/libs/androidfw/tests/data/libclient/R.h new file mode 100644 index 000000000000..43d1f9bb68e7 --- /dev/null +++ b/libs/androidfw/tests/data/libclient/R.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_DATA_LIB_R_H_ +#define TEST_DATA_LIB_R_H_ + +#include <cstdint> + +namespace com { +namespace android { +namespace libclient { + +struct R { + struct attr { + enum : uint32_t { + foo = 0x7f010000, // default + bar = 0x7f010001, // default + }; + }; + + struct style { + enum : uint32_t { + Theme = 0x7f020000, // default + }; + }; + + struct string { + enum : uint32_t { + foo_one = 0x7f030000, // default + foo_two = 0x7f030001, // default + }; + }; +}; + +} // namespace libclient +} // namespace android +} // namespace com + +#endif // TEST_DATA_R_H_ diff --git a/libs/androidfw/tests/data/libclient/build b/libs/androidfw/tests/data/libclient/build new file mode 100755 index 000000000000..08310e3c6eb8 --- /dev/null +++ b/libs/androidfw/tests/data/libclient/build @@ -0,0 +1,30 @@ +#!/bin/bash +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar +PATH_TO_LIB_ONE=../lib_one/lib_one.apk +PATH_TO_LIB_TWO=../lib_two/lib_two.apk + +aapt package \ + -M AndroidManifest.xml \ + -S res \ + -I $PATH_TO_FRAMEWORK_RES \ + -I $PATH_TO_LIB_ONE \ + -I $PATH_TO_LIB_TWO \ + -F libclient.apk -f diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk Binary files differnew file mode 100644 index 000000000000..17990248e862 --- /dev/null +++ b/libs/androidfw/tests/data/libclient/libclient.apk diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml new file mode 100644 index 000000000000..fead7c323767 --- /dev/null +++ b/libs/androidfw/tests/data/libclient/res/values/values.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <public type="attr" name="foo" id="0x7f010000" /> + <attr name="foo" /> + + <public type="attr" name="bar" id="0x7f010001" /> + <attr name="bar" /> + + <public type="style" name="Theme" id="0x7f020000" /> + <style name="Theme" parent="com.android.lib_one:style/Theme"> + <item name="foo">?com.android.lib_one:attr/attr2</item> + <item name="bar">@com.android.lib_one:string/foo</item> + </style> + + <public type="string" name="foo_one" id="0x7f030000" /> + <string name="foo_one">@com.android.lib_one:string/foo</string> + + <public type="string" name="foo_two" id="0x7f030001" /> + <string name="foo_two">@com.android.lib_two:string/foo</string> +</resources> |