Merge "New implementation of AssetManager/ResTable"
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index d501d25..fb89835 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -24,10 +24,14 @@
"-Wunreachable-code",
],
srcs: [
+ "ApkAssets.cpp",
"Asset.cpp",
"AssetDir.cpp",
"AssetManager.cpp",
+ "AssetManager2.cpp",
"AttributeResolution.cpp",
+ "ChunkIterator.cpp",
+ "LoadedArsc.cpp",
"LocaleData.cpp",
"misc.cpp",
"ObbFile.cpp",
@@ -65,7 +69,16 @@
shared: {
enabled: false,
},
- shared_libs: ["libz-host"],
+ static_libs: [
+ "libziparchive",
+ "libbase",
+ "liblog",
+ "libcutils",
+ "libutils",
+ ],
+ shared_libs: [
+ "libz-host",
+ ],
},
windows: {
enabled: true,
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
new file mode 100644
index 0000000..55f4c3c
--- /dev/null
+++ b/libs/androidfw/ApkAssets.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/ApkAssets.h"
+
+#include "android-base/logging.h"
+#include "utils/Trace.h"
+#include "ziparchive/zip_archive.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/Util.h"
+
+namespace android {
+
+std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path) {
+ ATRACE_NAME("ApkAssets::Load");
+ ::ZipArchiveHandle unmanaged_handle;
+ int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
+ if (result != 0) {
+ LOG(ERROR) << ::ErrorCodeString(result);
+ return {};
+ }
+
+ // Wrap the handle in a unique_ptr so it gets automatically closed.
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets());
+ loaded_apk->zip_handle_.reset(unmanaged_handle);
+
+ ::ZipString entry_name("resources.arsc");
+ ::ZipEntry entry;
+ result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
+ if (result != 0) {
+ LOG(ERROR) << ::ErrorCodeString(result);
+ return {};
+ }
+
+ if (entry.method == kCompressDeflated) {
+ LOG(WARNING) << "resources.arsc is compressed.";
+ }
+
+ loaded_apk->resources_asset_ =
+ loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
+ if (loaded_apk->resources_asset_ == nullptr) {
+ return {};
+ }
+
+ loaded_apk->path_ = path;
+ loaded_apk->loaded_arsc_ =
+ LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
+ loaded_apk->resources_asset_->getLength());
+ if (loaded_apk->loaded_arsc_ == nullptr) {
+ return {};
+ }
+ return loaded_apk;
+}
+
+std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode /*mode*/) const {
+ ATRACE_NAME("ApkAssets::Open");
+ CHECK(zip_handle_ != nullptr);
+
+ ::ZipString name(path.c_str());
+ ::ZipEntry entry;
+ int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
+ if (result != 0) {
+ LOG(ERROR) << "No entry '" << path << "' found in APK.";
+ return {};
+ }
+
+ if (entry.method == kCompressDeflated) {
+ auto compressed_asset = util::make_unique<_CompressedAsset>();
+ if (compressed_asset->openChunk(::GetFileDescriptor(zip_handle_.get()), entry.offset,
+ entry.method, entry.uncompressed_length,
+ entry.compressed_length) != NO_ERROR) {
+ LOG(ERROR) << "Failed to decompress '" << path << "'.";
+ return {};
+ }
+ return std::move(compressed_asset);
+ } else {
+ auto uncompressed_asset = util::make_unique<_FileAsset>();
+ if (uncompressed_asset->openChunk(path.c_str(), ::GetFileDescriptor(zip_handle_.get()),
+ entry.offset, entry.uncompressed_length) != NO_ERROR) {
+ LOG(ERROR) << "Failed to mmap '" << path << "'.";
+ return {};
+ }
+ return std::move(uncompressed_asset);
+ }
+ return {};
+}
+
+} // namespace android
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
new file mode 100644
index 0000000..8d65925
--- /dev/null
+++ b/libs/androidfw/AssetManager2.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/AssetManager2.h"
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "utils/ByteOrder.h"
+#include "utils/Trace.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+namespace android {
+
+AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
+
+bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
+ bool invalidate_caches) {
+ apk_assets_ = apk_assets;
+ if (invalidate_caches) {
+ InvalidateCaches(static_cast<uint32_t>(-1));
+ }
+ return true;
+}
+
+const std::vector<const ApkAssets*> AssetManager2::GetApkAssets() const { return apk_assets_; }
+
+const ResStringPool* AssetManager2::GetStringPoolForCookie(ApkAssetsCookie cookie) const {
+ if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
+ return nullptr;
+ }
+ return apk_assets_[cookie]->GetLoadedArsc()->GetStringPool();
+}
+
+void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
+ const int diff = configuration_.diff(configuration);
+ configuration_ = configuration;
+
+ if (diff) {
+ InvalidateCaches(static_cast<uint32_t>(diff));
+ }
+}
+
+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);
+}
+
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
+ Asset::AccessMode mode) {
+ const std::string new_path = "assets/" + filename;
+ return OpenNonAsset(new_path, cookie, mode);
+}
+
+// Search in reverse because that's how we used to do it and we need to preserve behaviour.
+// This is unfortunate, because ClassLoaders delegate to the parent first, so the order
+// is inconsistent for split APKs.
+std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
+ Asset::AccessMode mode,
+ ApkAssetsCookie* out_cookie) {
+ ATRACE_CALL();
+ for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
+ std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
+ if (asset) {
+ if (out_cookie != nullptr) {
+ *out_cookie = i;
+ }
+ return asset;
+ }
+ }
+
+ if (out_cookie != nullptr) {
+ *out_cookie = kInvalidCookie;
+ }
+ return {};
+}
+
+std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
+ ApkAssetsCookie cookie, Asset::AccessMode mode) {
+ ATRACE_CALL();
+ if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
+ return {};
+ }
+ return apk_assets_[cookie]->Open(filename, mode);
+}
+
+ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
+ bool stop_at_first_match, LoadedArsc::Entry* out_entry,
+ ResTable_config* out_selected_config,
+ uint32_t* out_flags) {
+ ATRACE_CALL();
+
+ // 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_;
+ if (density_override != 0 && density_override != configuration_.density) {
+ density_override_config = configuration_;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
+ }
+
+ LoadedArsc::Entry best_entry;
+ ResTable_config best_config;
+ int32_t best_index = -1;
+ uint32_t cumulated_flags = 0;
+
+ 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;
+ ResTable_config current_config;
+ uint32_t flags = 0;
+ if (!loaded_arsc->FindEntry(resid, *desired_config, ¤t_entry, ¤t_config, &flags)) {
+ continue;
+ }
+
+ cumulated_flags |= flags;
+
+ if (best_index == -1 || current_config.isBetterThan(best_config, desired_config)) {
+ best_entry = current_entry;
+ best_config = current_config;
+ best_index = static_cast<int32_t>(i);
+ if (stop_at_first_match) {
+ break;
+ }
+ }
+ }
+
+ if (best_index == -1) {
+ return kInvalidCookie;
+ }
+
+ *out_entry = best_entry;
+ *out_selected_config = best_config;
+ *out_flags = cumulated_flags;
+ return best_index;
+}
+
+bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
+ ATRACE_CALL();
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ true /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return false;
+ }
+
+ const std::string* package_name =
+ apk_assets_[cookie]->GetLoadedArsc()->GetPackageNameForId(resid);
+ if (package_name == nullptr) {
+ return false;
+ }
+
+ out_name->package = package_name->data();
+ out_name->package_len = package_name->size();
+
+ out_name->type = entry.type_string_ref.string8(&out_name->type_len);
+ out_name->type16 = nullptr;
+ if (out_name->type == nullptr) {
+ out_name->type16 = entry.type_string_ref.string16(&out_name->type_len);
+ if (out_name->type16 == nullptr) {
+ return false;
+ }
+ }
+
+ out_name->entry = entry.entry_string_ref.string8(&out_name->entry_len);
+ out_name->entry16 = nullptr;
+ if (out_name->entry == nullptr) {
+ out_name->entry16 = entry.entry_string_ref.string16(&out_name->entry_len);
+ if (out_name->entry16 == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ false /* stop_at_first_match */, &entry, &config, out_flags);
+ return cookie != kInvalidCookie;
+}
+
+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) {
+ ATRACE_CALL();
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie =
+ FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return kInvalidCookie;
+ }
+
+ if (dtohl(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
+ if (!may_be_bag) {
+ LOG(ERROR) << base::StringPrintf("Resource %08x is a complex map type.", resid);
+ }
+ return kInvalidCookie;
+ }
+
+ 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);
+ *out_selected_config = config;
+ *out_flags = flags;
+ return cookie;
+}
+
+const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
+ ATRACE_CALL();
+
+ auto cached_iter = cached_bags_.find(resid);
+ if (cached_iter != cached_bags_.end()) {
+ return cached_iter->second.get();
+ }
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ false /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return nullptr;
+ }
+
+ // Check that the size of the entry header is at least as big as
+ // the desired ResTable_map_entry. Also verify that the entry
+ // was intended to be a map.
+ if (dtohs(entry.entry->size) < sizeof(ResTable_map_entry) ||
+ (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) == 0) {
+ // Not a bag, nothing to do.
+ return nullptr;
+ }
+
+ const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry.entry);
+ const ResTable_map* map_entry =
+ 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) {
+ // 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;
+ util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
+ malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
+ ResolvedBag::Entry* new_entry = new_bag->entries;
+ for (; map_entry != map_entry_end; ++map_entry) {
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = dtohl(map_entry->name.ident);
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++new_entry;
+ }
+ new_bag->type_spec_flags = flags;
+ new_bag->entry_count = static_cast<uint32_t>(entry_count);
+ ResolvedBag* result = new_bag.get();
+ cached_bags_[resid] = std::move(new_bag);
+ return result;
+ }
+
+ // Get the parent and do a merge of the keys.
+ const ResolvedBag* parent_bag = GetBag(parent);
+ if (parent_bag == nullptr) {
+ // Failed to get the parent that should exist.
+ return nullptr;
+ }
+
+ // Combine flags from the parent and our own bag.
+ flags |= parent_bag->type_spec_flags;
+
+ // Create the max possible entries we can make. Once we construct the bag,
+ // we will realloc to fit to size.
+ const size_t max_count = parent_bag->entry_count + dtohl(map->count);
+ ResolvedBag* new_bag = reinterpret_cast<ResolvedBag*>(
+ malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry))));
+ ResolvedBag::Entry* new_entry = new_bag->entries;
+
+ const ResolvedBag::Entry* parent_entry = parent_bag->entries;
+ const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count;
+
+ // 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);
+ if (child_key <= parent_entry->key) {
+ // Use the child key if it comes before the parent
+ // or is equal to the parent (overrides).
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = child_key;
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++map_entry;
+ } else {
+ // Take the parent entry as-is.
+ memcpy(new_entry, parent_entry, sizeof(*new_entry));
+ }
+
+ if (child_key >= parent_entry->key) {
+ // Move to the next parent entry if we used it or it was overridden.
+ ++parent_entry;
+ }
+ // Increment to the next entry to fill.
+ ++new_entry;
+ }
+
+ // Finish the child entries if they exist.
+ while (map_entry != map_entry_end) {
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = dtohl(map_entry->name.ident);
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++map_entry;
+ ++new_entry;
+ }
+
+ // Finish the parent entries if they exist.
+ if (parent_entry != parent_entry_end) {
+ // Take the rest of the parent entries as-is.
+ const size_t num_entries_to_copy = parent_entry_end - parent_entry;
+ memcpy(new_entry, parent_entry, num_entries_to_copy * sizeof(*new_entry));
+ new_entry += num_entries_to_copy;
+ }
+
+ // Resize the resulting array to fit.
+ const size_t actual_count = new_entry - new_bag->entries;
+ if (actual_count != max_count) {
+ new_bag = reinterpret_cast<ResolvedBag*>(
+ realloc(new_bag, sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry))));
+ }
+
+ util::unique_cptr<ResolvedBag> final_bag{new_bag};
+ final_bag->type_spec_flags = flags;
+ final_bag->entry_count = static_cast<uint32_t>(actual_count);
+ ResolvedBag* result = final_bag.get();
+ cached_bags_[resid] = std::move(final_bag);
+ return result;
+}
+
+void AssetManager2::InvalidateCaches(uint32_t diff) {
+ if (diff == 0xffffffffu) {
+ // Everything must go.
+ cached_bags_.clear();
+ return;
+ }
+
+ // Be more conservative with what gets purged. Only if the bag has other possible
+ // variations with respect to what changed (diff) should we remove it.
+ for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
+ if (diff & iter->second->type_spec_flags) {
+ iter = cached_bags_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); }
+
+bool Theme::ApplyStyle(uint32_t resid, bool force) {
+ ATRACE_CALL();
+
+ const ResolvedBag* bag = asset_manager_->GetBag(resid);
+ if (bag == nullptr) {
+ return false;
+ }
+
+ // Merge the flags from this style.
+ type_spec_flags_ |= bag->type_spec_flags;
+
+ // On the first iteration, verify the attribute IDs and
+ // update the entry count in each type.
+ const auto bag_iter_end = end(bag);
+ for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+ const uint32_t attr_resid = bag_iter->key;
+
+ // If the resource ID passed in is not a style, the key can be
+ // some other identifier that is not a resource ID.
+ if (!util::is_valid_resid(attr_resid)) {
+ return false;
+ }
+
+ const uint32_t package_idx = util::get_package_id(attr_resid);
+
+ // The type ID is 1-based, so subtract 1 to get an index.
+ const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(attr_resid);
+
+ std::unique_ptr<Package>& package = packages_[package_idx];
+ if (package == nullptr) {
+ package.reset(new Package());
+ }
+
+ util::unique_cptr<Type>& type = package->types[type_idx];
+ if (type == nullptr) {
+ // Set the initial capacity to take up a total amount of 1024 bytes.
+ constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry);
+ const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity);
+ type.reset(
+ reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1)));
+ type->entry_capacity = initial_capacity;
+ }
+
+ // Set the entry_count to include this entry. We will populate
+ // and resize the array as necessary in the next pass.
+ if (entry_idx + 1 > type->entry_count) {
+ // Increase the entry count to include this.
+ type->entry_count = entry_idx + 1;
+ }
+ }
+
+ // On the second pass, we will realloc to fit the entry counts
+ // and populate the structures.
+ for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+ const uint32_t attr_resid = bag_iter->key;
+ const uint32_t package_idx = util::get_package_id(attr_resid);
+ const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(attr_resid);
+ Package* package = packages_[package_idx].get();
+ util::unique_cptr<Type>& type = package->types[type_idx];
+ if (type->entry_count != type->entry_capacity) {
+ // Resize to fit the actual entries that will be included.
+ Type* type_ptr = type.release();
+ type.reset(reinterpret_cast<Type*>(
+ realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry)))));
+ if (type->entry_capacity < type->entry_count) {
+ // Clear the newly allocated memory (which does not get zero initialized).
+ // We need to do this because we |= type_spec_flags.
+ memset(type->entries + type->entry_capacity, 0,
+ sizeof(Entry) * (type->entry_count - type->entry_capacity));
+ }
+ type->entry_capacity = type->entry_count;
+ }
+ Entry& entry = type->entries[entry_idx];
+ if (force || entry.value.dataType == Res_value::TYPE_NULL) {
+ entry.cookie = bag_iter->cookie;
+ entry.type_spec_flags |= bag->type_spec_flags;
+ entry.value = bag_iter->value;
+ }
+ }
+ return true;
+}
+
+ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
+ uint32_t* out_flags) const {
+ constexpr const int kMaxIterations = 20;
+
+ uint32_t type_spec_flags = 0u;
+
+ for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
+ if (!util::is_valid_resid(resid)) {
+ return kInvalidCookie;
+ }
+
+ const uint32_t package_idx = util::get_package_id(resid);
+
+ // Type ID is 1-based, subtract 1 to get the index.
+ const uint32_t type_idx = util::get_type_id(resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(resid);
+
+ const Package* package = packages_[package_idx].get();
+ if (package == nullptr) {
+ return kInvalidCookie;
+ }
+
+ const Type* type = package->types[type_idx].get();
+ if (type == nullptr) {
+ return kInvalidCookie;
+ }
+
+ if (entry_idx >= type->entry_count) {
+ return kInvalidCookie;
+ }
+
+ const Entry& entry = type->entries[entry_idx];
+ type_spec_flags |= entry.type_spec_flags;
+
+ switch (entry.value.dataType) {
+ case Res_value::TYPE_ATTRIBUTE:
+ resid = entry.value.data;
+ break;
+
+ case Res_value::TYPE_NULL:
+ return kInvalidCookie;
+
+ default:
+ *out_value = entry.value;
+ if (out_flags != nullptr) {
+ *out_flags = type_spec_flags;
+ }
+ return entry.cookie;
+ }
+ }
+
+ LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x",
+ kMaxIterations, resid);
+ return kInvalidCookie;
+}
+
+void Theme::Clear() {
+ type_spec_flags_ = 0u;
+ for (std::unique_ptr<Package>& package : packages_) {
+ package.reset();
+ }
+}
+
+bool Theme::SetTo(const Theme& o) {
+ if (this == &o) {
+ return true;
+ }
+
+ if (asset_manager_ != o.asset_manager_) {
+ return false;
+ }
+
+ type_spec_flags_ = o.type_spec_flags_;
+
+ for (size_t p = 0; p < arraysize(packages_); 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++) {
+ const Type* type = package->types[t].get();
+ if (type == nullptr) {
+ packages_[p]->types[t].reset();
+ continue;
+ }
+
+ const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry));
+ void* copied_data = malloc(type_alloc_size);
+ memcpy(copied_data, type, type_alloc_size);
+ packages_[p]->types[t].reset(reinterpret_cast<Type*>(copied_data));
+ }
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/androidfw/Chunk.h b/libs/androidfw/Chunk.h
new file mode 100644
index 0000000..e87b940
--- /dev/null
+++ b/libs/androidfw/Chunk.h
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#ifndef CHUNK_H_
+#define CHUNK_H_
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "utils/ByteOrder.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+// Helpful wrapper around a ResChunk_header that provides getter methods
+// that handle endianness conversions and provide access to the data portion
+// of the chunk.
+class Chunk {
+ public:
+ explicit Chunk(const ResChunk_header* chunk) : device_chunk_(chunk) {}
+
+ // Returns the type of the chunk. Caller need not worry about endianness.
+ inline int type() const { return dtohs(device_chunk_->type); }
+
+ // Returns the size of the entire chunk. This can be useful for skipping
+ // over the entire chunk. Caller need not worry about endianness.
+ inline size_t size() const { return dtohl(device_chunk_->size); }
+
+ // Returns the size of the header. Caller need not worry about endianness.
+ inline size_t header_size() const { return dtohs(device_chunk_->headerSize); }
+
+ template <typename T>
+ inline const T* header() const {
+ if (header_size() >= sizeof(T)) {
+ return reinterpret_cast<const T*>(device_chunk_);
+ }
+ return nullptr;
+ }
+
+ inline const void* data_ptr() const {
+ return reinterpret_cast<const uint8_t*>(device_chunk_) + header_size();
+ }
+
+ inline size_t data_size() const { return size() - header_size(); }
+
+ private:
+ const ResChunk_header* device_chunk_;
+};
+
+// Provides a Java style iterator over an array of ResChunk_header's.
+// Validation is performed while iterating.
+// The caller should check if there was an error during chunk validation
+// by calling HadError() and GetLastError() to get the reason for failure.
+// Example:
+//
+// ChunkIterator iter(data_ptr, data_len);
+// while (iter.HasNext()) {
+// const Chunk chunk = iter.Next();
+// ...
+// }
+//
+// if (iter.HadError()) {
+// LOG(ERROR) << iter.GetLastError();
+// }
+//
+class ChunkIterator {
+ public:
+ ChunkIterator(const void* data, size_t len)
+ : next_chunk_(reinterpret_cast<const ResChunk_header*>(data)),
+ len_(len),
+ last_error_(nullptr) {
+ CHECK(next_chunk_ != nullptr) << "data can't be nullptr";
+ VerifyNextChunk();
+ }
+
+ Chunk Next();
+ inline bool HasNext() const { return !HadError() && len_ != 0; };
+ inline bool HadError() const { return last_error_ != nullptr; }
+ inline std::string GetLastError() const { return last_error_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChunkIterator);
+
+ // Returns false if there was an error.
+ bool VerifyNextChunk();
+
+ const ResChunk_header* next_chunk_;
+ size_t len_;
+ const char* last_error_;
+};
+
+} // namespace android
+
+#endif /* CHUNK_H_ */
diff --git a/libs/androidfw/ChunkIterator.cpp b/libs/androidfw/ChunkIterator.cpp
new file mode 100644
index 0000000..747aa4a
--- /dev/null
+++ b/libs/androidfw/ChunkIterator.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "Chunk.h"
+
+#include "android-base/logging.h"
+
+namespace android {
+
+Chunk ChunkIterator::Next() {
+ CHECK(len_ != 0) << "called Next() after last chunk";
+
+ const ResChunk_header* this_chunk = next_chunk_;
+
+ // We've already checked the values of this_chunk, so safely increment.
+ next_chunk_ = reinterpret_cast<const ResChunk_header*>(
+ reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size));
+ len_ -= dtohl(this_chunk->size);
+
+ if (len_ != 0) {
+ // Prepare the next chunk.
+ VerifyNextChunk();
+ }
+ return Chunk(this_chunk);
+}
+
+// Returns false if there was an error.
+bool ChunkIterator::VerifyNextChunk() {
+ const uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_);
+
+ // This data must be 4-byte aligned, since we directly
+ // access 32-bit words, which must be aligned on
+ // certain architectures.
+ if (header_start & 0x03) {
+ last_error_ = "header not aligned on 4-byte boundary";
+ return false;
+ }
+
+ if (len_ < sizeof(ResChunk_header)) {
+ last_error_ = "not enough space for header";
+ return false;
+ }
+
+ const size_t header_size = dtohs(next_chunk_->headerSize);
+ const size_t size = dtohl(next_chunk_->size);
+ if (header_size < sizeof(ResChunk_header)) {
+ last_error_ = "header size too small";
+ return false;
+ }
+
+ if (header_size > size) {
+ last_error_ = "header size is larger than entire chunk";
+ return false;
+ }
+
+ if (size > len_) {
+ last_error_ = "chunk size is bigger than given data";
+ return false;
+ }
+
+ if ((size | header_size) & 0x03) {
+ last_error_ = "header sizes are not aligned on 4-byte boundary";
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
new file mode 100644
index 0000000..94d0d46
--- /dev/null
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -0,0 +1,572 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/LoadedArsc.h"
+
+#include <cstddef>
+#include <limits>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "utils/ByteOrder.h"
+#include "utils/Trace.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+#include "Chunk.h"
+#include "androidfw/ByteBucketArray.h"
+#include "androidfw/Util.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+
+namespace {
+
+// 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;
+
+ // 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>;
+
+// 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.
+class TypeSpecPtrBuilder {
+ public:
+ TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {}
+
+ void AddType(const ResTable_type* type) {
+ ResTable_config config;
+ config.copyFromDtoH(type->config);
+ types_.push_back(Type{config, type});
+ }
+
+ TypeSpecPtr Build() {
+ // Check for overflow.
+ if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
+ return {};
+ }
+ TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
+ type_spec->type_spec = header_;
+ type_spec->type_count = types_.size();
+ memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
+ return TypeSpecPtr(type_spec);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
+
+ const ResTable_typeSpec* header_;
+ std::vector<Type> types_;
+};
+
+} // 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,
+ uint32_t* out_flags) const {
+ ATRACE_NAME("LoadedPackage::FindEntry");
+ const TypeSpecPtr& ptr = type_specs_[type_id];
+ 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)) {
+ return false;
+ }
+
+ const ResTable_config* best_config = nullptr;
+ const ResTable_type* best_type = nullptr;
+ uint32_t best_offset = 0;
+
+ for (uint32_t i = 0; i < ptr->type_count; i++) {
+ const Type* type = &ptr->types[i];
+
+ 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.
+ size_t entry_count = dtohl(type->type->entryCount);
+ if (entry_id < 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]);
+ if (offset != ResTable_type::NO_ENTRY) {
+ // There is an entry for this resource, record it.
+ best_config = &type->configuration;
+ best_type = type->type;
+ best_offset = offset + dtohl(type->type->entriesStart);
+ }
+ }
+ }
+ }
+
+ if (best_type == nullptr) {
+ return false;
+ }
+
+ const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
+ *out_flags = dtohl(flags[entry_id]);
+ *out_selected_config = *best_config;
+
+ const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(best_type) + best_offset);
+ out_entry->entry = best_entry;
+ 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;
+}
+
+// The destructor gets generated into arbitrary translation units
+// if left implicit, which causes the compiler to complain about
+// 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");
+ 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);
+
+ if (type_id == 0) {
+ LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
+ return false;
+ }
+
+ for (const auto& loaded_package : packages_) {
+ if (loaded_package->package_id_ == package_id) {
+ return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry,
+ out_selected_config, out_flags);
+ }
+ }
+ return false;
+}
+
+const std::string* LoadedArsc::GetPackageNameForId(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 nullptr;
+}
+
+static bool VerifyType(const Chunk& chunk) {
+ ATRACE_CALL();
+ const ResTable_type* header = chunk.header<ResTable_type>();
+
+ const size_t entry_count = dtohl(header->entryCount);
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
+ return false;
+ }
+
+ // Make sure that there is enough room for the entry offsets.
+ const size_t offsets_offset = chunk.header_size();
+ const size_t entries_offset = dtohl(header->entriesStart);
+ const size_t offsets_length = sizeof(uint32_t) * entry_count;
+
+ if (offsets_offset + offsets_length > entries_offset) {
+ LOG(ERROR) << "Entry offsets overlap actual entry data.";
+ return false;
+ }
+
+ if (entries_offset > chunk.size()) {
+ LOG(ERROR) << "Entry offsets extend beyond chunk.";
+ return false;
+ }
+
+ if (entries_offset & 0x03) {
+ LOG(ERROR) << "Entries start at unaligned address.";
+ return false;
+ }
+
+ // Check each entry offset.
+ const uint32_t* offsets =
+ reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(header) + offsets_offset);
+ for (size_t i = 0; i < entry_count; i++) {
+ uint32_t offset = dtohl(offsets[i]);
+ if (offset != ResTable_type::NO_ENTRY) {
+ // Check that the offset is aligned.
+ if (offset & 0x03) {
+ LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned.";
+ return false;
+ }
+
+ // Check that the offset doesn't overflow.
+ if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) {
+ // Overflow in offset.
+ LOG(ERROR) << "Entry offset at index " << i << " is too large.";
+ return false;
+ }
+
+ offset += entries_offset;
+ if (offset > chunk.size() - sizeof(ResTable_entry)) {
+ LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry.";
+ return false;
+ }
+
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(header) + offset);
+ const size_t entry_size = dtohs(entry->size);
+ if (entry_size < sizeof(*entry)) {
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " is too small.";
+ return false;
+ }
+
+ // Check the declared entrySize.
+ if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " is too large.";
+ return false;
+ }
+
+ // If this is a map entry, then keep validating.
+ if (entry_size >= sizeof(ResTable_map_entry)) {
+ const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
+ const size_t map_entry_count = dtohl(map->count);
+
+ size_t map_entries_start = offset + entry_size;
+ if (map_entries_start & 0x03) {
+ LOG(ERROR) << "Map entries 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.";
+ return false;
+ }
+
+ // Great, all the map entries fit!.
+ } else {
+ // There needs to be room for one Res_value struct.
+ if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
+ LOG(ERROR) << "No room for Res_value after ResTable_entry.";
+ return false;
+ }
+
+ const Res_value* value = 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 is too small.";
+ return false;
+ }
+
+ if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
+ LOG(ERROR) << "Res_value size is too large.";
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) {
+ ATRACE_CALL();
+ const ResTable_package* header = chunk.header<ResTable_package>();
+ if (header == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small.";
+ return false;
+ }
+
+ loaded_package->package_id_ = dtohl(header->id);
+
+ // A TypeSpec builder. We use this to accumulate the set of Types
+ // available for a TypeSpec, and later build a single, contiguous block
+ // of memory that holds all the Types together with the TypeSpec.
+ std::unique_ptr<TypeSpecPtrBuilder> types_builder;
+
+ // Keep track of the last seen type index. Since type IDs are 1-based,
+ // this records their index, which is 0-based (type ID - 1).
+ uint8_t last_type_idx = 0;
+
+ ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
+ while (iter.HasNext()) {
+ const Chunk child_chunk = iter.Next();
+ switch (child_chunk.type()) {
+ case RES_STRING_POOL_TYPE: {
+ const uintptr_t pool_address =
+ reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
+ const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
+ if (pool_address == header_address + dtohl(header->typeStrings)) {
+ // This string pool is the type string pool.
+ status_t err = loaded_package->type_string_pool_.setTo(
+ child_chunk.header<ResStringPool_header>(), child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt package type string pool.";
+ return false;
+ }
+ } else if (pool_address == header_address + dtohl(header->keyStrings)) {
+ // This string pool is the key string pool.
+ status_t err = loaded_package->key_string_pool_.setTo(
+ child_chunk.header<ResStringPool_header>(), child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt package key string pool.";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Too many string pool chunks found in package.";
+ }
+ } break;
+
+ case RES_TABLE_TYPE_SPEC_TYPE: {
+ ATRACE_NAME("LoadTableTypeSpec");
+
+ // Starting a new TypeSpec, so finish the old one if there was one.
+ if (types_builder) {
+ TypeSpecPtr type_spec_ptr = types_builder->Build();
+ if (type_spec_ptr == nullptr) {
+ LOG(ERROR) << "Too many type configurations, overflow detected.";
+ return false;
+ }
+
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+ types_builder = {};
+ last_type_idx = 0;
+ }
+
+ 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;
+ }
+
+ if (type_spec->id == 0) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.";
+ return false;
+ }
+
+ // The data portion of this chunk contains entry_count 32bit entries,
+ // each one representing a set of flags.
+ // Here we only validate that the chunk is well formed.
+ const size_t entry_count = dtohl(type_spec->entryCount);
+
+ // There can only be 2^16 entries in a type, because that is the ID
+ // 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;
+ }
+
+ 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;
+ }
+
+ last_type_idx = type_spec->id - 1;
+ types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec);
+ } break;
+
+ case RES_TABLE_TYPE_TYPE: {
+ const ResTable_type* type = child_chunk.header<ResTable_type>();
+ if (type == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small.";
+ return false;
+ }
+
+ if (type->id == 0) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0.";
+ return false;
+ }
+
+ // 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;
+ }
+
+ if (!VerifyType(child_chunk)) {
+ return false;
+ }
+
+ types_builder->AddType(type);
+ } break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ // Finish the last TypeSpec.
+ if (types_builder) {
+ TypeSpecPtr type_spec_ptr = types_builder->Build();
+ if (type_spec_ptr == nullptr) {
+ LOG(ERROR) << "Too many type configurations, overflow detected.";
+ return false;
+ }
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return false;
+ }
+ return true;
+}
+
+bool LoadedArsc::LoadTable(const Chunk& chunk) {
+ ATRACE_CALL();
+ const ResTable_header* header = chunk.header<ResTable_header>();
+ if (header == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE is too small.";
+ return false;
+ }
+
+ const size_t package_count = dtohl(header->packageCount);
+ size_t packages_seen = 0;
+
+ packages_.reserve(package_count);
+
+ ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
+ while (iter.HasNext()) {
+ const Chunk child_chunk = iter.Next();
+ switch (child_chunk.type()) {
+ case RES_STRING_POOL_TYPE:
+ // Only use the first string pool. Ignore others.
+ if (global_string_pool_.getError() == NO_INIT) {
+ status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(),
+ child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt string pool.";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Multiple string pool chunks found in resource table.";
+ }
+ break;
+
+ case RES_TABLE_PACKAGE_TYPE: {
+ if (packages_seen + 1 > package_count) {
+ LOG(ERROR) << "More package chunks were found than the " << package_count
+ << " declared in the "
+ "header.";
+ return false;
+ }
+ packages_seen++;
+
+ std::unique_ptr<LoadedPackage> loaded_package = util::make_unique<LoadedPackage>();
+ if (!LoadPackage(child_chunk, loaded_package.get())) {
+ return false;
+ }
+ packages_.push_back(std::move(loaded_package));
+ } break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len) {
+ ATRACE_CALL();
+
+ // Not using make_unique because the constructor is private.
+ std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+
+ ChunkIterator iter(data, len);
+ while (iter.HasNext()) {
+ const Chunk chunk = iter.Next();
+ switch (chunk.type()) {
+ case RES_TABLE_TYPE:
+ if (!loaded_arsc->LoadTable(chunk)) {
+ return {};
+ }
+ break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return {};
+ }
+ return loaded_arsc;
+}
+
+} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7fbfffe..a30c849 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -140,7 +140,7 @@
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-inline void Res_value::copyFrom_dtoh(const Res_value& src)
+void Res_value::copyFrom_dtoh(const Res_value& src)
{
size = dtohs(src.size);
res0 = src.res0;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
new file mode 100644
index 0000000..a3d67f0
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef APKASSETS_H_
+#define APKASSETS_H_
+
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_archive.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/LoadedArsc.h"
+
+namespace android {
+
+// Holds an APK.
+class ApkAssets {
+ public:
+ static std::unique_ptr<ApkAssets> Load(const std::string& path);
+
+ std::unique_ptr<Asset> Open(const std::string& path,
+ Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
+
+ inline const std::string& GetPath() const { return path_; }
+
+ inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ApkAssets);
+
+ ApkAssets() = default;
+
+ struct ZipArchivePtrCloser {
+ void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); }
+ };
+
+ using ZipArchivePtr =
+ std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
+ ZipArchivePtr zip_handle_;
+ std::string path_;
+ std::unique_ptr<Asset> resources_asset_;
+ std::unique_ptr<LoadedArsc> loaded_arsc_;
+};
+
+} // namespace android
+
+#endif /* APKASSETS_H_ */
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
new file mode 100644
index 0000000..66d5034
--- /dev/null
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROIDFW_ASSETMANAGER2_H_
+#define ANDROIDFW_ASSETMANAGER2_H_
+
+#include "android-base/macros.h"
+
+#include <limits>
+#include <unordered_map>
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Asset.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/Util.h"
+
+namespace android {
+
+class Theme;
+
+using ApkAssetsCookie = int32_t;
+
+enum : ApkAssetsCookie {
+ kInvalidCookie = -1,
+};
+
+// Holds a bag that has been merged with its parent, if one exists.
+struct ResolvedBag {
+ // A single key-value entry in a bag.
+ struct Entry {
+ // The key, as described in ResTable_map::name.
+ uint32_t key;
+
+ Res_value value;
+
+ // Which ApkAssets this entry came from.
+ ApkAssetsCookie cookie;
+
+ ResStringPool* key_pool;
+ ResStringPool* type_pool;
+ };
+
+ // Denotes the configuration axis that this bag varies with.
+ // If a configuration changes with respect to one of these axis,
+ // the bag should be reloaded.
+ uint32_t type_spec_flags;
+
+ // The number of entries in this bag. Access them by indexing into `entries`.
+ uint32_t entry_count;
+
+ // The array of entries for this bag. An empty array is a neat trick to force alignment
+ // of the Entry structs that follow this structure and avoids a bunch of casts.
+ Entry entries[0];
+};
+
+// AssetManager2 is the main entry point for accessing assets and resources.
+// AssetManager2 provides caching of resources retrieved via the underlying
+// ApkAssets.
+class AssetManager2 : public ::AAssetManager {
+ public:
+ struct ResourceName {
+ const char* package = nullptr;
+ size_t package_len = 0u;
+
+ const char* type = nullptr;
+ const char16_t* type16 = nullptr;
+ size_t type_len = 0u;
+
+ const char* entry = nullptr;
+ const char16_t* entry16 = nullptr;
+ size_t entry_len = 0u;
+ };
+
+ AssetManager2();
+
+ // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
+ // are not owned by the AssetManager, and must have a longer lifetime.
+ //
+ // Only pass invalidate_caches=false when it is known that the structure
+ // change in ApkAssets is due to a safe addition of resources with completely
+ // new resource IDs.
+ bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
+
+ const std::vector<const ApkAssets*> GetApkAssets() const;
+
+ // 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;
+
+ // 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;
+
+ // 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);
+
+ // 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);
+
+ // Searches the set of APKs loaded by this AssetManager and opens the first one found.
+ // `mode` controls how the file is opened.
+ // `out_cookie` is populated with the cookie of the APK this file was found in.
+ //
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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`.
+ // `out_flags` holds the same flags as retrieved with GetResourceFlags().
+ // If `density_override` is non-zero, the configuration to match against is overridden with that
+ // density.
+ //
+ // Returns a valid cookie if the resource was found. If the resource was not found, or if the
+ // resource was a map/bag type, then kInvalidCookie is returned. If `may_be_bag` is false,
+ // 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);
+
+ // 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.
+ // To iterate over the keys, use the following idiom:
+ //
+ // const AssetManager2::ResolvedBag* bag = asset_manager->GetBag(id);
+ // if (bag != nullptr) {
+ // for (auto iter = begin(bag); iter != end(bag); ++iter) {
+ // ...
+ // }
+ // }
+ const ResolvedBag* GetBag(uint32_t resid);
+
+ // Creates a new Theme from this AssetManager.
+ std::unique_ptr<Theme> NewTheme();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AssetManager2);
+
+ // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple
+ // Res_value, or a complex map/bag type.
+ //
+ // `density_override` overrides the density of the current configuration when doing a search.
+ //
+ // When `stop_at_first_match` is true, the first match found is selected and the search
+ // terminates. This is useful for methods that just look up the name of a resource and don't
+ // care about the value. In this case, the value of `out_flags` is incomplete and should not
+ // be used.
+ //
+ // `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,
+ uint32_t* out_flags);
+
+ // Purge all resources that are cached and vary by the configuration axis denoted by the
+ // bitmask `diff`.
+ void InvalidateCaches(uint32_t diff);
+
+ // 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_;
+
+ // The current configuration set for this AssetManager. When this changes, cached resources
+ // may need to be purged.
+ ResTable_config configuration_;
+
+ // Cached set of bags. These are cached because they can inherit keys from parent bags,
+ // which involves some calculation.
+ std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
+};
+
+class Theme {
+ friend class AssetManager2;
+
+ public:
+ // Applies the style identified by `resid` to this theme. This can be called
+ // multiple times with different styles. By default, any theme attributes that
+ // are already defined before this call are not overridden. If `force` is set
+ // to true, this behavior is changed and all theme attributes from the style at
+ // `resid` are applied.
+ // Returns false if the style failed to apply.
+ bool ApplyStyle(uint32_t resid, bool force = false);
+
+ // Sets this Theme to be a copy of `o` if `o` has the same AssetManager as this Theme.
+ // Returns false if the AssetManagers of the Themes were not compatible.
+ bool SetTo(const Theme& o);
+
+ void Clear();
+
+ inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+
+ // Returns a bit mask of configuration changes that will impact this
+ // theme (and thus require completely reloading it).
+ inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
+
+ // Retrieve a value in the theme. If the theme defines this value,
+ // returns an asset cookie indicating which ApkAssets it came from
+ // and populates `out_value` with the value. If `out_flags` is non-null,
+ // populates it with a bitmask of the configuration axis the resource
+ // varies with.
+ //
+ // If the attribute is not found, returns kInvalidCookie.
+ //
+ // NOTE: This function does not do reference traversal. If you want
+ // to follow references to other resources to get the "real" value to
+ // use, you need to call ResolveReference() after this function.
+ ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value,
+ uint32_t* out_flags = nullptr) const;
+
+ // This is like AssetManager2::ResolveReference(), but also takes
+ // care of resolving attribute references to the theme.
+ ApkAssetsCookie ResolveAttributeReference(Res_value* in_out_value, ApkAssetsCookie src_cookie,
+ uint32_t* out_last_ref = nullptr,
+ uint32_t* in_out_type_spec_flags = nullptr,
+ ResTable_config* out_selected_config = nullptr) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Theme);
+
+ // Called by AssetManager2.
+ explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {}
+
+ struct Entry {
+ ApkAssetsCookie cookie;
+ uint32_t type_spec_flags;
+ Res_value value;
+ };
+
+ struct Type {
+ // Use uint32_t for fewer cycles when loading from memory.
+ uint32_t entry_count;
+ uint32_t entry_capacity;
+ Entry entries[0];
+ };
+
+ static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
+ static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
+
+ 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];
+ };
+
+ AssetManager2* asset_manager_;
+ uint32_t type_spec_flags_ = 0u;
+ std::unique_ptr<Package> packages_[kPackageCount];
+};
+
+inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
+
+inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
+ return bag->entries + bag->entry_count;
+}
+
+} // namespace android
+
+#endif /* ANDROIDFW_ASSETMANAGER2_H_ */
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 87c6b12..d84a207 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,9 +17,10 @@
#ifndef __BYTE_BUCKET_ARRAY_H
#define __BYTE_BUCKET_ARRAY_H
-#include <utils/Log.h>
-#include <stdint.h>
-#include <string.h>
+#include <cstdint>
+#include <cstring>
+
+#include "android-base/logging.h"
namespace android {
@@ -27,71 +28,65 @@
* Stores a sparsely populated array. Has a fixed size of 256
* (number of entries that a byte can represent).
*/
-template<typename T>
+template <typename T>
class ByteBucketArray {
-public:
- ByteBucketArray() : mDefault() {
- memset(mBuckets, 0, sizeof(mBuckets));
+ public:
+ ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+
+ ~ByteBucketArray() {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ if (buckets_[i] != NULL) {
+ delete[] buckets_[i];
+ }
+ }
+ memset(buckets_, 0, sizeof(buckets_));
+ }
+
+ inline size_t size() const { return kNumBuckets * kBucketSize; }
+
+ inline const T& get(size_t index) const { return (*this)[index]; }
+
+ const T& operator[](size_t index) const {
+ if (index >= size()) {
+ return default_;
}
- ~ByteBucketArray() {
- for (size_t i = 0; i < NUM_BUCKETS; i++) {
- if (mBuckets[i] != NULL) {
- delete [] mBuckets[i];
- }
- }
- memset(mBuckets, 0, sizeof(mBuckets));
+ uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
+ T* bucket = buckets_[bucket_index];
+ if (bucket == NULL) {
+ return default_;
+ }
+ return bucket[0x0f & static_cast<uint8_t>(index)];
+ }
+
+ T& editItemAt(size_t index) {
+ CHECK(index < size()) << "ByteBucketArray.getOrCreate(index=" << index
+ << ") with size=" << size();
+
+ uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
+ T* bucket = buckets_[bucket_index];
+ if (bucket == NULL) {
+ bucket = buckets_[bucket_index] = new T[kBucketSize]();
+ }
+ return bucket[0x0f & static_cast<uint8_t>(index)];
+ }
+
+ bool set(size_t index, const T& value) {
+ if (index >= size()) {
+ return false;
}
- inline size_t size() const {
- return NUM_BUCKETS * BUCKET_SIZE;
- }
+ editItemAt(index) = value;
+ return true;
+ }
- inline const T& get(size_t index) const {
- return (*this)[index];
- }
+ private:
+ enum { kNumBuckets = 16, kBucketSize = 16 };
- const T& operator[](size_t index) const {
- if (index >= size()) {
- return mDefault;
- }
-
- uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
- T* bucket = mBuckets[bucketIndex];
- if (bucket == NULL) {
- return mDefault;
- }
- return bucket[0x0f & static_cast<uint8_t>(index)];
- }
-
- T& editItemAt(size_t index) {
- ALOG_ASSERT(index < size(), "ByteBucketArray.getOrCreate(index=%u) with size=%u",
- (uint32_t) index, (uint32_t) size());
-
- uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
- T* bucket = mBuckets[bucketIndex];
- if (bucket == NULL) {
- bucket = mBuckets[bucketIndex] = new T[BUCKET_SIZE]();
- }
- return bucket[0x0f & static_cast<uint8_t>(index)];
- }
-
- bool set(size_t index, const T& value) {
- if (index >= size()) {
- return false;
- }
-
- editItemAt(index) = value;
- return true;
- }
-
-private:
- enum { NUM_BUCKETS = 16, BUCKET_SIZE = 16 };
-
- T* mBuckets[NUM_BUCKETS];
- T mDefault;
+ T* buckets_[kNumBuckets];
+ T default_;
};
-} // namespace android
+} // namespace android
-#endif // __BYTE_BUCKET_ARRAY_H
+#endif // __BYTE_BUCKET_ARRAY_H
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
new file mode 100644
index 0000000..e2e56c8
--- /dev/null
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#ifndef LOADEDARSC_H_
+#define LOADEDARSC_H_
+
+#include <memory>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+class Chunk;
+class LoadedPackage;
+
+// Read-only view into a resource table. This class validates all data
+// when loading, including offsets and lengths.
+class LoadedArsc {
+ public:
+ // Load the resource table from memory. The data's lifetime must out-live the
+ // object returned from this method.
+ static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len);
+
+ ~LoadedArsc();
+
+ // Returns the string pool where all string resource values
+ // (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,
+ 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;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
+
+ LoadedArsc() = default;
+ bool LoadTable(const Chunk& chunk);
+
+ ResStringPool global_string_pool_;
+ std::vector<std::unique_ptr<LoadedPackage>> packages_;
+};
+
+} // namespace android
+
+#endif /* LOADEDARSC_H_ */
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 33b91b9..c118b57 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -265,7 +265,7 @@
uint8_t res0;
// Type of the data value.
- enum {
+ enum : uint8_t {
// The 'data' is either 0 or 1, specifying this resource is either
// undefined or empty, respectively.
TYPE_NULL = 0x00,
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
new file mode 100644
index 0000000..5266d09
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+#ifndef UTIL_H_
+#define UTIL_H_
+
+#include <cstdlib>
+#include <memory>
+
+#include "android-base/macros.h"
+
+namespace android {
+namespace util {
+
+/**
+ * Makes a std::unique_ptr<> with the template parameter inferred by the
+ * compiler.
+ * This will be present in C++14 and can be removed then.
+ */
+template <typename T, class... Args>
+std::unique_ptr<T> make_unique(Args&&... args) {
+ return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
+}
+
+// Based on std::unique_ptr, but uses free() to release malloc'ed memory
+// without incurring the size increase of holding on to a custom deleter.
+template <typename T>
+class unique_cptr {
+ public:
+ using pointer = typename std::add_pointer<T>::type;
+
+ constexpr unique_cptr() : ptr_(nullptr) {}
+ constexpr unique_cptr(std::nullptr_t) : ptr_(nullptr) {}
+ explicit unique_cptr(pointer ptr) : ptr_(ptr) {}
+ unique_cptr(unique_cptr&& o) : ptr_(o.ptr_) { o.ptr_ = nullptr; }
+
+ ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); }
+
+ inline unique_cptr& operator=(unique_cptr&& o) {
+ if (&o == this) {
+ return *this;
+ }
+
+ std::free(reinterpret_cast<void*>(ptr_));
+ ptr_ = o.ptr_;
+ o.ptr_ = nullptr;
+ return *this;
+ }
+
+ inline unique_cptr& operator=(std::nullptr_t) {
+ std::free(reinterpret_cast<void*>(ptr_));
+ ptr_ = nullptr;
+ return *this;
+ }
+
+ pointer release() {
+ pointer result = ptr_;
+ ptr_ = nullptr;
+ return result;
+ }
+
+ inline pointer get() const { return ptr_; }
+
+ void reset(pointer ptr = pointer()) {
+ if (ptr == ptr_) {
+ return;
+ }
+
+ pointer old_ptr = ptr_;
+ ptr_ = ptr;
+ std::free(reinterpret_cast<void*>(old_ptr));
+ }
+
+ inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); }
+
+ inline explicit operator bool() const { return ptr_ != nullptr; }
+
+ inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; }
+
+ inline pointer operator->() const { return ptr_; }
+
+ inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; }
+
+ inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(unique_cptr);
+
+ pointer ptr_;
+};
+
+inline uint8_t get_package_id(uint32_t resid) {
+ return static_cast<uint8_t>((resid >> 24) & 0x000000ffu);
+}
+
+// The type ID is 1-based, so if the returned value is 0 it is invalid.
+inline uint8_t get_type_id(uint32_t resid) {
+ return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
+}
+
+inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+
+inline bool is_internal_id(uint32_t resid) {
+ return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
+}
+
+inline bool is_valid_resid(uint32_t resid) {
+ return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0;
+}
+
+} // namespace util
+} // namespace android
+
+#endif /* UTIL_H_ */
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index d91a133..6754cd8 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -21,22 +21,31 @@
LOCAL_PATH:= $(call my-dir)
testFiles := \
+ ApkAssets_test.cpp \
AppAsLib_test.cpp \
Asset_test.cpp \
+ AssetManager2_test.cpp \
AttributeFinder_test.cpp \
AttributeResolution_test.cpp \
ByteBucketArray_test.cpp \
Config_test.cpp \
ConfigLocale_test.cpp \
Idmap_test.cpp \
- Main.cpp \
+ LoadedArsc_test.cpp \
ResTable_test.cpp \
Split_test.cpp \
TestHelpers.cpp \
+ TestMain.cpp \
Theme_test.cpp \
TypeWrappers_test.cpp \
ZipUtils_test.cpp
+benchmarkFiles := \
+ AssetManager2_bench.cpp \
+ BenchMain.cpp \
+ TestHelpers.cpp \
+ Theme_bench.cpp
+
androidfw_test_cflags := \
-Wall \
-Werror \
@@ -89,5 +98,25 @@
LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
include $(BUILD_NATIVE_TEST)
+
+# ==========================================================
+# Build the device benchmarks: libandroidfw_benchmarks
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libandroidfw_benchmarks
+LOCAL_CFLAGS := $(androidfw_test_cflags)
+LOCAL_SRC_FILES := $(benchmarkFiles)
+LOCAL_STATIC_LIBRARIES := \
+ libgoogle-benchmark
+LOCAL_SHARED_LIBRARIES := \
+ libandroidfw \
+ libbase \
+ libcutils \
+ libutils \
+ libziparchive
+LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
+
+include $(BUILD_NATIVE_TEST)
endif # Not SDK_ONLY
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
new file mode 100644
index 0000000..3a1fc8f
--- /dev/null
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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/ApkAssets.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+
+using com::android::basic::R;
+
+namespace android {
+
+TEST(ApkAssetsTest, LoadApk) {
+ std::unique_ptr<ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, loaded_apk);
+
+ std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
+ ASSERT_NE(nullptr, asset);
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
new file mode 100644
index 0000000..9ff9478
--- /dev/null
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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 "benchmark/benchmark.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace basic = com::android::basic;
+namespace app = com::android::app;
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+
+static void BM_AssetManagerLoadAssets(benchmark::State& state) {
+ std::string path = GetTestDataPath() + "/basic/basic.apk";
+ while (state.KeepRunning()) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path);
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+ }
+}
+BENCHMARK(BM_AssetManagerLoadAssets);
+
+static void BM_AssetManagerLoadAssetsOld(benchmark::State& state) {
+ String8 path((GetTestDataPath() + "/basic/basic.apk").data());
+ while (state.KeepRunning()) {
+ AssetManager assets;
+ assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAsset */);
+
+ // Force creation.
+ assets.getResources(true);
+ }
+}
+BENCHMARK(BM_AssetManagerLoadAssetsOld);
+
+static void BM_AssetManagerLoadFrameworkAssets(benchmark::State& state) {
+ std::string path = kFrameworkPath;
+ while (state.KeepRunning()) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path);
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+ }
+}
+BENCHMARK(BM_AssetManagerLoadFrameworkAssets);
+
+static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) {
+ String8 path(kFrameworkPath);
+ while (state.KeepRunning()) {
+ AssetManager assets;
+ assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAsset */);
+
+ // Force creation.
+ assets.getResources(true);
+ }
+}
+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;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ 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);
+ }
+}
+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);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResourceOld);
+
+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);
+ }
+}
+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);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResourceFrameworkLocaleOld);
+
+static void BM_AssetManagerGetBag(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ while (state.KeepRunning()) {
+ const ResolvedBag* bag = assets.GetBag(app::R::style::StyleTwo);
+ const auto bag_end = end(bag);
+ for (auto iter = begin(bag); iter != bag_end; ++iter) {
+ uint32_t key = iter->key;
+ Res_value value = iter->value;
+ benchmark::DoNotOptimize(key);
+ benchmark::DoNotOptimize(value);
+ }
+ }
+}
+BENCHMARK(BM_AssetManagerGetBag);
+
+static void BM_AssetManagerGetBagOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8((GetTestDataPath() + "/styles/styles.apk").data()),
+ nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAssets */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& table = assets.getResources(true);
+
+ while (state.KeepRunning()) {
+ const ResTable::bag_entry* bag_begin;
+ const ssize_t N = table.lockBag(app::R::style::StyleTwo, &bag_begin);
+ const ResTable::bag_entry* const bag_end = bag_begin + N;
+ for (auto iter = bag_begin; iter != bag_end; ++iter) {
+ uint32_t key = iter->map.name.ident;
+ Res_value value = iter->map.value;
+ benchmark::DoNotOptimize(key);
+ benchmark::DoNotOptimize(value);
+ }
+ table.unlockBag(bag_begin);
+ }
+}
+BENCHMARK(BM_AssetManagerGetBagOld);
+
+} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
new file mode 100644
index 0000000..39c5381
--- /dev/null
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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/AssetManager2.h"
+#include "androidfw/AssetManager.h"
+
+#include "android-base/logging.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace basic = com::android::basic;
+namespace app = com::android::app;
+
+namespace android {
+
+class AssetManager2Test : public ::testing::Test {
+ public:
+ void SetUp() override {
+ basic_assets_ = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, basic_assets_);
+
+ basic_de_fr_assets_ = ApkAssets::Load(GetTestDataPath() + "/basic/basic_de_fr.apk");
+ ASSERT_NE(nullptr, basic_de_fr_assets_);
+
+ style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, style_assets_);
+ }
+
+ protected:
+ std::unique_ptr<ApkAssets> basic_assets_;
+ std::unique_ptr<ApkAssets> basic_de_fr_assets_;
+ std::unique_ptr<ApkAssets> style_assets_;
+};
+
+TEST_F(AssetManager2Test, FindsResourcesFromSingleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ // Came from our ApkAssets.
+ EXPECT_EQ(0, cookie);
+
+ // It is the default config.
+ EXPECT_EQ(0, selected_config.language[0]);
+ EXPECT_EQ(0, selected_config.language[1]);
+
+ // It is a string.
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+}
+
+TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ // Came from our de_fr ApkAssets.
+ EXPECT_EQ(1, cookie);
+
+ // The configuration is german.
+ EXPECT_EQ('d', selected_config.language[0]);
+ EXPECT_EQ('e', selected_config.language[1]);
+
+ // It is a string.
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+}
+
+TEST_F(AssetManager2Test, FindsBagResourcesFromSingleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ const ResolvedBag* bag = assetmanager.GetBag(basic::R::array::integerArray1);
+ ASSERT_NE(nullptr, bag);
+ ASSERT_EQ(3u, bag->entry_count);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag->entries[0].value.data);
+ EXPECT_EQ(0, bag->entries[0].cookie);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[1].value.dataType);
+ EXPECT_EQ(2u, bag->entries[1].value.data);
+ EXPECT_EQ(0, bag->entries[1].cookie);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[2].value.dataType);
+ EXPECT_EQ(3u, bag->entries[2].value.data);
+ EXPECT_EQ(0, bag->entries[2].cookie);
+}
+
+TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ const ResolvedBag* bag_one = assetmanager.GetBag(app::R::style::StyleOne);
+ ASSERT_NE(nullptr, bag_one);
+ ASSERT_EQ(2u, bag_one->entry_count);
+
+ EXPECT_EQ(app::R::attr::attr_one, bag_one->entries[0].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_one->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag_one->entries[0].value.data);
+ EXPECT_EQ(0, bag_one->entries[0].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_two, bag_one->entries[1].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_one->entries[1].value.dataType);
+ EXPECT_EQ(2u, bag_one->entries[1].value.data);
+ EXPECT_EQ(0, bag_one->entries[1].cookie);
+
+ const ResolvedBag* bag_two = assetmanager.GetBag(app::R::style::StyleTwo);
+ ASSERT_NE(nullptr, bag_two);
+ ASSERT_EQ(5u, bag_two->entry_count);
+
+ // attr_one is inherited from StyleOne.
+ EXPECT_EQ(app::R::attr::attr_one, bag_two->entries[0].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag_two->entries[0].value.data);
+ EXPECT_EQ(0, bag_two->entries[0].cookie);
+
+ // attr_two should be overridden from StyleOne by StyleTwo.
+ EXPECT_EQ(app::R::attr::attr_two, bag_two->entries[1].key);
+ EXPECT_EQ(Res_value::TYPE_STRING, bag_two->entries[1].value.dataType);
+ EXPECT_EQ(0, bag_two->entries[1].cookie);
+ EXPECT_EQ(std::string("string"), GetStringFromPool(assetmanager.GetStringPoolForCookie(0),
+ bag_two->entries[1].value.data));
+
+ // The rest are new attributes.
+
+ EXPECT_EQ(app::R::attr::attr_three, bag_two->entries[2].key);
+ EXPECT_EQ(Res_value::TYPE_ATTRIBUTE, bag_two->entries[2].value.dataType);
+ EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[2].value.data);
+ EXPECT_EQ(0, bag_two->entries[2].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_five, bag_two->entries[3].key);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, bag_two->entries[3].value.dataType);
+ EXPECT_EQ(app::R::string::string_one, bag_two->entries[3].value.data);
+ EXPECT_EQ(0, bag_two->entries[3].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[4].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[4].value.dataType);
+ EXPECT_EQ(3u, bag_two->entries[4].value.data);
+ EXPECT_EQ(0, bag_two->entries[4].cookie);
+}
+
+TEST_F(AssetManager2Test, FindsBagResourcesFromMultipleApkAssets) {}
+
+TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {}
+
+TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
+
+} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 7550517..1ff2ed4 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -205,4 +205,5 @@
EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
}
-} // namespace android
+} // namespace android
+
diff --git a/libs/androidfw/tests/BenchMain.cpp b/libs/androidfw/tests/BenchMain.cpp
new file mode 100644
index 0000000..105c5f9
--- /dev/null
+++ b/libs/androidfw/tests/BenchMain.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 <iostream>
+
+#include "benchmark/benchmark.h"
+
+#include "TestHelpers.h"
+
+int main(int argc, char** argv) {
+ ::benchmark::Initialize(&argc, argv);
+ ::android::InitializeTest(&argc, argv);
+
+ std::cerr << "using --testdata=" << ::android::GetTestDataPath() << "\n";
+
+ size_t result = ::benchmark::RunSpecifiedBenchmarks();
+ return result == 0 ? 1 : 0;
+}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
new file mode 100644
index 0000000..47b3894
--- /dev/null
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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/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/styles/R.h"
+
+namespace app = com::android::app;
+namespace basic = com::android::basic;
+
+namespace android {
+
+TEST(LoadedArscTest, LoadSinglePackageArsc) {
+ base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
+ &contents));
+
+ std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+ ASSERT_NE(nullptr, loaded_arsc);
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 24;
+
+ LoadedArsc::Entry entry;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ASSERT_TRUE(
+ loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags));
+ ASSERT_NE(nullptr, entry.entry);
+}
+
+TEST(LoadedArscTest, FindDefaultEntry) {
+ base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
+ std::string contents;
+ ASSERT_TRUE(
+ ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+
+ std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+ 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';
+
+ LoadedArsc::Entry entry;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry,
+ &selected_config, &flags));
+ ASSERT_NE(nullptr, entry.entry);
+}
+
+// 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.
+TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+
+} // namespace android
diff --git a/libs/androidfw/tests/Main.cpp b/libs/androidfw/tests/Main.cpp
deleted file mode 100644
index 6a50691..0000000
--- a/libs/androidfw/tests/Main.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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 <libgen.h>
-
-#include <iostream>
-#include <memory>
-#include <string>
-
-#include "android-base/file.h"
-#include "android-base/strings.h"
-#include "gtest/gtest.h"
-
-#include "TestHelpers.h"
-
-// Extract the directory of the current executable path.
-static std::string GetExecutableDir() {
- const std::string path = android::base::GetExecutablePath();
- std::unique_ptr<char, decltype(&std::free)> mutable_path = {
- strdup(path.c_str()), std::free};
- std::string executable_dir = dirname(mutable_path.get());
- return executable_dir;
-}
-
-int main(int argc, char** argv) {
- ::testing::InitGoogleTest(&argc, argv);
-
- // Set the default test data path to be the executable path directory.
- android::SetTestDataPath(GetExecutableDir());
-
- const char* command = argv[0];
- ++argv;
- --argc;
-
- while (argc > 0) {
- const std::string arg = *argv;
- if (android::base::StartsWith(arg, "--testdata=")) {
- android::SetTestDataPath(arg.substr(strlen("--testdata=")));
- } else if (arg == "-h" || arg == "--help") {
- std::cerr
- << "\nAdditional options specific to this test:\n"
- " --testdata=[PATH]\n"
- " Specify the location of test data used within the tests.\n";
- return 1;
- } else {
- std::cerr << command << ": Unrecognized argument '" << *argv << "'.\n";
- return 1;
- }
-
- --argc;
- ++argv;
- }
-
- std::cerr << "using --testdata=" << android::GetTestDataPath() << "\n";
- return RUN_ALL_TESTS();
-}
diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp
index 2c834b1..1e763a5 100644
--- a/libs/androidfw/tests/TestHelpers.cpp
+++ b/libs/androidfw/tests/TestHelpers.cpp
@@ -16,15 +16,51 @@
#include "TestHelpers.h"
+#include <libgen.h>
#include <unistd.h>
+#include <memory>
+#include <string>
+
+#include "android-base/file.h"
#include "android-base/logging.h"
+#include "android-base/strings.h"
#include "ziparchive/zip_archive.h"
namespace android {
static std::string sTestDataPath;
+// Extract the directory of the current executable path.
+static std::string GetExecutableDir() {
+ const std::string path = base::GetExecutablePath();
+ std::unique_ptr<char, decltype(&std::free)> mutable_path = {strdup(path.c_str()), std::free};
+ std::string executable_dir = dirname(mutable_path.get());
+ return executable_dir;
+}
+
+void InitializeTest(int* argc, char** argv) {
+ // Set the default test data path to be the executable path directory.
+ SetTestDataPath(GetExecutableDir());
+
+ for (int i = 1; i < *argc; i++) {
+ const std::string arg = argv[i];
+ if (base::StartsWith(arg, "--testdata=")) {
+ SetTestDataPath(arg.substr(strlen("--testdata=")));
+ for (int j = i; j != *argc; j++) {
+ argv[j] = argv[j + 1];
+ }
+ --(*argc);
+ --i;
+ } else if (arg == "-h" || arg == "--help") {
+ std::cerr << "\nAdditional options specific to this test:\n"
+ " --testdata=[PATH]\n"
+ " Specify the location of test data used within the tests.\n";
+ exit(1);
+ }
+ }
+}
+
void SetTestDataPath(const std::string& path) { sTestDataPath = path; }
const std::string& GetTestDataPath() {
@@ -90,4 +126,9 @@
return ::testing::AssertionSuccess() << actual_str.string();
}
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) {
+ String8 str = pool->string8ObjectAt(idx);
+ return std::string(str.string(), str.length());
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index d9cee22..a11ea84 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -35,6 +35,8 @@
namespace android {
+void InitializeTest(int* argc, char** argv);
+
enum { MAY_NOT_BE_BAG = false };
void SetTestDataPath(const std::string& path);
@@ -56,6 +58,8 @@
::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
const char* expected_str);
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx);
+
} // namespace android
#endif // TEST_HELPERS_H_
diff --git a/libs/androidfw/tests/TestMain.cpp b/libs/androidfw/tests/TestMain.cpp
new file mode 100644
index 0000000..d1c0f60
--- /dev/null
+++ b/libs/androidfw/tests/TestMain.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 <iostream>
+
+#include "TestHelpers.h"
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ::android::InitializeTest(&argc, argv);
+
+ std::cerr << "using --testdata=" << ::android::GetTestDataPath() << "\n";
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/androidfw/tests/Theme_bench.cpp b/libs/androidfw/tests/Theme_bench.cpp
new file mode 100644
index 0000000..c471be6
--- /dev/null
+++ b/libs/androidfw/tests/Theme_bench.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "benchmark/benchmark.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr const static uint32_t kStyleId = 0x01030237u; // android:style/Theme.Material.Light
+constexpr const static uint32_t kAttrId = 0x01010030u; // android:attr/colorForeground
+
+static void BM_ThemeApplyStyleFramework(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()});
+
+ while (state.KeepRunning()) {
+ auto theme = assets.NewTheme();
+ theme->ApplyStyle(kStyleId, false /* force */);
+ }
+}
+BENCHMARK(BM_ThemeApplyStyleFramework);
+
+static void BM_ThemeApplyStyleFrameworkOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /* cookie */, false /* appAsLib */,
+ true /* isSystemAsset */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& res_table = assets.getResources(true);
+
+ while (state.KeepRunning()) {
+ std::unique_ptr<ResTable::Theme> theme{new ResTable::Theme(res_table)};
+ theme->applyStyle(kStyleId, false /* force */);
+ }
+}
+BENCHMARK(BM_ThemeApplyStyleFrameworkOld);
+
+static void BM_ThemeGetAttribute(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ auto theme = assets.NewTheme();
+ theme->ApplyStyle(kStyleId, false /* force */);
+
+ Res_value value;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ theme->GetAttribute(kAttrId, &value, &flags);
+ }
+}
+BENCHMARK(BM_ThemeGetAttribute);
+
+static void BM_ThemeGetAttributeOld(benchmark::State& state) {
+ AssetManager assets;
+ assets.addAssetPath(String8(kFrameworkPath), nullptr /* cookie */, false /* appAsLib */,
+ true /* isSystemAsset */);
+ const ResTable& res_table = assets.getResources(true);
+ std::unique_ptr<ResTable::Theme> theme{new ResTable::Theme(res_table)};
+ theme->applyStyle(kStyleId, false /* force */);
+
+ Res_value value;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ theme->getAttribute(kAttrId, &value, &flags);
+ }
+}
+BENCHMARK(BM_ThemeGetAttributeOld);
+
+} // namespace android
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 3774657..c0011b6d 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,59 +14,221 @@
* limitations under the License.
*/
-#include "androidfw/ResourceTypes.h"
+#include "androidfw/AssetManager2.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
+#include "android-base/logging.h"
#include "TestHelpers.h"
-#include "data/app/R.h"
-#include "data/system/R.h"
+#include "data/styles/R.h"
namespace app = com::android::app;
namespace android {
-/**
- * TODO(adamlesinski): Enable when fixed.
- */
-TEST(ThemeTest, DISABLED_shouldCopyThemeFromDifferentResTable) {
- ResTable table;
+class ThemeTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, style_assets_);
+ }
- std::string system_contents;
- ASSERT_TRUE(ReadFileFromZipToString("/system/system.apk", "resources.arsc",
- &system_contents));
- ASSERT_EQ(NO_ERROR,
- table.add(system_contents.data(), system_contents.size()));
+ protected:
+ std::unique_ptr<ApkAssets> style_assets_;
+};
- std::string app_contents;
- ASSERT_TRUE(ReadFileFromZipToString("/basic/basic.apk", "resources.arsc",
- &app_contents));
- ASSERT_EQ(NO_ERROR, table.add(app_contents.data(), app_contents.size()));
+TEST_F(ThemeTest, EmptyTheme) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
- ResTable::Theme theme1(table);
- ASSERT_EQ(NO_ERROR, theme1.applyStyle(app::R::style::Theme_One));
- Res_value val;
- ASSERT_GE(theme1.getAttribute(android::R::attr::background, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
- ASSERT_EQ(uint32_t(0xffff0000), val.data);
- ASSERT_GE(theme1.getAttribute(app::R::attr::number, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- ASSERT_EQ(uint32_t(1), val.data);
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ EXPECT_EQ(0u, theme->GetChangingConfigurations());
+ EXPECT_EQ(&assetmanager, theme->GetAssetManager());
- ResTable table2;
- ASSERT_EQ(NO_ERROR,
- table2.add(system_contents.data(), system_contents.size()));
- ASSERT_EQ(NO_ERROR, table2.add(app_contents.data(), app_contents.size()));
+ Res_value value;
+ uint32_t flags;
+ EXPECT_EQ(kInvalidCookie, theme->GetAttribute(app::R::attr::attr_one, &value, &flags));
+}
- ResTable::Theme theme2(table2);
- ASSERT_EQ(NO_ERROR, theme2.setTo(theme1));
- ASSERT_GE(theme2.getAttribute(android::R::attr::background, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
- ASSERT_EQ(uint32_t(0xffff0000), val.data);
- ASSERT_GE(theme2.getAttribute(app::R::attr::number, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- ASSERT_EQ(uint32_t(1), val.data);
+TEST_F(ThemeTest, SingleThemeNoParent) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleOne));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ cookie = theme->GetAttribute(app::R::attr::attr_two, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(2u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, SingleThemeWithParent) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ cookie = theme->GetAttribute(app::R::attr::attr_two, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+ EXPECT_EQ(0, cookie);
+ EXPECT_EQ(std::string("string"),
+ GetStringFromPool(assetmanager.GetStringPoolForCookie(0), value.data));
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // This attribute should point to an attr_indirect, so the result should be 3.
+ cookie = theme->GetAttribute(app::R::attr::attr_three, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(3u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleThree));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_six
+ cookie = theme->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the old attr_five (force=true was not used).
+ cookie = theme->GetAttribute(app::R::attr::attr_five, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+ EXPECT_EQ(app::R::string::string_one, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, MultipleThemesOverlaidForced) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleThree, true /* force */));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_six
+ cookie = theme->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_five (force=true was used).
+ cookie = theme->GetAttribute(app::R::attr::attr_five, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(5u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, CopyThemeSameAssetManager) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme_one = assetmanager.NewTheme();
+ ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // attr_six is not here.
+ EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_six, &value, &flags));
+
+ std::unique_ptr<Theme> theme_two = assetmanager.NewTheme();
+ ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleThree));
+
+ // Copy the theme to theme_one.
+ ASSERT_TRUE(theme_one->SetTo(*theme_two));
+
+ // Clear theme_two to make sure we test that there WAS a copy.
+ theme_two->Clear();
+
+ // attr_one is now not here.
+ EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags));
+
+ // attr_six is now here because it was copied.
+ cookie = theme_one->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, FailToCopyThemeWithDifferentAssetManager) {
+ AssetManager2 assetmanager_one;
+ assetmanager_one.SetApkAssets({style_assets_.get()});
+
+ AssetManager2 assetmanager_two;
+ assetmanager_two.SetApkAssets({style_assets_.get()});
+
+ auto theme_one = assetmanager_one.NewTheme();
+ ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne));
+
+ auto theme_two = assetmanager_two.NewTheme();
+ ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo));
+
+ EXPECT_FALSE(theme_one->SetTo(*theme_two));
}
} // namespace android
diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h
index 4127aa0..68527c7 100644
--- a/libs/androidfw/tests/data/styles/R.h
+++ b/libs/androidfw/tests/data/styles/R.h
@@ -32,6 +32,7 @@
attr_four = 0x7f010003u,
attr_five = 0x7f010004u,
attr_indirect = 0x7f010005u,
+ attr_six = 0x7f010006u,
};
};
@@ -45,6 +46,7 @@
enum : uint32_t {
StyleOne = 0x7f020000u,
StyleTwo = 0x7f020001u,
+ StyleThree = 0x7f020002u,
};
};
};
diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml
index 70c54f6..da592f8 100644
--- a/libs/androidfw/tests/data/styles/res/values/styles.xml
+++ b/libs/androidfw/tests/data/styles/res/values/styles.xml
@@ -39,6 +39,7 @@
<public type="style" name="StyleOne" id="0x7f020000" />
<style name="StyleOne">
<item name="attr_one">1</item>
+ <item name="attr_two">2</item>
</style>
<public type="style" name="StyleTwo" id="0x7f020001" />
@@ -48,5 +49,14 @@
<item name="attr_three">?attr/attr_indirect</item>
<item name="attr_five">@string/string_one</item>
</style>
+
+ <public type="attr" name="attr_six" id="0x7f010006" />
+ <attr name="attr_six" />
+
+ <public type="style" name="StyleThree" id="0x7f020002" />
+ <style name="StyleThree">
+ <item name="attr_six">6</item>
+ <item name="attr_five">5</item>
+ </style>
</resources>
diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk
index 6064c48..d4ccb83 100644
--- a/libs/androidfw/tests/data/styles/styles.apk
+++ b/libs/androidfw/tests/data/styles/styles.apk
Binary files differ