New implementation of AssetManager/ResTable
The multiwindow model and Resources-per-activity
model that came in N puts greater demands on AssetManagers.
They are created whenever window dimensions change, which
can be frequently. There is a need to be able to cheaply
create a new AssetManager for each Activity, which shares
a lot of underlying state.
In order to make the creation of AssetManagers cheap,
we need a new implementation of the native AssetManager
and ResTable to support immutable representations of
APKs. This new data structure/class is ApkAssets.
ApkAssets have the same functionality of an AssetManager, except
that they operate on a single APK, and they do not do any caching.
Once loaded, they are immutable.
ApkAssets will be exposed as a Java object, with its implementation in
native code. The existing Java StringBlock will be owned by ApkAssets,
which means that Strings can be shared across AssetManagers.
ApkAssets can be cached by the ResourcesManager. Creating an AssetManager
requires only a list of ApkAssets and a configuration.
AssetManager2 (named with the suffix '2' for now while transitioning
to the new implementation) caches bags that are accessed.
Since ApkAssets are expected to be kept around longer, they do more validation
of the resource table, which cause slower load times. Measured on an angler-userdebug,
loading the framework assets takes 11ms with ApkAssets, and 2ms with the old
AssetManager implementation.
The tradeoff is that there does not need to be any security checks once an ApkAssets
is loaded, and regular resource retrieval is faster. Measured on an angler-userdebug,
accessing resource (android:string/ok) with many locales takes 18us with AssetManager2,
and 19us with AssetManager (this is per resource, so these add up).
Test: make libandroidfw_tests
Change-Id: Id0e57ee828f17008891fe3741935a9be8830b01d
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 907d914..d11e07a 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 08d6591..dadfe89 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