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
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index fb89835..ecf6bd4 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -38,6 +38,7 @@
"ResourceTypes.cpp",
"StreamingZipInflater.cpp",
"TypeWrappers.cpp",
+ "Util.cpp",
"ZipFileRO.cpp",
"ZipUtils.cpp",
],
@@ -82,6 +83,9 @@
},
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 55f4c3c..9a08f63 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 @@
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 8d65925..d2eff65 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -36,13 +36,84 @@
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 @@
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 @@
}
}
-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 @@
}
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 @@
desired_config = &density_override_config;
}
- LoadedArsc::Entry best_entry;
+ 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);
+
+ if (type_id == 0) {
+ LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+ return kInvalidCookie;
+ }
+
+ 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;
- int32_t best_index = -1;
- uint32_t cumulated_flags = 0;
+ ApkAssetsCookie best_cookie = kInvalidCookie;
+ uint32_t cumulated_flags = 0u;
- 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();
-
- LoadedArsc::Entry current_entry;
+ 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 @@
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::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 @@
uint32_t* out_flags) {
ATRACE_CALL();
- LoadedArsc::Entry entry;
+ LoadedArscEntry entry;
ResTable_config config;
uint32_t flags = 0u;
ApkAssetsCookie cookie =
@@ -234,6 +332,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 747aa4a..d8adbe5 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 94d0d46..e17a3a6 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 @@
// 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 @@
} // 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 @@
// 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 @@
}
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 0000000..202bc8e
--- /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 a3d67f0..9d4fd29 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -32,6 +32,7 @@
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 @@
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 66d5034..8655339 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 @@
// 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 @@
// 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 @@
// `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 @@
// 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 @@
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
similarity index 100%
rename from libs/androidfw/Chunk.h
rename to libs/androidfw/include/androidfw/Chunk.h
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e2e56c8..8362008 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 @@
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 @@
// (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 dadfe89..cdcf971 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1521,6 +1521,8 @@
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 @@
*/
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 @@
// 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 5266d09..fd96730 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -102,6 +102,10 @@
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 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 @@
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 3a1fc8f..0203712 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -26,9 +26,28 @@
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 9ff9478..b3c2dc3 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 @@
}
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);
}
}
+
+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;
+ }
+ }
+
+ 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(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) {
- AssetManager assets;
- if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()),
- nullptr /* cookie */, false /* appAsLib */,
- false /* isSystemAssets */)) {
- state.SkipWithError("Failed to load assets");
- return;
- }
-
- const ResTable& table = assets.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);
- }
+ GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
+ basic::R::integer::number1, state);
}
BENCHMARK(BM_AssetManagerGetResourceOld);
+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_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);
+
constexpr static const uint32_t kStringOkId = 0x0104000au;
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;
- }
-
- AssetManager2 assets;
- assets.SetApkAssets({apk.get()});
-
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 @@
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 39c5381..543456a 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 47b3894..045507e 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 @@
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, FindDefaultEntry) {
- base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
std::string contents;
ASSERT_TRUE(
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
@@ -65,7 +66,7 @@
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 @@
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 b151f3f..ad1cd2b 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, 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 c0011b6d..59cb18a 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 @@
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 @@
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
deleted file mode 100644
index 44c27c7..0000000
--- a/libs/androidfw/tests/data/lib/lib.apk
+++ /dev/null
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/lib_one/AndroidManifest.xml
similarity index 95%
rename from libs/androidfw/tests/data/lib/AndroidManifest.xml
rename to libs/androidfw/tests/data/lib_one/AndroidManifest.xml
index 02f5d3e..860adf7 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
similarity index 80%
rename from libs/androidfw/tests/data/lib/R.h
rename to libs/androidfw/tests/data/lib_one/R.h
index bb22d22..fcaeb8d 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 @@
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
similarity index 89%
copy from libs/androidfw/tests/data/lib/build
copy to libs/androidfw/tests/data/lib_one/build
index 5c3d02c..c6adf0b 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
new file mode 100644
index 0000000..f287554
--- /dev/null
+++ b/libs/androidfw/tests/data/lib_one/lib_one.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib_one/res/values/values.xml
similarity index 66%
copy from libs/androidfw/tests/data/lib/res/values/values.xml
copy to libs/androidfw/tests/data/lib_one/res/values/values.xml
index 51e3a40..752b7e9 100644
--- a/libs/androidfw/tests/data/lib/res/values/values.xml
+++ b/libs/androidfw/tests/data/lib_one/res/values/values.xml
@@ -15,11 +15,18 @@
-->
<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:attr1">700</item>
- <item name="com.android.lib:attr2">?com.android.lib:attr1</item>
+ <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/AndroidManifest.xml b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml
similarity index 87%
copy from libs/androidfw/tests/data/lib/AndroidManifest.xml
copy to libs/androidfw/tests/data/lib_two/AndroidManifest.xml
index 02f5d3e..4b131e5 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.lib">
+<manifest package="com.android.lib_two">
<application />
</manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib_two/R.h
similarity index 72%
copy from libs/androidfw/tests/data/lib/R.h
copy to libs/androidfw/tests/data/lib_two/R.h
index bb22d22..c04a9d3 100644
--- a/libs/androidfw/tests/data/lib/R.h
+++ b/libs/androidfw/tests/data/lib_two/R.h
@@ -14,32 +14,26 @@
* limitations under the License.
*/
-#ifndef TEST_DATA_LIB_R_H_
-#define TEST_DATA_LIB_R_H_
+#ifndef TEST_DATA_LIB_TWO_R_H_
+#define TEST_DATA_LIB_TWO_R_H_
#include <cstdint>
namespace com {
namespace android {
-namespace lib {
+namespace lib_two {
struct R {
- struct attr {
+ struct string {
enum : uint32_t {
- attr1 = 0x02010000, // default
- attr2 = 0x02010001, // default
- };
- };
-
- struct style {
- enum : uint32_t {
- Theme = 0x02020000, // default
+ LibraryString = 0x02020000, // default
+ foo = 0x02020001, // default
};
};
};
-} // namespace lib
+} // namespace lib_two
} // namespace android
} // namespace com
-#endif // TEST_DATA_R_H_
+#endif // TEST_DATA_LIB_TWO_R_H_
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib_two/build
similarity index 89%
rename from libs/androidfw/tests/data/lib/build
rename to libs/androidfw/tests/data/lib_two/build
index 5c3d02c..fd75e1d 100755
--- a/libs/androidfw/tests/data/lib/build
+++ b/libs/androidfw/tests/data/lib_two/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_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
new file mode 100644
index 0000000..ad44f9c
--- /dev/null
+++ b/libs/androidfw/tests/data/lib_two/lib_two.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml
similarity index 73%
rename from libs/androidfw/tests/data/lib/res/values/values.xml
rename to libs/androidfw/tests/data/lib_two/res/values/values.xml
index 51e3a40..f4eea26 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/lib/AndroidManifest.xml b/libs/androidfw/tests/data/libclient/AndroidManifest.xml
similarity index 87%
copy from libs/androidfw/tests/data/lib/AndroidManifest.xml
copy to libs/androidfw/tests/data/libclient/AndroidManifest.xml
index 02f5d3e..8436383 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/libclient/AndroidManifest.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.lib">
+<manifest package="com.android.libclient">
<application />
</manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/libclient/R.h
similarity index 75%
copy from libs/androidfw/tests/data/lib/R.h
copy to libs/androidfw/tests/data/libclient/R.h
index bb22d22..43d1f9b 100644
--- a/libs/androidfw/tests/data/lib/R.h
+++ b/libs/androidfw/tests/data/libclient/R.h
@@ -21,24 +21,31 @@
namespace com {
namespace android {
-namespace lib {
+namespace libclient {
struct R {
struct attr {
enum : uint32_t {
- attr1 = 0x02010000, // default
- attr2 = 0x02010001, // default
+ foo = 0x7f010000, // default
+ bar = 0x7f010001, // default
};
};
struct style {
enum : uint32_t {
- Theme = 0x02020000, // default
+ Theme = 0x7f020000, // default
+ };
+ };
+
+ struct string {
+ enum : uint32_t {
+ foo_one = 0x7f030000, // default
+ foo_two = 0x7f030001, // default
};
};
};
-} // namespace lib
+} // namespace libclient
} // namespace android
} // namespace com
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/libclient/build
similarity index 66%
copy from libs/androidfw/tests/data/lib/build
copy to libs/androidfw/tests/data/libclient/build
index 5c3d02c..08310e3 100755
--- a/libs/androidfw/tests/data/lib/build
+++ b/libs/androidfw/tests/data/libclient/build
@@ -17,4 +17,14 @@
set -e
-aapt package -M AndroidManifest.xml -S res -F lib.apk -f --shared-lib
+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
new file mode 100644
index 0000000..1799024
--- /dev/null
+++ b/libs/androidfw/tests/data/libclient/libclient.apk
Binary files differ
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 0000000..fead7c3
--- /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>