libandroidfw: Improve performance of AssetManager2
AssetManager2 relied on creating a list of configurations present in the resource table so as to avoid copying and converting ResTable_config's from the APK on every resource retrieval. ResTable, however, had a better optimization that pruned the configurations that didn't match the currently set configuration. This vastly reduced the number of ResTable_configs to test. In this CL, AssetManager2 follows suite with this optimization and only maintains the filtered ResTable_configs, falling back to the slow path when the configuration is overridden. Test: mma frameworks/base/libs/androidfw Test: adb sync system data Test: adb shell /data/benchmarktest64/libandroidfw_benchmarks/libandroidfw_benchmarks Change-Id: I5d46f8b005a37b72750d00bd75f090e7b5a36f60
This commit is contained in:
@@ -145,6 +145,7 @@ cc_test {
|
||||
"tests/TypeWrappers_test.cpp",
|
||||
"tests/ZipUtils_test.cpp",
|
||||
],
|
||||
static_libs: ["libgmock"],
|
||||
target: {
|
||||
android: {
|
||||
srcs: [
|
||||
|
||||
@@ -36,6 +36,31 @@
|
||||
|
||||
namespace android {
|
||||
|
||||
struct FindEntryResult {
|
||||
// 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;
|
||||
|
||||
// The configuration for which the resulting entry was defined. This is already swapped to host
|
||||
// endianness.
|
||||
ResTable_config config;
|
||||
|
||||
// The bitmask of configuration axis with which the resource value varies.
|
||||
uint32_t type_flags;
|
||||
|
||||
// The dynamic package ID map for the package from which this resource came from.
|
||||
const DynamicRefTable* dynamic_ref_table;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
AssetManager2::AssetManager2() {
|
||||
memset(&configuration_, 0, sizeof(configuration_));
|
||||
}
|
||||
@@ -44,6 +69,7 @@ bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets
|
||||
bool invalidate_caches) {
|
||||
apk_assets_ = apk_assets;
|
||||
BuildDynamicRefTable();
|
||||
RebuildFilterList();
|
||||
if (invalidate_caches) {
|
||||
InvalidateCaches(static_cast<uint32_t>(-1));
|
||||
}
|
||||
@@ -79,7 +105,7 @@ void AssetManager2::BuildDynamicRefTable() {
|
||||
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->packages_.push_back(ConfiguredPackage{package.get(), {}});
|
||||
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
|
||||
|
||||
// Add the package name -> build time ID mappings.
|
||||
@@ -94,7 +120,7 @@ void AssetManager2::BuildDynamicRefTable() {
|
||||
// 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();
|
||||
const std::string& package_name = iter->packages_[0].loaded_package_->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);
|
||||
@@ -108,17 +134,20 @@ void AssetManager2::DumpToLog() const {
|
||||
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]);
|
||||
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;
|
||||
for (const auto& package_group : package_groups_) {
|
||||
list = "";
|
||||
for (const auto& package : package_group.packages_) {
|
||||
base::StringAppendF(&list, "%s(%02x), ", package.loaded_package_->GetPackageName().c_str(),
|
||||
package.loaded_package_->GetPackageId());
|
||||
}
|
||||
LOG(INFO) << base::StringPrintf("PG (%02x): ",
|
||||
package_group.dynamic_ref_table.mAssignedPackageId)
|
||||
<< list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,52 +186,54 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
|
||||
configuration_ = configuration;
|
||||
|
||||
if (diff) {
|
||||
RebuildFilterList();
|
||||
InvalidateCaches(static_cast<uint32_t>(diff));
|
||||
}
|
||||
}
|
||||
|
||||
std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system,
|
||||
bool exclude_mipmap) {
|
||||
bool exclude_mipmap) const {
|
||||
ATRACE_CALL();
|
||||
std::set<ResTable_config> configurations;
|
||||
for (const PackageGroup& package_group : package_groups_) {
|
||||
for (const LoadedPackage* package : package_group.packages_) {
|
||||
if (exclude_system && package->IsSystem()) {
|
||||
for (const ConfiguredPackage& package : package_group.packages_) {
|
||||
if (exclude_system && package.loaded_package_->IsSystem()) {
|
||||
continue;
|
||||
}
|
||||
package->CollectConfigurations(exclude_mipmap, &configurations);
|
||||
package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations);
|
||||
}
|
||||
}
|
||||
return configurations;
|
||||
}
|
||||
|
||||
std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
|
||||
bool merge_equivalent_languages) {
|
||||
bool merge_equivalent_languages) const {
|
||||
ATRACE_CALL();
|
||||
std::set<std::string> locales;
|
||||
for (const PackageGroup& package_group : package_groups_) {
|
||||
for (const LoadedPackage* package : package_group.packages_) {
|
||||
if (exclude_system && package->IsSystem()) {
|
||||
for (const ConfiguredPackage& package : package_group.packages_) {
|
||||
if (exclude_system && package.loaded_package_->IsSystem()) {
|
||||
continue;
|
||||
}
|
||||
package->CollectLocales(merge_equivalent_languages, &locales);
|
||||
package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales);
|
||||
}
|
||||
}
|
||||
return locales;
|
||||
}
|
||||
|
||||
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
|
||||
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename,
|
||||
Asset::AccessMode mode) const {
|
||||
const std::string new_path = "assets/" + filename;
|
||||
return OpenNonAsset(new_path, mode);
|
||||
}
|
||||
|
||||
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
|
||||
Asset::AccessMode mode) {
|
||||
Asset::AccessMode mode) const {
|
||||
const std::string new_path = "assets/" + filename;
|
||||
return OpenNonAsset(new_path, cookie, mode);
|
||||
}
|
||||
|
||||
std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
|
||||
std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const {
|
||||
ATRACE_CALL();
|
||||
|
||||
std::string full_path = "assets/" + dirname;
|
||||
@@ -236,7 +267,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
|
||||
// is inconsistent for split APKs.
|
||||
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
|
||||
Asset::AccessMode mode,
|
||||
ApkAssetsCookie* out_cookie) {
|
||||
ApkAssetsCookie* out_cookie) const {
|
||||
ATRACE_CALL();
|
||||
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
|
||||
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
|
||||
@@ -255,7 +286,8 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
|
||||
}
|
||||
|
||||
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
|
||||
ApkAssetsCookie cookie, Asset::AccessMode mode) {
|
||||
ApkAssetsCookie cookie,
|
||||
Asset::AccessMode mode) const {
|
||||
ATRACE_CALL();
|
||||
if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
|
||||
return {};
|
||||
@@ -264,12 +296,13 @@ 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, FindEntryResult* out_entry) {
|
||||
bool /*stop_at_first_match*/,
|
||||
FindEntryResult* out_entry) const {
|
||||
// Might use this if density_override != 0.
|
||||
ResTable_config density_override_config;
|
||||
|
||||
// Select our configuration or generate a density override configuration.
|
||||
ResTable_config* desired_config = &configuration_;
|
||||
const ResTable_config* desired_config = &configuration_;
|
||||
if (density_override != 0 && density_override != configuration_.density) {
|
||||
density_override_config = configuration_;
|
||||
density_override_config.density = density_override;
|
||||
@@ -283,53 +316,135 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
|
||||
|
||||
const uint32_t package_id = get_package_id(resid);
|
||||
const uint8_t type_idx = get_type_id(resid) - 1;
|
||||
const uint16_t entry_id = get_entry_id(resid);
|
||||
const uint16_t entry_idx = get_entry_id(resid);
|
||||
|
||||
const uint8_t idx = package_ids_[package_id];
|
||||
if (idx == 0xff) {
|
||||
const uint8_t package_idx = package_ids_[package_id];
|
||||
if (package_idx == 0xff) {
|
||||
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
|
||||
return kInvalidCookie;
|
||||
}
|
||||
|
||||
FindEntryResult best_entry;
|
||||
ApkAssetsCookie best_cookie = kInvalidCookie;
|
||||
uint32_t cumulated_flags = 0u;
|
||||
|
||||
const PackageGroup& package_group = package_groups_[idx];
|
||||
const PackageGroup& package_group = package_groups_[package_idx];
|
||||
const size_t package_count = package_group.packages_.size();
|
||||
FindEntryResult current_entry;
|
||||
for (size_t i = 0; i < package_count; i++) {
|
||||
const LoadedPackage* loaded_package = package_group.packages_[i];
|
||||
if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) {
|
||||
|
||||
ApkAssetsCookie best_cookie = kInvalidCookie;
|
||||
const LoadedPackage* best_package = nullptr;
|
||||
const ResTable_type* best_type = nullptr;
|
||||
const ResTable_config* best_config = nullptr;
|
||||
ResTable_config best_config_copy;
|
||||
uint32_t best_offset = 0u;
|
||||
uint32_t type_flags = 0u;
|
||||
|
||||
// If desired_config is the same as the set configuration, then we can use our filtered list
|
||||
// and we don't need to match the configurations, since they already matched.
|
||||
const bool use_fast_path = desired_config == &configuration_;
|
||||
|
||||
for (size_t pi = 0; pi < package_count; pi++) {
|
||||
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
|
||||
const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
|
||||
ApkAssetsCookie cookie = package_group.cookies_[pi];
|
||||
|
||||
// If the type IDs are offset in this package, we need to take that into account when searching
|
||||
// for a type.
|
||||
const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
|
||||
if (UNLIKELY(type_spec == nullptr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cumulated_flags |= current_entry.type_flags;
|
||||
uint16_t local_entry_idx = entry_idx;
|
||||
|
||||
const ResTable_config* current_config = current_entry.config;
|
||||
const ResTable_config* best_config = best_entry.config;
|
||||
if (best_cookie == kInvalidCookie ||
|
||||
current_config->isBetterThan(*best_config, desired_config) ||
|
||||
(loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
|
||||
best_entry = current_entry;
|
||||
best_cookie = package_group.cookies_[i];
|
||||
if (stop_at_first_match) {
|
||||
break;
|
||||
// If there is an IDMAP supplied with this package, translate the entry ID.
|
||||
if (type_spec->idmap_entries != nullptr) {
|
||||
if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
|
||||
// There is no mapping, so the resource is not meant to be in this overlay package.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
|
||||
|
||||
// If the package is an overlay, then even configurations that are the same MUST be chosen.
|
||||
const bool package_is_overlay = loaded_package->IsOverlay();
|
||||
|
||||
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
|
||||
if (use_fast_path) {
|
||||
const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
|
||||
const size_t type_count = candidate_configs.size();
|
||||
for (uint32_t i = 0; i < type_count; i++) {
|
||||
const ResTable_config& this_config = candidate_configs[i];
|
||||
|
||||
// We can skip calling ResTable_config::match() because we know that all candidate
|
||||
// configurations that do NOT match have been filtered-out.
|
||||
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
|
||||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
|
||||
// The configuration matches and is better than the previous selection.
|
||||
// Find the entry value if it exists for this configuration.
|
||||
const ResTable_type* type_chunk = filtered_group.types[i];
|
||||
const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
|
||||
if (offset == ResTable_type::NO_ENTRY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
best_cookie = cookie;
|
||||
best_package = loaded_package;
|
||||
best_type = type_chunk;
|
||||
best_config = &this_config;
|
||||
best_offset = offset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is the slower path, which doesn't use the filtered list of configurations.
|
||||
// Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
|
||||
// and fill in any new fields that did not exist when the APK was compiled.
|
||||
// Furthermore when selecting configurations we can't just record the pointer to the
|
||||
// ResTable_config, we must copy it.
|
||||
const auto iter_end = type_spec->types + type_spec->type_count;
|
||||
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
|
||||
ResTable_config this_config;
|
||||
this_config.copyFromDtoH((*iter)->config);
|
||||
|
||||
if (this_config.match(*desired_config)) {
|
||||
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
|
||||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
|
||||
// The configuration matches and is better than the previous selection.
|
||||
// Find the entry value if it exists for this configuration.
|
||||
const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
|
||||
if (offset == ResTable_type::NO_ENTRY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
best_cookie = cookie;
|
||||
best_package = loaded_package;
|
||||
best_type = *iter;
|
||||
best_config_copy = this_config;
|
||||
best_config = &best_config_copy;
|
||||
best_offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_cookie == kInvalidCookie) {
|
||||
if (UNLIKELY(best_cookie == kInvalidCookie)) {
|
||||
return kInvalidCookie;
|
||||
}
|
||||
|
||||
*out_entry = best_entry;
|
||||
const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
|
||||
if (UNLIKELY(best_entry == nullptr)) {
|
||||
return kInvalidCookie;
|
||||
}
|
||||
|
||||
out_entry->entry = best_entry;
|
||||
out_entry->config = *best_config;
|
||||
out_entry->type_flags = type_flags;
|
||||
out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
|
||||
out_entry->entry_string_ref =
|
||||
StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
|
||||
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
|
||||
out_entry->type_flags = cumulated_flags;
|
||||
return best_cookie;
|
||||
}
|
||||
|
||||
bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
|
||||
bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
|
||||
ATRACE_CALL();
|
||||
|
||||
FindEntryResult entry;
|
||||
@@ -339,7 +454,8 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid);
|
||||
const LoadedPackage* package =
|
||||
apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
|
||||
if (package == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -367,7 +483,7 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
|
||||
bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const {
|
||||
FindEntryResult entry;
|
||||
ApkAssetsCookie cookie =
|
||||
FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
|
||||
@@ -381,7 +497,7 @@ bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
|
||||
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
|
||||
uint16_t density_override, Res_value* out_value,
|
||||
ResTable_config* out_selected_config,
|
||||
uint32_t* out_flags) {
|
||||
uint32_t* out_flags) const {
|
||||
ATRACE_CALL();
|
||||
|
||||
FindEntryResult entry;
|
||||
@@ -400,7 +516,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
|
||||
// Create a reference since we can't represent this complex type as a Res_value.
|
||||
out_value->dataType = Res_value::TYPE_REFERENCE;
|
||||
out_value->data = resid;
|
||||
*out_selected_config = *entry.config;
|
||||
*out_selected_config = entry.config;
|
||||
*out_flags = entry.type_flags;
|
||||
return cookie;
|
||||
}
|
||||
@@ -412,7 +528,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
|
||||
// Convert the package ID to the runtime assigned package ID.
|
||||
entry.dynamic_ref_table->lookupResourceValue(out_value);
|
||||
|
||||
*out_selected_config = *entry.config;
|
||||
*out_selected_config = entry.config;
|
||||
*out_flags = entry.type_flags;
|
||||
return cookie;
|
||||
}
|
||||
@@ -420,7 +536,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
|
||||
ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
|
||||
ResTable_config* in_out_selected_config,
|
||||
uint32_t* in_out_flags,
|
||||
uint32_t* out_last_reference) {
|
||||
uint32_t* out_last_reference) const {
|
||||
ATRACE_CALL();
|
||||
constexpr const int kMaxIterations = 20;
|
||||
|
||||
@@ -488,7 +604,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
|
||||
// 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);
|
||||
LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
|
||||
resid);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -520,7 +637,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
|
||||
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);
|
||||
LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid,
|
||||
resid);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -539,7 +657,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
|
||||
uint32_t child_key = dtohl(map_entry->name.ident);
|
||||
if (!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);
|
||||
LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key,
|
||||
resid);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -578,7 +697,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
|
||||
uint32_t new_key = dtohl(map_entry->name.ident);
|
||||
if (!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);
|
||||
LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
|
||||
resid);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -634,7 +754,7 @@ static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
|
||||
|
||||
uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
|
||||
const std::string& fallback_type,
|
||||
const std::string& fallback_package) {
|
||||
const std::string& fallback_package) const {
|
||||
StringPiece package_name, type, entry;
|
||||
if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) {
|
||||
return 0u;
|
||||
@@ -666,7 +786,8 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
|
||||
const static std::u16string kAttrPrivate16 = u"^attr-private";
|
||||
|
||||
for (const PackageGroup& package_group : package_groups_) {
|
||||
for (const LoadedPackage* package : package_group.packages_) {
|
||||
for (const ConfiguredPackage& package_impl : package_group.packages_) {
|
||||
const LoadedPackage* package = package_impl.loaded_package_;
|
||||
if (package_name != package->GetPackageName()) {
|
||||
// All packages in the same group are expected to have the same package name.
|
||||
break;
|
||||
@@ -688,6 +809,32 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void AssetManager2::RebuildFilterList() {
|
||||
for (PackageGroup& group : package_groups_) {
|
||||
for (ConfiguredPackage& impl : group.packages_) {
|
||||
// Destroy it.
|
||||
impl.filtered_configs_.~ByteBucketArray();
|
||||
|
||||
// Re-create it.
|
||||
new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
|
||||
|
||||
// Create the filters here.
|
||||
impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
|
||||
FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
|
||||
const auto iter_end = spec->types + spec->type_count;
|
||||
for (auto iter = spec->types; iter != iter_end; ++iter) {
|
||||
ResTable_config this_config;
|
||||
this_config.copyFromDtoH((*iter)->config);
|
||||
if (this_config.match(configuration_)) {
|
||||
group.configurations.push_back(this_config);
|
||||
group.types.push_back(*iter);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager2::InvalidateCaches(uint32_t diff) {
|
||||
if (diff == 0xffffffffu) {
|
||||
// Everything must go.
|
||||
@@ -868,7 +1015,7 @@ ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
|
||||
ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
|
||||
ResTable_config* in_out_selected_config,
|
||||
uint32_t* in_out_type_spec_flags,
|
||||
uint32_t* out_last_ref) {
|
||||
uint32_t* out_last_ref) const {
|
||||
if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
|
||||
uint32_t new_flags;
|
||||
cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
|
||||
|
||||
@@ -44,44 +44,6 @@ namespace android {
|
||||
|
||||
constexpr const static int kAppPackageId = 0x7f;
|
||||
|
||||
// Element of a TypeSpec array. See TypeSpec.
|
||||
struct Type {
|
||||
// The configuration for which this type defines entries.
|
||||
// This is already converted to host endianness.
|
||||
ResTable_config configuration;
|
||||
|
||||
// Pointer to the mmapped data where entry definitions are kept.
|
||||
const ResTable_type* type;
|
||||
};
|
||||
|
||||
// TypeSpec is going to be immediately proceeded by
|
||||
// an array of Type structs, all in the same block of memory.
|
||||
struct TypeSpec {
|
||||
// Pointer to the mmapped data where flags are kept.
|
||||
// Flags denote whether the resource entry is public
|
||||
// and under which configurations it varies.
|
||||
const ResTable_typeSpec* type_spec;
|
||||
|
||||
// Pointer to the mmapped data where the IDMAP mappings for this type
|
||||
// exist. May be nullptr if no IDMAP exists.
|
||||
const IdmapEntry_header* idmap_entries;
|
||||
|
||||
// The number of types that follow this struct.
|
||||
// There is a type for each configuration
|
||||
// that entries are defined for.
|
||||
size_t type_count;
|
||||
|
||||
// Trick to easily access a variable number of Type structs
|
||||
// proceeding this struct, and to ensure their alignment.
|
||||
const Type types[0];
|
||||
};
|
||||
|
||||
// TypeSpecPtr points to the block of memory that holds
|
||||
// a TypeSpec struct, followed by an array of Type structs.
|
||||
// TypeSpecPtr is a managed pointer that knows how to delete
|
||||
// itself.
|
||||
using TypeSpecPtr = util::unique_cptr<TypeSpec>;
|
||||
|
||||
namespace {
|
||||
|
||||
// Builder that helps accumulate Type structs and then create a single
|
||||
@@ -95,21 +57,22 @@ class TypeSpecPtrBuilder {
|
||||
}
|
||||
|
||||
void AddType(const ResTable_type* type) {
|
||||
ResTable_config config;
|
||||
config.copyFromDtoH(type->config);
|
||||
types_.push_back(Type{config, type});
|
||||
types_.push_back(type);
|
||||
}
|
||||
|
||||
TypeSpecPtr Build() {
|
||||
// Check for overflow.
|
||||
if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
|
||||
using ElementType = const ResTable_type*;
|
||||
if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
|
||||
types_.size()) {
|
||||
return {};
|
||||
}
|
||||
TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
|
||||
TypeSpec* type_spec =
|
||||
(TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
|
||||
type_spec->type_spec = header_;
|
||||
type_spec->idmap_entries = idmap_header_;
|
||||
type_spec->type_count = types_.size();
|
||||
memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
|
||||
memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
|
||||
return TypeSpecPtr(type_spec);
|
||||
}
|
||||
|
||||
@@ -118,7 +81,7 @@ class TypeSpecPtrBuilder {
|
||||
|
||||
const ResTable_typeSpec* header_;
|
||||
const IdmapEntry_header* idmap_header_;
|
||||
std::vector<Type> types_;
|
||||
std::vector<const ResTable_type*> types_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -162,18 +125,17 @@ static bool VerifyResTableType(const ResTable_type* header) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
|
||||
size_t entry_idx) {
|
||||
static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) {
|
||||
// Check that the offset is aligned.
|
||||
if (entry_offset & 0x03) {
|
||||
LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
|
||||
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the offset doesn't overflow.
|
||||
if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
|
||||
// Overflow in offset.
|
||||
LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
|
||||
LOG(ERROR) << "Entry at offset " << entry_offset << " is too large.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -181,7 +143,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset
|
||||
|
||||
entry_offset += dtohl(type->entriesStart);
|
||||
if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
|
||||
LOG(ERROR) << "Entry offset at index " << entry_idx
|
||||
LOG(ERROR) << "Entry at offset " << entry_offset
|
||||
<< " is too large. No room for ResTable_entry.";
|
||||
return false;
|
||||
}
|
||||
@@ -191,13 +153,13 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset
|
||||
|
||||
const size_t entry_size = dtohs(entry->size);
|
||||
if (entry_size < sizeof(*entry)) {
|
||||
LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
|
||||
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
|
||||
<< " is too small.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
|
||||
LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
|
||||
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
|
||||
<< " is too large.";
|
||||
return false;
|
||||
}
|
||||
@@ -205,7 +167,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset
|
||||
if (entry_size < sizeof(ResTable_map_entry)) {
|
||||
// There needs to be room for one Res_value struct.
|
||||
if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
|
||||
LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
|
||||
LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset
|
||||
<< " for type " << (int)type->id << ".";
|
||||
return false;
|
||||
}
|
||||
@@ -214,12 +176,12 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset
|
||||
reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
|
||||
const size_t value_size = dtohs(value->size);
|
||||
if (value_size < sizeof(Res_value)) {
|
||||
LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
|
||||
LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
|
||||
LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
|
||||
LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset
|
||||
<< " is too large.";
|
||||
return false;
|
||||
}
|
||||
@@ -228,117 +190,76 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset
|
||||
const size_t map_entry_count = dtohl(map->count);
|
||||
size_t map_entries_start = entry_offset + entry_size;
|
||||
if (map_entries_start & 0x03) {
|
||||
LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
|
||||
LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each entry is sizeof(ResTable_map) big.
|
||||
if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
|
||||
LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
|
||||
LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << ".";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
|
||||
const ResTable_config& config, FindEntryResult* out_entry) const {
|
||||
const ResTable_config* best_config = nullptr;
|
||||
const ResTable_type* best_type = nullptr;
|
||||
uint32_t best_offset = 0;
|
||||
const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk,
|
||||
uint16_t entry_index) {
|
||||
uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index);
|
||||
if (entry_offset == ResTable_type::NO_ENTRY) {
|
||||
return nullptr;
|
||||
}
|
||||
return GetEntryFromOffset(type_chunk, entry_offset);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
|
||||
const Type* type = &type_spec_ptr->types[i];
|
||||
const ResTable_type* type_chunk = type->type;
|
||||
uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
|
||||
// The configuration matches and is better than the previous selection.
|
||||
// Find the entry value if it exists for this configuration.
|
||||
const size_t entry_count = dtohl(type_chunk->entryCount);
|
||||
const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
|
||||
|
||||
if (type->configuration.match(config) &&
|
||||
(best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
|
||||
// The configuration matches and is better than the previous selection.
|
||||
// Find the entry value if it exists for this configuration.
|
||||
const size_t entry_count = dtohl(type_chunk->entryCount);
|
||||
const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
|
||||
// Check if there is the desired entry in this type.
|
||||
|
||||
// Check if there is the desired entry in this type.
|
||||
|
||||
if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
|
||||
// This is encoded as a sparse map, so perform a binary search.
|
||||
const ResTable_sparseTypeEntry* sparse_indices =
|
||||
reinterpret_cast<const ResTable_sparseTypeEntry*>(
|
||||
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
|
||||
const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
|
||||
const ResTable_sparseTypeEntry* result =
|
||||
std::lower_bound(sparse_indices, sparse_indices_end, entry_idx,
|
||||
[](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
|
||||
return dtohs(entry.idx) < entry_idx;
|
||||
});
|
||||
|
||||
if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) {
|
||||
// No entry found.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
|
||||
// the real offset divided by 4.
|
||||
best_offset = uint32_t{dtohs(result->offset)} * 4u;
|
||||
} else {
|
||||
if (entry_idx >= entry_count) {
|
||||
// This entry cannot be here.
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
|
||||
if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
|
||||
// This is encoded as a sparse map, so perform a binary search.
|
||||
const ResTable_sparseTypeEntry* sparse_indices =
|
||||
reinterpret_cast<const ResTable_sparseTypeEntry*>(
|
||||
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
|
||||
const uint32_t offset = dtohl(entry_offsets[entry_idx]);
|
||||
if (offset == ResTable_type::NO_ENTRY) {
|
||||
continue;
|
||||
}
|
||||
const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
|
||||
const ResTable_sparseTypeEntry* result =
|
||||
std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
|
||||
[](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
|
||||
return dtohs(entry.idx) < entry_idx;
|
||||
});
|
||||
|
||||
// There is an entry for this resource, record it.
|
||||
best_offset = offset;
|
||||
}
|
||||
|
||||
best_config = &type->configuration;
|
||||
best_type = type_chunk;
|
||||
if (result == sparse_indices_end || dtohs(result->idx) != entry_index) {
|
||||
// No entry found.
|
||||
return ResTable_type::NO_ENTRY;
|
||||
}
|
||||
|
||||
// Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
|
||||
// the real offset divided by 4.
|
||||
return uint32_t{dtohs(result->offset)} * 4u;
|
||||
}
|
||||
|
||||
if (best_type == nullptr) {
|
||||
return false;
|
||||
// This type is encoded as a dense array.
|
||||
if (entry_index >= entry_count) {
|
||||
// This entry cannot be here.
|
||||
return ResTable_type::NO_ENTRY;
|
||||
}
|
||||
|
||||
if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
|
||||
reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
|
||||
|
||||
const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
|
||||
out_entry->type_flags = dtohl(flags[entry_idx]);
|
||||
out_entry->entry = best_entry;
|
||||
out_entry->config = best_config;
|
||||
out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
|
||||
out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
|
||||
return true;
|
||||
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
|
||||
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
|
||||
return dtohl(entry_offsets[entry_index]);
|
||||
}
|
||||
|
||||
bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
|
||||
FindEntryResult* out_entry) const {
|
||||
// If the type IDs are offset in this package, we need to take that into account when searching
|
||||
// for a type.
|
||||
const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
|
||||
if (UNLIKELY(ptr == nullptr)) {
|
||||
return false;
|
||||
const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
|
||||
uint32_t offset) {
|
||||
if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If there is an IDMAP supplied with this package, translate the entry ID.
|
||||
if (ptr->idmap_entries != nullptr) {
|
||||
if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
|
||||
// There is no mapping, so the resource is not meant to be in this overlay package.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return FindEntry(ptr, entry_idx, config, out_entry);
|
||||
return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
|
||||
offset + dtohl(type_chunk->entriesStart));
|
||||
}
|
||||
|
||||
void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
|
||||
@@ -346,7 +267,7 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
|
||||
const static std::u16string kMipMap = u"mipmap";
|
||||
const size_t type_count = type_specs_.size();
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
|
||||
const TypeSpecPtr& type_spec = type_specs_[i];
|
||||
if (type_spec != nullptr) {
|
||||
if (exclude_mipmap) {
|
||||
const int type_idx = type_spec->type_spec->id - 1;
|
||||
@@ -367,8 +288,11 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < type_spec->type_count; j++) {
|
||||
out_configs->insert(type_spec->types[j].configuration);
|
||||
const auto iter_end = type_spec->types + type_spec->type_count;
|
||||
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
|
||||
ResTable_config config;
|
||||
config.copyFromDtoH((*iter)->config);
|
||||
out_configs->insert(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,10 +302,12 @@ void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out
|
||||
char temp_locale[RESTABLE_MAX_LOCALE_LEN];
|
||||
const size_t type_count = type_specs_.size();
|
||||
for (size_t i = 0; i < type_count; i++) {
|
||||
const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
|
||||
const TypeSpecPtr& type_spec = type_specs_[i];
|
||||
if (type_spec != nullptr) {
|
||||
for (size_t j = 0; j < type_spec->type_count; j++) {
|
||||
const ResTable_config& configuration = type_spec->types[j].configuration;
|
||||
const auto iter_end = type_spec->types + type_spec->type_count;
|
||||
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
|
||||
ResTable_config configuration;
|
||||
configuration.copyFromDtoH((*iter)->config);
|
||||
if (configuration.locale != 0) {
|
||||
configuration.getBcp47Locale(temp_locale, canonicalize);
|
||||
std::string locale(temp_locale);
|
||||
@@ -409,17 +335,17 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name,
|
||||
return 0u;
|
||||
}
|
||||
|
||||
for (size_t ti = 0; ti < type_spec->type_count; ti++) {
|
||||
const Type* type = &type_spec->types[ti];
|
||||
size_t entry_count = dtohl(type->type->entryCount);
|
||||
const auto iter_end = type_spec->types + type_spec->type_count;
|
||||
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
|
||||
const ResTable_type* type = *iter;
|
||||
size_t entry_count = dtohl(type->entryCount);
|
||||
for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
|
||||
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
|
||||
reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
|
||||
reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize));
|
||||
const uint32_t offset = dtohl(entry_offsets[entry_idx]);
|
||||
if (offset != ResTable_type::NO_ENTRY) {
|
||||
const ResTable_entry* entry =
|
||||
reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) +
|
||||
dtohl(type->type->entriesStart) + offset);
|
||||
const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
|
||||
reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset);
|
||||
if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) {
|
||||
// The package ID will be overridden by the caller (due to runtime assignment of package
|
||||
// IDs for shared libraries).
|
||||
@@ -431,8 +357,7 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name,
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
|
||||
const uint8_t package_id = get_package_id(resid);
|
||||
const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
|
||||
for (const auto& loaded_package : packages_) {
|
||||
if (loaded_package->GetPackageId() == package_id) {
|
||||
return loaded_package.get();
|
||||
@@ -680,26 +605,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
|
||||
return std::move(loaded_package);
|
||||
}
|
||||
|
||||
bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
|
||||
FindEntryResult* out_entry) const {
|
||||
ATRACE_CALL();
|
||||
|
||||
const uint8_t package_id = get_package_id(resid);
|
||||
const uint8_t type_id = get_type_id(resid);
|
||||
const uint16_t entry_id = get_entry_id(resid);
|
||||
|
||||
if (UNLIKELY(type_id == 0)) {
|
||||
LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& loaded_package : packages_) {
|
||||
if (loaded_package->GetPackageId() == package_id) {
|
||||
return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
|
||||
bool load_as_shared_library) {
|
||||
|
||||
@@ -69,6 +69,8 @@ struct ResolvedBag {
|
||||
Entry entries[0];
|
||||
};
|
||||
|
||||
struct FindEntryResult;
|
||||
|
||||
// AssetManager2 is the main entry point for accessing assets and resources.
|
||||
// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
|
||||
class AssetManager2 {
|
||||
@@ -127,7 +129,7 @@ class AssetManager2 {
|
||||
// If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap'
|
||||
// will be excluded from the list.
|
||||
std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false,
|
||||
bool exclude_mipmap = false);
|
||||
bool exclude_mipmap = false) const;
|
||||
|
||||
// Returns all the locales for which there are resources defined. This includes resource
|
||||
// locales in all the ApkAssets set for this AssetManager.
|
||||
@@ -136,24 +138,24 @@ class AssetManager2 {
|
||||
// If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
|
||||
// and de-duped in the resulting list.
|
||||
std::set<std::string> GetResourceLocales(bool exclude_system = false,
|
||||
bool merge_equivalent_languages = false);
|
||||
bool merge_equivalent_languages = false) const;
|
||||
|
||||
// Searches the set of APKs loaded by this AssetManager and opens the first one found located
|
||||
// in the assets/ directory.
|
||||
// `mode` controls how the file is opened.
|
||||
//
|
||||
// NOTE: The loaded APKs are searched in reverse order.
|
||||
std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode);
|
||||
std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const;
|
||||
|
||||
// Opens a file within the assets/ directory of the APK specified by `cookie`.
|
||||
// `mode` controls how the file is opened.
|
||||
std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
|
||||
Asset::AccessMode mode);
|
||||
Asset::AccessMode mode) const;
|
||||
|
||||
// Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
|
||||
// of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
|
||||
// The entries are sorted by their ASCII name.
|
||||
std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
|
||||
std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const;
|
||||
|
||||
// Searches the set of APKs loaded by this AssetManager and opens the first one found.
|
||||
// `mode` controls how the file is opened.
|
||||
@@ -161,24 +163,24 @@ class AssetManager2 {
|
||||
//
|
||||
// NOTE: The loaded APKs are searched in reverse order.
|
||||
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
|
||||
ApkAssetsCookie* out_cookie = nullptr);
|
||||
ApkAssetsCookie* out_cookie = nullptr) const;
|
||||
|
||||
// Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
|
||||
// This is typically used to open a specific AndroidManifest.xml, or a binary XML file
|
||||
// referenced by a resource lookup with GetResource().
|
||||
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
|
||||
Asset::AccessMode mode);
|
||||
Asset::AccessMode mode) const;
|
||||
|
||||
// Populates the `out_name` parameter with resource name information.
|
||||
// Utf8 strings are preferred, and only if they are unavailable are
|
||||
// the Utf16 variants populated.
|
||||
// Returns false if the resource was not found or the name was missing/corrupt.
|
||||
bool GetResourceName(uint32_t resid, ResourceName* out_name);
|
||||
bool GetResourceName(uint32_t resid, ResourceName* out_name) const;
|
||||
|
||||
// Populates `out_flags` with the bitmask of configuration axis that this resource varies with.
|
||||
// See ResTable_config for the list of configuration axis.
|
||||
// Returns false if the resource was not found.
|
||||
bool GetResourceFlags(uint32_t resid, uint32_t* out_flags);
|
||||
bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const;
|
||||
|
||||
// Finds the resource ID assigned to `resource_name`.
|
||||
// `resource_name` must be of the form '[package:][type/]entry'.
|
||||
@@ -186,7 +188,7 @@ class AssetManager2 {
|
||||
// If no type is specified in `resource_name`, then `fallback_type` is used as the type.
|
||||
// Returns 0x0 if no resource by that name was found.
|
||||
uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {},
|
||||
const std::string& fallback_package = {});
|
||||
const std::string& fallback_package = {}) const;
|
||||
|
||||
// Retrieves the best matching resource with ID `resid`. The resource value is filled into
|
||||
// `out_value` and the configuration for the selected value is populated in `out_selected_config`.
|
||||
@@ -199,7 +201,7 @@ class AssetManager2 {
|
||||
// this function logs if the resource was a map/bag type before returning kInvalidCookie.
|
||||
ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override,
|
||||
Res_value* out_value, ResTable_config* out_selected_config,
|
||||
uint32_t* out_flags);
|
||||
uint32_t* out_flags) const;
|
||||
|
||||
// Resolves the resource reference in `in_out_value` if the data type is
|
||||
// Res_value::TYPE_REFERENCE.
|
||||
@@ -215,7 +217,7 @@ class AssetManager2 {
|
||||
// it was not found.
|
||||
ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
|
||||
ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
|
||||
uint32_t* out_last_reference);
|
||||
uint32_t* out_last_reference) const;
|
||||
|
||||
// Retrieves the best matching bag/map resource with ID `resid`.
|
||||
// This method will resolve all parent references for this bag and merge keys with the child.
|
||||
@@ -233,9 +235,9 @@ class AssetManager2 {
|
||||
std::unique_ptr<Theme> NewTheme();
|
||||
|
||||
template <typename Func>
|
||||
void ForEachPackage(Func func) {
|
||||
void ForEachPackage(Func func) const {
|
||||
for (const PackageGroup& package_group : package_groups_) {
|
||||
func(package_group.packages_.front()->GetPackageName(),
|
||||
func(package_group.packages_.front().loaded_package_->GetPackageName(),
|
||||
package_group.dynamic_ref_table.mAssignedPackageId);
|
||||
}
|
||||
}
|
||||
@@ -260,7 +262,7 @@ class AssetManager2 {
|
||||
// NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
|
||||
// bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
|
||||
ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
|
||||
FindEntryResult* out_entry);
|
||||
FindEntryResult* out_entry) const;
|
||||
|
||||
// Assigns package IDs to all shared library ApkAssets.
|
||||
// Should be called whenever the ApkAssets are changed.
|
||||
@@ -270,13 +272,43 @@ class AssetManager2 {
|
||||
// bitmask `diff`.
|
||||
void InvalidateCaches(uint32_t diff);
|
||||
|
||||
// Triggers the re-construction of lists of types that match the set configuration.
|
||||
// This should always be called when mutating the AssetManager's configuration or ApkAssets set.
|
||||
void RebuildFilterList();
|
||||
|
||||
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
|
||||
// have a longer lifetime.
|
||||
std::vector<const ApkAssets*> apk_assets_;
|
||||
|
||||
// A collection of configurations and their associated ResTable_type that match the current
|
||||
// AssetManager configuration.
|
||||
struct FilteredConfigGroup {
|
||||
std::vector<ResTable_config> configurations;
|
||||
std::vector<const ResTable_type*> types;
|
||||
};
|
||||
|
||||
// Represents an single package.
|
||||
struct ConfiguredPackage {
|
||||
// A pointer to the immutable, loaded package info.
|
||||
const LoadedPackage* loaded_package_;
|
||||
|
||||
// A mutable AssetManager-specific list of configurations that match the AssetManager's
|
||||
// current configuration. This is used as an optimization to avoid checking every single
|
||||
// candidate configuration when looking up resources.
|
||||
ByteBucketArray<FilteredConfigGroup> filtered_configs_;
|
||||
};
|
||||
|
||||
// Represents a logical package, which can be made up of many individual packages. Each package
|
||||
// in a PackageGroup shares the same package name and package ID.
|
||||
struct PackageGroup {
|
||||
std::vector<const LoadedPackage*> packages_;
|
||||
// The set of packages that make-up this group.
|
||||
std::vector<ConfiguredPackage> packages_;
|
||||
|
||||
// The cookies associated with each package in the group. They share the same order as
|
||||
// packages_.
|
||||
std::vector<ApkAssetsCookie> cookies_;
|
||||
|
||||
// A library reference table that contains build-package ID to runtime-package ID mappings.
|
||||
DynamicRefTable dynamic_ref_table;
|
||||
};
|
||||
|
||||
@@ -350,7 +382,7 @@ class Theme {
|
||||
ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
|
||||
ResTable_config* in_out_selected_config = nullptr,
|
||||
uint32_t* in_out_type_spec_flags = nullptr,
|
||||
uint32_t* out_last_ref = nullptr);
|
||||
uint32_t* out_last_ref = nullptr) const;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Theme);
|
||||
|
||||
@@ -41,33 +41,40 @@ class DynamicPackageEntry {
|
||||
int package_id = 0;
|
||||
};
|
||||
|
||||
struct FindEntryResult {
|
||||
// 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;
|
||||
// TypeSpec is going to be immediately proceeded by
|
||||
// an array of Type structs, all in the same block of memory.
|
||||
struct TypeSpec {
|
||||
// Pointer to the mmapped data where flags are kept.
|
||||
// Flags denote whether the resource entry is public
|
||||
// and under which configurations it varies.
|
||||
const ResTable_typeSpec* type_spec;
|
||||
|
||||
// The configuration for which the resulting entry was defined. This points to a structure that
|
||||
// is already swapped to host endianness.
|
||||
const ResTable_config* config;
|
||||
// Pointer to the mmapped data where the IDMAP mappings for this type
|
||||
// exist. May be nullptr if no IDMAP exists.
|
||||
const IdmapEntry_header* idmap_entries;
|
||||
|
||||
// The bitmask of configuration axis with which the resource value varies.
|
||||
uint32_t type_flags;
|
||||
// The number of types that follow this struct.
|
||||
// There is a type for each configuration that entries are defined for.
|
||||
size_t type_count;
|
||||
|
||||
// The dynamic package ID map for the package from which this resource came from.
|
||||
const DynamicRefTable* dynamic_ref_table;
|
||||
// Trick to easily access a variable number of Type structs
|
||||
// proceeding this struct, and to ensure their alignment.
|
||||
const ResTable_type* types[0];
|
||||
|
||||
// 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;
|
||||
inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const {
|
||||
if (entry_index >= dtohl(type_spec->entryCount)) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
// 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;
|
||||
const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1);
|
||||
return flags[entry_index];
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeSpec;
|
||||
class LoadedArsc;
|
||||
// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of
|
||||
// ResTable_type pointers.
|
||||
// TypeSpecPtr is a managed pointer that knows how to delete itself.
|
||||
using TypeSpecPtr = util::unique_cptr<TypeSpec>;
|
||||
|
||||
class LoadedPackage {
|
||||
public:
|
||||
@@ -77,9 +84,6 @@ class LoadedPackage {
|
||||
|
||||
~LoadedPackage();
|
||||
|
||||
bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
|
||||
FindEntryResult* out_entry) const;
|
||||
|
||||
// Finds the entry with the specified type name and entry name. The names are in UTF-16 because
|
||||
// the underlying ResStringPool API expects this. For now this is acceptable, but since
|
||||
// the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
|
||||
@@ -87,6 +91,12 @@ class LoadedPackage {
|
||||
// for patching the correct package ID to the resource ID.
|
||||
uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
|
||||
|
||||
static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index);
|
||||
|
||||
static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index);
|
||||
|
||||
static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset);
|
||||
|
||||
// Returns the string pool where type names are stored.
|
||||
inline const ResStringPool* GetTypeStringPool() const {
|
||||
return &type_string_pool_;
|
||||
@@ -136,14 +146,32 @@ class LoadedPackage {
|
||||
// before being inserted into the set. This may cause some equivalent locales to de-dupe.
|
||||
void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
|
||||
|
||||
// type_idx is TT - 1 from 0xPPTTEEEE.
|
||||
inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
|
||||
// If the type IDs are offset in this package, we need to take that into account when searching
|
||||
// for a type.
|
||||
return type_specs_[type_index - type_id_offset_].get();
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachTypeSpec(Func f) const {
|
||||
for (size_t i = 0; i < type_specs_.size(); i++) {
|
||||
const TypeSpecPtr& ptr = type_specs_[i];
|
||||
if (ptr != nullptr) {
|
||||
uint8_t type_id = ptr->type_spec->id;
|
||||
if (ptr->idmap_entries != nullptr) {
|
||||
type_id = ptr->idmap_entries->target_type_id;
|
||||
}
|
||||
f(ptr.get(), type_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
|
||||
|
||||
LoadedPackage();
|
||||
|
||||
bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
|
||||
const ResTable_config& config, FindEntryResult* out_entry) const;
|
||||
|
||||
ResStringPool type_string_pool_;
|
||||
ResStringPool key_string_pool_;
|
||||
std::string package_name_;
|
||||
@@ -153,7 +181,7 @@ class LoadedPackage {
|
||||
bool system_ = false;
|
||||
bool overlay_ = false;
|
||||
|
||||
ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
|
||||
ByteBucketArray<TypeSpecPtr> type_specs_;
|
||||
std::vector<DynamicPackageEntry> dynamic_package_map_;
|
||||
};
|
||||
|
||||
@@ -181,25 +209,20 @@ class LoadedArsc {
|
||||
return &global_string_pool_;
|
||||
}
|
||||
|
||||
// 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, FindEntryResult* out_entry) const;
|
||||
|
||||
// Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
|
||||
const LoadedPackage* GetPackageForId(uint32_t resid) const;
|
||||
|
||||
// Returns true if this is a system provided resource.
|
||||
inline bool IsSystem() const {
|
||||
return system_;
|
||||
}
|
||||
// Gets a pointer to the package with the specified package ID, or nullptr if no such package
|
||||
// exists.
|
||||
const LoadedPackage* GetPackageById(uint8_t package_id) const;
|
||||
|
||||
// Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
|
||||
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
|
||||
return packages_;
|
||||
}
|
||||
|
||||
// Returns true if this is a system provided resource.
|
||||
inline bool IsSystem() const {
|
||||
return system_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
|
||||
|
||||
|
||||
@@ -26,58 +26,56 @@
|
||||
|
||||
using ::android::base::unique_fd;
|
||||
using ::com::android::basic::R;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Ge;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::SizeIs;
|
||||
using ::testing::StrEq;
|
||||
|
||||
namespace android {
|
||||
|
||||
TEST(ApkAssetsTest, LoadApk) {
|
||||
std::unique_ptr<const ApkAssets> loaded_apk =
|
||||
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
|
||||
const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
|
||||
ASSERT_NE(nullptr, loaded_package);
|
||||
|
||||
std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
|
||||
ASSERT_NE(nullptr, asset);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
|
||||
ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
|
||||
}
|
||||
|
||||
TEST(ApkAssetsTest, LoadApkFromFd) {
|
||||
const std::string path = GetTestDataPath() + "/basic/basic.apk";
|
||||
unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
|
||||
ASSERT_GE(fd.get(), 0);
|
||||
ASSERT_THAT(fd.get(), Ge(0));
|
||||
|
||||
std::unique_ptr<const ApkAssets> loaded_apk =
|
||||
ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
|
||||
const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
|
||||
ASSERT_NE(nullptr, loaded_package);
|
||||
|
||||
std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
|
||||
ASSERT_NE(nullptr, asset);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
|
||||
ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
|
||||
}
|
||||
|
||||
TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
|
||||
std::unique_ptr<const ApkAssets> loaded_apk =
|
||||
ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
|
||||
EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
|
||||
|
||||
loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
loaded_arsc = loaded_apk->GetLoadedArsc();
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
|
||||
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
|
||||
}
|
||||
|
||||
@@ -86,19 +84,22 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) {
|
||||
ResTable target_table;
|
||||
const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
|
||||
ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
|
||||
ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
|
||||
ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
|
||||
Eq(NO_ERROR));
|
||||
|
||||
ResTable overlay_table;
|
||||
const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
|
||||
ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
|
||||
ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
|
||||
ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
|
||||
Eq(NO_ERROR));
|
||||
|
||||
util::unique_cptr<void> idmap_data;
|
||||
void* temp_data;
|
||||
size_t idmap_len;
|
||||
|
||||
ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
|
||||
overlay_path.c_str(), &temp_data, &idmap_len));
|
||||
ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
|
||||
overlay_path.c_str(), &temp_data, &idmap_len),
|
||||
Eq(NO_ERROR));
|
||||
idmap_data.reset(temp_data);
|
||||
|
||||
TemporaryFile tf;
|
||||
@@ -108,37 +109,30 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) {
|
||||
// Open something so that the destructor of TemporaryFile closes a valid fd.
|
||||
tf.fd = open("/dev/null", O_WRONLY);
|
||||
|
||||
std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
|
||||
ASSERT_NE(nullptr, loaded_overlay_apk);
|
||||
ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull());
|
||||
}
|
||||
|
||||
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
|
||||
std::unique_ptr<const ApkAssets> loaded_apk =
|
||||
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
{
|
||||
std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
|
||||
ASSERT_NE(nullptr, assets);
|
||||
}
|
||||
{ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
|
||||
|
||||
{
|
||||
std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
|
||||
ASSERT_NE(nullptr, assets);
|
||||
}
|
||||
{ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
|
||||
}
|
||||
|
||||
TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
|
||||
std::unique_ptr<const ApkAssets> loaded_apk =
|
||||
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
|
||||
ASSERT_NE(nullptr, loaded_apk);
|
||||
ASSERT_THAT(loaded_apk, NotNull());
|
||||
|
||||
auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
|
||||
ASSERT_NE(nullptr, asset);
|
||||
ASSERT_THAT(asset, NotNull());
|
||||
|
||||
off64_t start, length;
|
||||
unique_fd fd(asset->openFileDescriptor(&start, &length));
|
||||
EXPECT_GE(fd.get(), 0);
|
||||
ASSERT_THAT(fd.get(), Ge(0));
|
||||
|
||||
lseek64(fd.get(), start, SEEK_SET);
|
||||
|
||||
@@ -146,7 +140,7 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
|
||||
buffer.resize(length);
|
||||
ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
|
||||
|
||||
EXPECT_EQ("This should be uncompressed.\n\n", buffer);
|
||||
EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "androidfw/LoadedArsc.h"
|
||||
|
||||
#include "androidfw/ResourceUtils.h"
|
||||
|
||||
#include "TestHelpers.h"
|
||||
#include "data/basic/R.h"
|
||||
#include "data/libclient/R.h"
|
||||
@@ -27,6 +29,13 @@ namespace basic = com::android::basic;
|
||||
namespace libclient = com::android::libclient;
|
||||
namespace sparse = com::android::sparse;
|
||||
|
||||
using ::testing::Eq;
|
||||
using ::testing::Ge;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::SizeIs;
|
||||
using ::testing::StrEq;
|
||||
|
||||
namespace android {
|
||||
|
||||
TEST(LoadedArscTest, LoadSinglePackageArsc) {
|
||||
@@ -35,39 +44,24 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) {
|
||||
&contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
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());
|
||||
const LoadedPackage* package =
|
||||
loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one));
|
||||
ASSERT_THAT(package, NotNull());
|
||||
EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app"));
|
||||
EXPECT_THAT(package->GetPackageId(), Eq(0x7f));
|
||||
|
||||
ResTable_config config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.sdkVersion = 24;
|
||||
const uint8_t type_index = get_type_id(app::R::string::string_one) - 1;
|
||||
const uint16_t entry_index = get_entry_id(app::R::string::string_one);
|
||||
|
||||
FindEntryResult entry;
|
||||
const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
|
||||
ASSERT_THAT(type_spec, NotNull());
|
||||
ASSERT_THAT(type_spec->type_count, Ge(1u));
|
||||
|
||||
ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
|
||||
ASSERT_NE(nullptr, entry.entry);
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, FindDefaultEntry) {
|
||||
std::string contents;
|
||||
ASSERT_TRUE(
|
||||
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
|
||||
ResTable_config desired_config;
|
||||
memset(&desired_config, 0, sizeof(desired_config));
|
||||
desired_config.language[0] = 'd';
|
||||
desired_config.language[1] = 'e';
|
||||
|
||||
FindEntryResult entry;
|
||||
ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
|
||||
ASSERT_NE(nullptr, entry.entry);
|
||||
const ResTable_type* type = type_spec->types[0];
|
||||
ASSERT_THAT(type, NotNull());
|
||||
ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, LoadSparseEntryApp) {
|
||||
@@ -76,15 +70,22 @@ TEST(LoadedArscTest, LoadSparseEntryApp) {
|
||||
&contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
ResTable_config config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.sdkVersion = 26;
|
||||
const LoadedPackage* package =
|
||||
loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
|
||||
ASSERT_THAT(package, NotNull());
|
||||
|
||||
FindEntryResult entry;
|
||||
ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry));
|
||||
ASSERT_NE(nullptr, entry.entry);
|
||||
const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
|
||||
const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
|
||||
|
||||
const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
|
||||
ASSERT_THAT(type_spec, NotNull());
|
||||
ASSERT_THAT(type_spec->type_count, Ge(1u));
|
||||
|
||||
const ResTable_type* type = type_spec->types[0];
|
||||
ASSERT_THAT(type, NotNull());
|
||||
ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, LoadSharedLibrary) {
|
||||
@@ -93,14 +94,13 @@ TEST(LoadedArscTest, LoadSharedLibrary) {
|
||||
&contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
const auto& packages = loaded_arsc->GetPackages();
|
||||
ASSERT_EQ(1u, packages.size());
|
||||
|
||||
ASSERT_THAT(packages, SizeIs(1u));
|
||||
EXPECT_TRUE(packages[0]->IsDynamic());
|
||||
EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName());
|
||||
EXPECT_EQ(0, packages[0]->GetPackageId());
|
||||
EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one"));
|
||||
EXPECT_THAT(packages[0]->GetPackageId(), Eq(0));
|
||||
|
||||
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
|
||||
|
||||
@@ -114,25 +114,23 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) {
|
||||
"resources.arsc", &contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
const auto& packages = loaded_arsc->GetPackages();
|
||||
ASSERT_EQ(1u, packages.size());
|
||||
|
||||
ASSERT_THAT(packages, SizeIs(1u));
|
||||
EXPECT_FALSE(packages[0]->IsDynamic());
|
||||
EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName());
|
||||
EXPECT_EQ(0x7f, packages[0]->GetPackageId());
|
||||
EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient"));
|
||||
EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
|
||||
|
||||
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
|
||||
|
||||
// The library has two dependencies.
|
||||
ASSERT_EQ(2u, dynamic_pkg_map.size());
|
||||
ASSERT_THAT(dynamic_pkg_map, SizeIs(2u));
|
||||
EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one"));
|
||||
EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02));
|
||||
|
||||
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);
|
||||
EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two"));
|
||||
EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03));
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
|
||||
@@ -143,13 +141,12 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc =
|
||||
LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
|
||||
true /*load_as_shared_library*/);
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
const auto& packages = loaded_arsc->GetPackages();
|
||||
ASSERT_EQ(1u, packages.size());
|
||||
|
||||
ASSERT_THAT(packages, SizeIs(1u));
|
||||
EXPECT_TRUE(packages[0]->IsDynamic());
|
||||
EXPECT_EQ(0x7f, packages[0]->GetPackageId());
|
||||
EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, LoadFeatureSplit) {
|
||||
@@ -157,21 +154,27 @@ TEST(LoadedArscTest, LoadFeatureSplit) {
|
||||
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
|
||||
&contents));
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
ResTable_config desired_config;
|
||||
memset(&desired_config, 0, sizeof(desired_config));
|
||||
const LoadedPackage* package =
|
||||
loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3));
|
||||
ASSERT_THAT(package, NotNull());
|
||||
|
||||
FindEntryResult entry;
|
||||
ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
|
||||
uint8_t type_index = get_type_id(basic::R::string::test3) - 1;
|
||||
uint8_t entry_index = get_entry_id(basic::R::string::test3);
|
||||
|
||||
const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
|
||||
ASSERT_THAT(type_spec, NotNull());
|
||||
ASSERT_THAT(type_spec->type_count, Ge(1u));
|
||||
ASSERT_THAT(type_spec->types[0], NotNull());
|
||||
|
||||
size_t len;
|
||||
const char16_t* type_name16 = entry.type_string_ref.string16(&len);
|
||||
ASSERT_NE(nullptr, type_name16);
|
||||
ASSERT_NE(0u, len);
|
||||
const char16_t* type_name16 =
|
||||
package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len);
|
||||
ASSERT_THAT(type_name16, NotNull());
|
||||
EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string"));
|
||||
|
||||
std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
|
||||
EXPECT_EQ(std::string("string"), type_name);
|
||||
ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull());
|
||||
}
|
||||
|
||||
class MockLoadedIdmap : public LoadedIdmap {
|
||||
@@ -199,23 +202,33 @@ class MockLoadedIdmap : public LoadedIdmap {
|
||||
};
|
||||
|
||||
TEST(LoadedArscTest, LoadOverlay) {
|
||||
std::string contents, overlay_contents;
|
||||
ASSERT_TRUE(
|
||||
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
|
||||
std::string contents;
|
||||
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
|
||||
&overlay_contents));
|
||||
&contents));
|
||||
|
||||
MockLoadedIdmap loaded_idmap;
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc =
|
||||
LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
LoadedArsc::Load(StringPiece(contents), &loaded_idmap);
|
||||
ASSERT_THAT(loaded_arsc, NotNull());
|
||||
|
||||
ResTable_config desired_config;
|
||||
memset(&desired_config, 0, sizeof(desired_config));
|
||||
const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u);
|
||||
ASSERT_THAT(package, NotNull());
|
||||
|
||||
FindEntryResult entry;
|
||||
ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
|
||||
const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1);
|
||||
ASSERT_THAT(type_spec, NotNull());
|
||||
ASSERT_THAT(type_spec->type_count, Ge(1u));
|
||||
ASSERT_THAT(type_spec->types[0], NotNull());
|
||||
|
||||
// The entry being overlaid doesn't exist at the original entry index.
|
||||
ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull());
|
||||
|
||||
// Since this is an overlay, the actual entry ID must be mapped.
|
||||
ASSERT_THAT(type_spec->idmap_entries, NotNull());
|
||||
uint16_t target_entry_id = 0u;
|
||||
ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id));
|
||||
ASSERT_THAT(target_entry_id, Eq(0x0u));
|
||||
ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull());
|
||||
}
|
||||
|
||||
// structs with size fields (like Res_value, ResTable_entry) should be
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "CommonHelpers.h"
|
||||
|
||||
Reference in New Issue
Block a user