AssetManager2: Implement IDMAP support
This enables RRO (runtime resource overlays) with AssetManager2
Test: make libandroidfw_tests
Test: out/host/<platform>/nativetest64/libandroidfw_tests/libandroidfw_tests --testdata=frameworks/base/libs/androidfw/tests/data
Change-Id: Id8079104faefbfaa3f4017d8f7ee1a8968f151a2
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 987096f..5484cf0 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -43,6 +43,7 @@
"AssetManager2.cpp",
"AttributeResolution.cpp",
"ChunkIterator.cpp",
+ "Idmap.cpp",
"LoadedArsc.cpp",
"LocaleData.cpp",
"misc.cpp",
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 0e577d1..158df13 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -20,64 +20,126 @@
#include <algorithm>
+#include "android-base/errors.h"
+#include "android-base/file.h"
#include "android-base/logging.h"
+#include "android-base/unique_fd.h"
+#include "android-base/utf8.h"
+#include "utils/Compat.h"
#include "utils/FileMap.h"
#include "utils/Trace.h"
#include "ziparchive/zip_archive.h"
#include "androidfw/Asset.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
namespace android {
-ApkAssets::ApkAssets() : zip_handle_(nullptr, ::CloseArchive) {}
+using base::SystemErrorCodeToString;
+using base::unique_fd;
+
+static const std::string kResourcesArsc("resources.arsc");
+
+ApkAssets::ApkAssets(void* unmanaged_handle, const std::string& path)
+ : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) {
+}
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
- return ApkAssets::LoadImpl(path, system, false /*load_as_shared_library*/);
+ return ApkAssets::LoadImpl(path, nullptr, nullptr, system, false /*load_as_shared_library*/);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
bool system) {
- return ApkAssets::LoadImpl(path, system, true /*load_as_shared_library*/);
+ return ApkAssets::LoadImpl(path, nullptr, nullptr, system, true /*load_as_shared_library*/);
}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(const std::string& path, bool system,
- bool load_as_shared_library) {
+std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
+ bool system) {
+ std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
+ if (idmap_asset == nullptr) {
+ return {};
+ }
+
+ const StringPiece idmap_data(
+ reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
+ static_cast<size_t>(idmap_asset->getLength()));
+ std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
+ if (loaded_idmap == nullptr) {
+ LOG(ERROR) << "failed to load IDMAP " << idmap_path;
+ return {};
+ }
+ return LoadImpl(loaded_idmap->OverlayApkPath(), std::move(idmap_asset), std::move(loaded_idmap),
+ system, false /*load_as_shared_library*/);
+}
+
+std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
+ unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
+ if (fd == -1) {
+ LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ const off64_t file_len = lseek64(fd, 0, SEEK_END);
+ if (file_len < 0) {
+ LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno);
+ return {};
+ }
+
+ std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>();
+ if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno);
+ return {};
+ }
+ return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
+ const std::string& path, std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
ATRACE_CALL();
::ZipArchiveHandle unmanaged_handle;
int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
if (result != 0) {
- LOG(ERROR) << ::ErrorCodeString(result);
+ LOG(ERROR) << "Failed to open APK '" << path << "' " << ::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);
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
- ::ZipString entry_name("resources.arsc");
+ // Find the resource table.
+ ::ZipString entry_name(kResourcesArsc.c_str());
::ZipEntry entry;
result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
if (result != 0) {
- LOG(ERROR) << ::ErrorCodeString(result);
- return {};
+ // There is no resources.arsc, so create an empty LoadedArsc and return.
+ loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+ return std::move(loaded_apk);
}
if (entry.method == kCompressDeflated) {
- LOG(WARNING) << "resources.arsc is compressed.";
+ LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
}
- loaded_apk->path_ = path;
- loaded_apk->resources_asset_ =
- loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
+ // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
+ loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
if (loaded_apk->resources_asset_ == nullptr) {
+ LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
+ // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
+ loaded_apk->idmap_asset_ = std::move(idmap_asset);
+
+ const StringPiece data(
+ reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+ loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ =
- LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
- loaded_apk->resources_asset_->getLength(), system, load_as_shared_library);
+ LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
if (loaded_apk->loaded_arsc_ == nullptr) {
+ LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
@@ -93,7 +155,6 @@
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
if (result != 0) {
- LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'";
return {};
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index f1f2e2d..d4d9dcb 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -35,7 +35,9 @@
namespace android {
-AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
+AssetManager2::AssetManager2() {
+ memset(&configuration_, 0, sizeof(configuration_));
+}
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
bool invalidate_caches) {
@@ -55,9 +57,9 @@
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
for (size_t i = 0; i < apk_assets_count; i++) {
- const ApkAssets* apk_asset = apk_assets_[i];
- for (const std::unique_ptr<const LoadedPackage>& package :
- apk_asset->GetLoadedArsc()->GetPackages()) {
+ const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
+
+ for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
@@ -312,7 +314,8 @@
cumulated_flags |= current_flags;
- if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) {
+ if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config) ||
+ (loaded_package->IsOverlay() && current_config.compare(best_config) == 0)) {
best_entry = current_entry;
best_config = current_config;
best_cookie = package_group.cookies_[i];
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
new file mode 100644
index 0000000..7c1ee5c
--- /dev/null
+++ b/libs/androidfw/Idmap.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 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/Idmap.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
+
+#include "androidfw/ResourceTypes.h"
+
+using ::android::base::StringPrintf;
+
+namespace android {
+
+constexpr static inline bool is_valid_package_id(uint16_t id) {
+ return id != 0 && id <= 255;
+}
+
+constexpr static inline bool is_valid_type_id(uint16_t id) {
+ // Type IDs and package IDs have the same constraints in the IDMAP.
+ return is_valid_package_id(id);
+}
+
+bool LoadedIdmap::Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
+ uint16_t* output_entry_id) {
+ if (input_entry_id < dtohs(header->entry_id_offset)) {
+ // After applying the offset, the entry is not present.
+ return false;
+ }
+
+ input_entry_id -= dtohs(header->entry_id_offset);
+ if (input_entry_id >= dtohs(header->entry_count)) {
+ // The entry is not present.
+ return false;
+ }
+
+ uint32_t result = dtohl(header->entries[input_entry_id]);
+ if (result == 0xffffffffu) {
+ return false;
+ }
+ *output_entry_id = static_cast<uint16_t>(result);
+ return true;
+}
+
+static bool is_word_aligned(const void* data) {
+ return (reinterpret_cast<uintptr_t>(data) & 0x03) == 0;
+}
+
+static bool IsValidIdmapHeader(const StringPiece& data) {
+ if (!is_word_aligned(data.data())) {
+ LOG(ERROR) << "Idmap header is not word aligned.";
+ return false;
+ }
+
+ if (data.size() < sizeof(Idmap_header)) {
+ LOG(ERROR) << "Idmap header is too small.";
+ return false;
+ }
+
+ const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data());
+ if (dtohl(header->magic) != kIdmapMagic) {
+ LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
+ dtohl(header->magic), kIdmapMagic);
+ return false;
+ }
+
+ if (dtohl(header->version) != kIdmapCurrentVersion) {
+ // We are strict about versions because files with this format are auto-generated and don't need
+ // backwards compatibility.
+ LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
+ dtohl(header->version), kIdmapCurrentVersion);
+ return false;
+ }
+
+ if (!is_valid_package_id(dtohs(header->target_package_id))) {
+ LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x",
+ dtohs(header->target_package_id));
+ return false;
+ }
+
+ if (dtohs(header->type_count) > 255) {
+ LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)",
+ (int)dtohs(header->type_count));
+ return false;
+ }
+ return true;
+}
+
+LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) {
+ size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
+ arraysize(header_->overlay_path));
+ overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
+}
+
+std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) {
+ ATRACE_CALL();
+ if (!IsValidIdmapHeader(idmap_data)) {
+ return {};
+ }
+
+ const Idmap_header* header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
+
+ // Can't use make_unique because LoadedImpl constructor is private.
+ std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(new LoadedIdmap(header));
+
+ const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + sizeof(*header);
+ size_t data_size = idmap_data.size() - sizeof(*header);
+
+ size_t type_maps_encountered = 0u;
+ while (data_size >= sizeof(IdmapEntry_header)) {
+ if (!is_word_aligned(data_ptr)) {
+ LOG(ERROR) << "Type mapping in Idmap is not word aligned";
+ return {};
+ }
+
+ // Validate the type IDs.
+ const IdmapEntry_header* entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr);
+ if (!is_valid_type_id(dtohs(entry_header->target_type_id)) || !is_valid_type_id(dtohs(entry_header->overlay_type_id))) {
+ LOG(ERROR) << StringPrintf("Invalid type map (0x%02x -> 0x%02x)",
+ dtohs(entry_header->target_type_id),
+ dtohs(entry_header->overlay_type_id));
+ return {};
+ }
+
+ // Make sure there is enough space for the entries declared in the header.
+ if ((data_size - sizeof(*entry_header)) / sizeof(uint32_t) <
+ static_cast<size_t>(dtohs(entry_header->entry_count))) {
+ LOG(ERROR) << StringPrintf("Idmap too small for the number of entries (%d)",
+ (int)dtohs(entry_header->entry_count));
+ return {};
+ }
+
+ // Only add a non-empty overlay.
+ if (dtohs(entry_header->entry_count != 0)) {
+ loaded_idmap->type_map_[static_cast<uint8_t>(dtohs(entry_header->overlay_type_id))] =
+ entry_header;
+ }
+
+ const size_t entry_size_bytes =
+ sizeof(*entry_header) + (dtohs(entry_header->entry_count) * sizeof(uint32_t));
+ data_ptr += entry_size_bytes;
+ data_size -= entry_size_bytes;
+ type_maps_encountered++;
+ }
+
+ // Verify that we parsed all the type maps.
+ if (type_maps_encountered != static_cast<size_t>(dtohs(header->type_count))) {
+ LOG(ERROR) << "Parsed " << type_maps_encountered << " type maps but expected "
+ << (int)dtohs(header->type_count);
+ return {};
+ }
+ return std::move(loaded_idmap);
+}
+
+uint8_t LoadedIdmap::TargetPackageId() const {
+ return static_cast<uint8_t>(dtohs(header_->target_package_id));
+}
+
+const IdmapEntry_header* LoadedIdmap::GetEntryMapForType(uint8_t type_id) const {
+ auto iter = type_map_.find(type_id);
+ if (iter != type_map_.end()) {
+ return iter->second;
+ }
+ return nullptr;
+}
+
+} // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index bd7b804..b62fc81 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -37,7 +37,7 @@
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
-using android::base::StringPrintf;
+using ::android::base::StringPrintf;
namespace android {
@@ -61,6 +61,10 @@
// and under which configurations it varies.
const ResTable_typeSpec* type_spec;
+ // Pointer to the mmapped data where the IDMAP mappings for this type
+ // exist. May be nullptr if no IDMAP exists.
+ const IdmapEntry_header* idmap_entries;
+
// The number of types that follow this struct.
// There is a type for each configuration
// that entries are defined for.
@@ -84,7 +88,10 @@
// the Type structs.
class TypeSpecPtrBuilder {
public:
- TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {}
+ explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header,
+ const IdmapEntry_header* idmap_header)
+ : header_(header), idmap_header_(idmap_header) {
+ }
void AddType(const ResTable_type* type) {
ResTable_config config;
@@ -99,6 +106,7 @@
}
TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
type_spec->type_spec = header_;
+ type_spec->idmap_entries = idmap_header_;
type_spec->type_count = types_.size();
memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
return TypeSpecPtr(type_spec);
@@ -108,6 +116,7 @@
DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
const ResTable_typeSpec* header_;
+ const IdmapEntry_header* idmap_header_;
std::vector<Type> types_;
};
@@ -125,6 +134,14 @@
return false;
}
+ // If there is an IDMAP supplied with this package, translate the entry ID.
+ if (ptr->idmap_entries != nullptr) {
+ if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
+ // There is no mapping, so the resource is not meant to be in this overlay package.
+ return false;
+ }
+ }
+
// Don't bother checking if the entry ID is larger than
// the number of entries.
if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
@@ -225,7 +242,7 @@
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) {
+ if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
LOG(ERROR) << "Entry offsets overlap actual entry data.";
return false;
}
@@ -269,13 +286,13 @@
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.";
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " 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.";
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << i << " is too large.";
return false;
}
@@ -286,13 +303,13 @@
size_t map_entries_start = offset + entry_size;
if (map_entries_start & 0x03) {
- LOG(ERROR) << "Map entries start at unaligned offset.";
+ LOG(ERROR) << "Map entries at index " << i << " 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.";
+ LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << i << ".";
return false;
}
@@ -300,7 +317,9 @@
} 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.";
+ LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << i << " for type "
+ << (int)header->id << " with config " << header->config.toString().string()
+ << ".";
return false;
}
@@ -308,12 +327,12 @@
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.";
+ LOG(ERROR) << "Res_value at index " << i << " 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.";
+ LOG(ERROR) << "Res_value size " << value_size << " at index " << i << " is too large.";
return false;
}
}
@@ -412,10 +431,13 @@
return 0u;
}
-std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
+std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap) {
ATRACE_CALL();
std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
+ // typeIdOffset was added at some point, but we still must recognize apps built before this
+ // was added.
constexpr size_t kMinPackageSize =
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
@@ -430,6 +452,12 @@
loaded_package->dynamic_ = true;
}
+ if (loaded_idmap != nullptr) {
+ // This is an overlay and so it needs to pretend to be the target package.
+ loaded_package->package_id_ = loaded_idmap->TargetPackageId();
+ loaded_package->overlay_ = true;
+ }
+
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -490,7 +518,16 @@
LOG(ERROR) << "Too many type configurations, overflow detected.";
return {};
}
- loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+ // We only add the type to the package if there is no IDMAP, or if the type is
+ // overlaying something.
+ if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
+ // If this is an overlay, insert it at the target type ID.
+ if (type_spec_ptr->idmap_entries != nullptr) {
+ last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
+ }
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+ }
types_builder = {};
last_type_idx = 0;
@@ -531,7 +568,15 @@
}
last_type_idx = type_spec->id - 1;
- types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec);
+
+ // If this is an overlay, associate the mapping of this type to the target type
+ // from the IDMAP.
+ const IdmapEntry_header* idmap_entry_header = nullptr;
+ if (loaded_idmap != nullptr) {
+ idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
+ }
+
+ types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
} break;
case RES_TABLE_TYPE_TYPE: {
@@ -608,7 +653,16 @@
LOG(ERROR) << "Too many type configurations, overflow detected.";
return {};
}
- loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+ // We only add the type to the package if there is no IDMAP, or if the type is
+ // overlaying something.
+ if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
+ // If this is an overlay, insert it at the target type ID.
+ if (type_spec_ptr->idmap_entries != nullptr) {
+ last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
+ }
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+ }
}
if (iter.HadError()) {
@@ -618,7 +672,8 @@
return loaded_package;
}
-bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
+bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
+ bool load_as_shared_library) {
ATRACE_CALL();
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
@@ -652,13 +707,13 @@
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.";
+ << " declared in the header.";
return false;
}
packages_seen++;
- std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk);
+ std::unique_ptr<LoadedPackage> loaded_package =
+ LoadedPackage::Load(child_chunk, loaded_idmap);
if (!loaded_package) {
return false;
}
@@ -684,7 +739,8 @@
return true;
}
-std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len, bool system,
+std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
+ const LoadedIdmap* loaded_idmap, bool system,
bool load_as_shared_library) {
ATRACE_CALL();
@@ -692,12 +748,12 @@
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
loaded_arsc->system_ = system;
- ChunkIterator iter(data, len);
+ ChunkIterator iter(data.data(), data.size());
while (iter.HasNext()) {
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
- if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) {
+ if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
return {};
}
break;
@@ -717,4 +773,8 @@
return std::move(loaded_arsc);
}
+std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() {
+ return std::unique_ptr<LoadedArsc>(new LoadedArsc());
+}
+
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 2e392d5..3a307fc 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -28,36 +28,63 @@
namespace android {
+class LoadedIdmap;
+
// Holds an APK.
class ApkAssets {
public:
+ // Creates an ApkAssets.
+ // If `system` is true, the package is marked as a system package, and allows some functions to
+ // filter out this package when computing what configurations/resources are available.
static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+
+ // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
+ // If `system` is true, the package is marked as a system package, and allows some functions to
+ // filter out this package when computing what configurations/resources are available.
static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const std::string& path,
bool system = false);
+ // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
+ // data.
+ // If `system` is true, the package is marked as a system package, and allows some functions to
+ // filter out this package when computing what configurations/resources are available.
+ static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
+ bool system = false);
+
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
bool ForEachFile(const std::string& path,
const std::function<void(const StringPiece&, FileType)>& f) const;
- inline const std::string& GetPath() const { return path_; }
+ inline const std::string& GetPath() const {
+ return path_;
+ }
- inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
+ // This is never nullptr.
+ inline const LoadedArsc* GetLoadedArsc() const {
+ return loaded_arsc_.get();
+ }
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
- static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path, bool system,
- bool load_as_shared_library);
+ static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path,
+ std::unique_ptr<Asset> idmap_asset,
+ std::unique_ptr<const LoadedIdmap> loaded_idmap,
+ bool system, bool load_as_shared_library);
- ApkAssets();
+ // Creates an Asset from any file on the file system.
+ static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
+ ApkAssets(void* unmanaged_handle, const std::string& path);
using ZipArchivePtr = std::unique_ptr<void, void(*)(void*)>;
ZipArchivePtr zip_handle_;
- std::string path_;
+ const std::string path_;
std::unique_ptr<Asset> resources_asset_;
+ std::unique_ptr<Asset> idmap_asset_;
std::unique_ptr<const LoadedArsc> loaded_arsc_;
};
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index fd94144..ef40a09 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -96,24 +96,29 @@
// new resource IDs.
bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
- inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; }
+ inline const std::vector<const ApkAssets*> GetApkAssets() const {
+ return apk_assets_;
+ }
// Returns the string pool for the given asset cookie.
- // Use the string pool returned here with a valid Res_value object of
- // type Res_value::TYPE_STRING.
+ // Use the string pool returned here with a valid Res_value object of type Res_value::TYPE_STRING.
const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
// Returns the DynamicRefTable for the given package ID.
+ // This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
// Returns the DynamicRefTable for the ApkAssets represented by the cookie.
+ // This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForCookie(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);
- inline const ResTable_config& GetConfiguration() const { return configuration_; }
+ inline const ResTable_config& GetConfiguration() const {
+ return configuration_;
+ }
// Returns all configurations for which there are resources defined. This includes resource
// configurations in all the ApkAssets set for this AssetManager.
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
new file mode 100644
index 0000000..fd02e6f
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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 IDMAP_H_
+#define IDMAP_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+struct Idmap_header;
+struct IdmapEntry_header;
+
+// Represents a loaded/parsed IDMAP for a Runtime Resource Overlay (RRO).
+// An RRO and its target APK have different resource IDs assigned to their resources. Overlaying
+// a resource is done by resource name. An IDMAP is a generated mapping between the resource IDs
+// of the RRO and the target APK for each resource with the same name.
+// A LoadedIdmap can be set alongside the overlay's LoadedArsc to allow the overlay ApkAssets to
+// masquerade as the target ApkAssets resources.
+class LoadedIdmap {
+ public:
+ // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
+ static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data);
+
+ // Performs a lookup of the expected entry ID for the given IDMAP entry header.
+ // Returns true if the mapping exists and fills `output_entry_id` with the result.
+ static bool Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
+ uint16_t* output_entry_id);
+
+ // Returns the package ID for which this overlay should apply.
+ uint8_t TargetPackageId() const;
+
+ // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
+ inline const std::string& OverlayApkPath() const {
+ return overlay_apk_path_;
+ }
+
+ // Returns the mapping of target entry ID to overlay entry ID for the given target type.
+ const IdmapEntry_header* GetEntryMapForType(uint8_t type_id) const;
+
+ protected:
+ // Exposed as protected so that tests can subclass and mock this class out.
+ LoadedIdmap() = default;
+
+ const Idmap_header* header_ = nullptr;
+ std::string overlay_apk_path_;
+ std::unordered_map<uint8_t, const IdmapEntry_header*> type_map_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
+
+ explicit LoadedIdmap(const Idmap_header* header);
+};
+
+} // namespace android
+
+#endif // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index f30b158..1f272e8 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -25,6 +25,7 @@
#include "androidfw/ByteBucketArray.h"
#include "androidfw/Chunk.h"
+#include "androidfw/Idmap.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -70,20 +71,36 @@
uint32_t* out_flags) const;
// Returns the string pool where type names are stored.
- inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; }
+ inline const ResStringPool* GetTypeStringPool() const {
+ return &type_string_pool_;
+ }
// Returns the string pool where the names of resource entries are stored.
- inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; }
+ inline const ResStringPool* GetKeyStringPool() const {
+ return &key_string_pool_;
+ }
- inline const std::string& GetPackageName() const { return package_name_; }
+ inline const std::string& GetPackageName() const {
+ return package_name_;
+ }
- inline int GetPackageId() const { return package_id_; }
+ inline int GetPackageId() const {
+ return package_id_;
+ }
// Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
- inline bool IsDynamic() const { return dynamic_; }
+ inline bool IsDynamic() const {
+ return dynamic_;
+ }
// Returns true if this package originates from a system provided resource.
- inline bool IsSystem() const { return system_; }
+ inline bool IsSystem() const {
+ return system_;
+ }
+
+ inline bool IsOverlay() const {
+ return overlay_;
+ }
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
@@ -111,7 +128,7 @@
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk);
+ static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk, const LoadedIdmap* loaded_idmap);
LoadedPackage() = default;
@@ -122,6 +139,7 @@
int type_id_offset_ = 0;
bool dynamic_ = false;
bool system_ = false;
+ bool overlay_ = false;
ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
@@ -137,14 +155,21 @@
// If `load_as_shared_library` is set to true, the application package (0x7f) is treated
// as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
// ID.
- static std::unique_ptr<const LoadedArsc> Load(const void* data, size_t len, bool system = false,
+ static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
+ const LoadedIdmap* loaded_idmap = nullptr,
+ bool system = false,
bool load_as_shared_library = false);
+ // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
+ static std::unique_ptr<const LoadedArsc> CreateEmpty();
+
~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_; }
+ inline const ResStringPool* GetStringPool() const {
+ return &global_string_pool_;
+ }
// Finds the resource with ID `resid` with the best value for configuration `config`.
// The parameter `out_entry` will be filled with the resulting resource entry.
@@ -157,7 +182,9 @@
const LoadedPackage* GetPackageForId(uint32_t resid) const;
// Returns true if this is a system provided resource.
- inline bool IsSystem() const { return system_; }
+ inline bool IsSystem() const {
+ return system_;
+ }
// Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
@@ -168,7 +195,7 @@
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
LoadedArsc() = default;
- bool LoadTable(const Chunk& chunk, bool load_as_shared_library);
+ bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
ResStringPool global_string_pool_;
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 66c66c2..8f858b6 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -38,6 +38,9 @@
namespace android {
+constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
+constexpr const static uint32_t kIdmapCurrentVersion = 0x00000001u;
+
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
* casting on raw data and expect char16_t to be exactly 16 bits.
@@ -1583,6 +1586,30 @@
uint16_t packageName[128];
};
+struct alignas(uint32_t) Idmap_header {
+ // Always 0x504D4449 ('IDMP')
+ uint32_t magic;
+
+ uint32_t version;
+
+ uint32_t target_crc32;
+ uint32_t overlay_crc32;
+
+ uint8_t target_path[256];
+ uint8_t overlay_path[256];
+
+ uint16_t target_package_id;
+ uint16_t type_count;
+} __attribute__((packed));
+
+struct alignas(uint32_t) IdmapEntry_header {
+ uint16_t target_type_id;
+ uint16_t overlay_type_id;
+ uint16_t entry_count;
+ uint16_t entry_id_offset;
+ uint32_t entries[0];
+} __attribute__((packed));
+
class AssetManager2;
/**
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index c85b0b9..2766ce1 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -17,12 +17,14 @@
#include "androidfw/ApkAssets.h"
#include "android-base/file.h"
+#include "android-base/test_utils.h"
#include "android-base/unique_fd.h"
+#include "androidfw/Util.h"
#include "TestHelpers.h"
#include "data/basic/R.h"
-using com::android::basic::R;
+using ::com::android::basic::R;
namespace android {
@@ -54,6 +56,37 @@
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
}
+TEST(ApkAssetsTest, LoadApkWithIdmap) {
+ std::string contents;
+ ResTable target_table;
+ const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
+ ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
+ ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+
+ ResTable overlay_table;
+ const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
+ ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
+ ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+
+ util::unique_cptr<void> idmap_data;
+ void* temp_data;
+ size_t idmap_len;
+
+ ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
+ overlay_path.c_str(), &temp_data, &idmap_len));
+ idmap_data.reset(temp_data);
+
+ TemporaryFile tf;
+ ASSERT_TRUE(base::WriteFully(tf.fd, idmap_data.get(), idmap_len));
+ close(tf.fd);
+
+ // Open something so that the destructor of TemporaryFile closes a valid fd.
+ tf.fd = open("/dev/null", O_WRONLY);
+
+ std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
+ ASSERT_NE(nullptr, loaded_overlay_apk);
+}
+
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index d12be18..9eb4a13 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -22,7 +22,7 @@
#include "TestHelpers.h"
#include "data/basic/R.h"
-using com::android::basic::R;
+using ::com::android::basic::R;
namespace android {
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 756869f..2b72d14 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -32,8 +32,7 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
&contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(contents.data(), contents.size());
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
@@ -59,8 +58,7 @@
ASSERT_TRUE(
ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(contents.data(), contents.size());
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
ResTable_config desired_config;
@@ -82,8 +80,7 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
&contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(contents.data(), contents.size());
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -104,8 +101,7 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk",
"resources.arsc", &contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(contents.data(), contents.size());
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -132,8 +128,9 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk",
"resources.arsc", &contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(
- contents.data(), contents.size(), false /*system*/, true /*load_as_shared_library*/);
+ std::unique_ptr<const LoadedArsc> loaded_arsc =
+ LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
+ true /*load_as_shared_library*/);
ASSERT_NE(nullptr, loaded_arsc);
const auto& packages = loaded_arsc->GetPackages();
@@ -147,8 +144,7 @@
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
&contents));
- std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(contents.data(), contents.size());
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
ASSERT_NE(nullptr, loaded_arsc);
ResTable_config desired_config;
@@ -174,6 +170,54 @@
EXPECT_EQ(std::string("string"), type_name);
}
+class MockLoadedIdmap : public LoadedIdmap {
+ public:
+ MockLoadedIdmap() : LoadedIdmap() {
+ local_header_.magic = kIdmapMagic;
+ local_header_.version = kIdmapCurrentVersion;
+ local_header_.target_package_id = 0x08;
+ local_header_.type_count = 1;
+ header_ = &local_header_;
+
+ entry_header = util::unique_cptr<IdmapEntry_header>(
+ (IdmapEntry_header*)::malloc(sizeof(IdmapEntry_header) + sizeof(uint32_t)));
+ entry_header->target_type_id = 0x03;
+ entry_header->overlay_type_id = 0x02;
+ entry_header->entry_id_offset = 1;
+ entry_header->entry_count = 1;
+ entry_header->entries[0] = 0x00000000u;
+ type_map_[entry_header->overlay_type_id] = entry_header.get();
+ }
+
+ private:
+ Idmap_header local_header_;
+ util::unique_cptr<IdmapEntry_header> entry_header;
+};
+
+TEST(LoadedArscTest, LoadOverlay) {
+ std::string contents, overlay_contents;
+ ASSERT_TRUE(
+ ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
+ &overlay_contents));
+
+ MockLoadedIdmap loaded_idmap;
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc =
+ LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
+ ASSERT_NE(nullptr, loaded_arsc);
+
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+
+ LoadedArscEntry entry;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ASSERT_TRUE(
+ loaded_arsc->FindEntry(0x08030001u, desired_config, &entry, &selected_config, &flags));
+}
+
// 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.