androidfw: Add support for compact resource entries
Bug: 237583012
Given the large number of simple resources such as strings in
Android resources, their ResTable_entry and Res_value can be
encoded together in a compact way. This allows a significant
saving in both storage and memory footprint.
The basic observations for simple resources are:
* ResTable_entry.size will always be sizeof(ResTable_entry)
unless it's a complex entry
* ResTable_entry.key is unlikely to exceed 16-bit
* ResTable_entry.flags only uses 3 bits for now
* Res_value.size will always be sizeof(Res_value)
Given the above, we could well encode the information into
a compact/compatible structure.
struct compact {
uint16_t key;
uint16_t flags;
uint32_t data;
};
The layout of this structure will allow maximum backward
compatibility. e.g. the flags will be at the same offset,
and a
`dtohs((ResTable_entry *)entry->flags) & FLAG_COMPACT`
would tell if this entry is a compact one or not. For a
compact entry:
struct compact *entry;
entry_size == sizeof(*entry)
entry_key == static_cast<uint32_t>(dtohs(entry->key))
entry_flags == dtohs(entry->flags) & 0xff // low 8-bit
data_type == dtohs(entry->flags) >> 8 // high 8-bit
data_size == sizeof(Res_value)
data_value == dtohl(entry->data)
To allow minimum code change and backward compatibility,
we change 'struct ResTable_entry' to 'union ResTable_entry',
with an anonymous structure inside that's fully backward
compatible. Thus, any existing reference such as:
ResTable_entry *entry = ...
if (dtohs(entry->flags) & ResTable_entry::FLAG_COMPLEX) ...
would still work.
However, special care needs to be taken after an entry is
obtained, and when value needs to be extracted.
A compact entry will not encode a complex value, and hence
complex entries/values are handled the same way.
Change-Id: I15d97a4f5e85fab28c075496f7f0cf6b1fcd73e3
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 1381bdd..06ffb72 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -43,28 +43,19 @@
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
- const uint16_t entry_size = dtohs(table_entry->size);
+ const uint16_t entry_size = table_entry->size();
// Check if the entry represents a bag value.
- if (entry_size >= sizeof(ResTable_map_entry) &&
- (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
- const auto map_entry = table_entry.convert<ResTable_map_entry>();
- if (!map_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- return map_entry.verified();
+ if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+ return table_entry.convert<ResTable_map_entry>().verified();
}
- // The entry represents a non-bag value.
- const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
- if (!entry_value) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- Res_value value;
- value.copyFrom_dtoh(entry_value.value());
- return value;
+ return table_entry->value();
}
} // namespace
@@ -814,17 +805,12 @@
return base::unexpected(std::nullopt);
}
- auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
- if (!best_entry_result.has_value()) {
- return base::unexpected(best_entry_result.error());
+ auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (!best_entry_verified.has_value()) {
+ return base::unexpected(best_entry_verified.error());
}
- const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
- if (!best_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
-
- const auto entry = GetEntryValue(best_entry.verified());
+ const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
@@ -837,7 +823,7 @@
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
- best_entry->key.index),
+ (*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 5b69cca..e78f91e 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -107,8 +107,8 @@
return true;
}
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
- incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +136,7 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- const size_t entry_size = dtohs(entry->size);
+ const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
@@ -149,6 +149,11 @@
return base::unexpected(std::nullopt);
}
+ // If entry is compact, value is already encoded, and a compact entry
+ // cannot be a map_entry, we are done verifying
+ if (entry->is_compact())
+ return entry.verified();
+
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +197,7 @@
return base::unexpected(std::nullopt);
}
}
- return {};
+ return entry.verified();
}
LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +233,7 @@
entryIndex_);
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
@@ -297,13 +302,14 @@
return value;
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+ uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
- return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+ return valid;
}
base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -400,7 +406,7 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+ if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 46fbe7c..aac52b4 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4415,20 +4415,14 @@
return err;
}
- if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
- outValue->size = dtohs(value->size);
- outValue->res0 = value->res0;
- outValue->dataType = value->dataType;
- outValue->data = dtohl(value->data);
+ *outValue = entry.entry->value();
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
@@ -4619,11 +4613,10 @@
return err;
}
- const uint16_t entrySize = dtohs(entry.entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+ const uint16_t entrySize = entry.entry->size();
+ const ResTable_map_entry* map_entry = entry.entry->map_entry();
+ const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+ const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
size_t N = count;
@@ -4687,7 +4680,7 @@
// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
- + dtohs(entry.entry->size);
+ + entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
@@ -5065,7 +5058,7 @@
continue;
}
- if (dtohl(entry->key.index) == (size_t) *ei) {
+ if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
@@ -6579,8 +6572,8 @@
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ if (entry->size() < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}
@@ -6591,7 +6584,7 @@
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
- outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
@@ -7662,7 +7655,7 @@
continue;
}
- uintptr_t esize = dtohs(ent->size);
+ uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
@@ -7674,30 +7667,27 @@
}
const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
+ const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (bagPtr) {
printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
} else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
+ value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");
if (inclValues) {
- if (valuePtr != NULL) {
+ if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
- } else if (bagPtr != NULL) {
+ } else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa19..2c39a81 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -91,11 +91,11 @@
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
- } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
- } else if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ } else if (entry->size() < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e459639..79d96282 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -166,14 +166,14 @@
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 24628cd..42d8cbe 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -26,6 +26,7 @@
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -1490,6 +1491,8 @@
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
+struct ResTable_map_entry;
+
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
@@ -1497,12 +1500,11 @@
* * A Res_value structure, if FLAG_COMPLEX is -not- set.
* * An array of ResTable_map structures, if FLAG_COMPLEX is set.
* These supply a set of name/value mappings of data.
+ * * If FLAG_COMPACT is set, this entry is a compact entry for
+ * simple values only
*/
-struct ResTable_entry
+union ResTable_entry
{
- // Number of bytes in this structure.
- uint16_t size;
-
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
@@ -1514,18 +1516,91 @@
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004,
+ // If set, this is a compact entry with data type and value directly
+ // encoded in the this entry, see ResTable_entry::compact
+ FLAG_COMPACT = 0x0008,
};
- uint16_t flags;
-
- // Reference into ResTable_package::keyStrings identifying this entry.
- struct ResStringPool_ref key;
+
+ struct Full {
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+ } full;
+
+ /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+ * offset as a normal entry. This is only for simple data values where
+ *
+ * - size for entry or value can be inferred (both being 8 bytes).
+ * - key index is encoded in 16-bit
+ * - dataType is encoded as the higher 8-bit of flags
+ * - data is encoded directly in this entry
+ */
+ struct Compact {
+ uint16_t key;
+ uint16_t flags;
+ uint32_t data;
+ } compact;
+
+ uint16_t flags() const { return dtohs(full.flags); };
+ bool is_compact() const { return flags() & FLAG_COMPACT; }
+ bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+ size_t size() const {
+ return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+ }
+
+ uint32_t key() const {
+ return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+ }
+
+ /* Always verify the memory associated with this entry and its value
+ * before calling value() or map_entry()
+ */
+ Res_value value() const {
+ Res_value v;
+ if (is_compact()) {
+ v.size = sizeof(Res_value);
+ v.res0 = 0;
+ v.data = dtohl(this->compact.data);
+ v.dataType = dtohs(compact.flags) >> 8;
+ } else {
+ auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+ auto value = reinterpret_cast<const Res_value*>(vaddr);
+ v.size = dtohs(value->size);
+ v.res0 = value->res0;
+ v.data = dtohl(value->data);
+ v.dataType = value->dataType;
+ }
+ return v;
+ }
+
+ const ResTable_map_entry* map_entry() const {
+ return is_complex() && !is_compact() ?
+ reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+ }
};
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
/**
* Extended form of a ResTable_entry for map entries, defining a parent map
* resource from which to inherit values.
*/
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5..aba3ab3 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -37,8 +37,8 @@
offsets[0] = 0;
ResTable_entry e1;
memset(&e1, 0, sizeof(e1));
- e1.size = sizeof(e1);
- e1.key.index = 0;
+ e1.full.size = sizeof(e1);
+ e1.full.key.index = 0;
t.header.size += sizeof(e1);
Res_value v1;
@@ -50,8 +50,8 @@
offsets[2] = sizeof(e1) + sizeof(v1);
ResTable_entry e2;
memset(&e2, 0, sizeof(e2));
- e2.size = sizeof(e2);
- e2.key.index = 1;
+ e2.full.size = sizeof(e2);
+ e2.full.key.index = 1;
t.header.size += sizeof(e2);
Res_value v2;
@@ -83,7 +83,7 @@
TypeVariant::iterator iter = v.beginEntries();
ASSERT_EQ(uint32_t(0), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key.index);
+ ASSERT_EQ(uint32_t(0), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;
@@ -96,7 +96,7 @@
ASSERT_EQ(uint32_t(2), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(1), iter->key.index);
+ ASSERT_EQ(uint32_t(1), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 47750fc..4e597fb 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3743,15 +3743,15 @@
size_t amt = 0;
ResTable_entry header;
memset(&header, 0, sizeof(header));
- header.size = htods(sizeof(header));
+ header.full.size = htods(sizeof(header));
const type ty = mType;
if (ty == TYPE_BAG) {
- header.flags |= htods(header.FLAG_COMPLEX);
+ header.full.flags |= htods(header.FLAG_COMPLEX);
}
if (isPublic) {
- header.flags |= htods(header.FLAG_PUBLIC);
+ header.full.flags |= htods(header.FLAG_PUBLIC);
}
- header.key.index = htodl(mNameIndex);
+ header.full.key.index = htodl(mNameIndex);
if (ty != TYPE_BAG) {
status_t err = data->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index f9e52b4..df87889 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -687,32 +687,32 @@
continue;
}
- printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
- : "[ResTable_entry]");
+ if (entry->is_complex()) {
+ printer_->Print("[ResTable_map_entry]");
+ } else if (entry->is_compact()) {
+ printer_->Print("[ResTable_entry_compact]");
+ } else {
+ printer_->Print("[ResTable_entry]");
+ }
+
printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index))
- .c_str()));
- printer_->Print(
- StringPrintf(" keyIndex: %u", android::util::DeviceToHost32(entry->key.index)));
- printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(entry->size)));
- printer_->Print(StringPrintf(" flags: 0x%04x", android::util::DeviceToHost32(entry->flags)));
+ " name: %s", android::util::GetString(key_pool_, entry->key()).c_str()));
+ printer_->Print(StringPrintf(" keyIndex: %u", entry->key()));
+ printer_->Print(StringPrintf(" size: %zu", entry->size()));
+ printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags()));
printer_->Indent();
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- auto map_entry = (const ResTable_map_entry*)entry;
- printer_->Print(
- StringPrintf(" count: 0x%04x", android::util::DeviceToHost32(map_entry->count)));
+ if (auto map_entry = entry->map_entry()) {
+ uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count);
+ printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count));
printer_->Print(StringPrintf(" parent: 0x%08x\n",
android::util::DeviceToHost32(map_entry->parent.ident)));
// Print the name and value mappings
- auto maps = (const ResTable_map*)((const uint8_t*)entry +
- android::util::DeviceToHost32(entry->size));
- for (size_t i = 0, count = android::util::DeviceToHost32(map_entry->count); i < count;
- i++) {
+ auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size());
+ for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
printer_->Print(StringPrintf(
@@ -725,9 +725,8 @@
printer_->Print("\n");
// Print the value of the entry
- auto value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- PrintResValue(value, config, type);
+ Res_value value = entry->value();
+ PrintResValue(&value, config, type);
}
printer_->Undent();
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index cffcdf2..5fdfb66 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -159,6 +159,9 @@
AddOptionalSwitch("--enable-sparse-encoding",
"This decreases APK size at the cost of resource retrieval performance.",
&options_.use_sparse_encoding);
+ AddOptionalSwitch("--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &options_.table_flattener_options.use_compact_entries);
AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
&legacy_x_flag_);
AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index d9e379d..8291862 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -384,21 +384,16 @@
continue;
}
- const ResourceName name(
- package->name, *parsed_type,
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index)));
+ const ResourceName name(package->name, *parsed_type,
+ android::util::GetString(key_pool_, entry->key()));
const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resource_value;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
+ if (auto mapEntry = entry->map_entry()) {
// TODO(adamlesinski): Check that the entry count is valid.
resource_value = ParseMapEntry(name, config, mapEntry);
} else {
- const Res_value* value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- resource_value = ParseValue(name, config, *value);
+ resource_value = ParseValue(name, config, entry->value());
}
if (!resource_value) {
@@ -419,7 +414,7 @@
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
.SetAllowMangled(true);
- if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
Visibility visibility{Visibility::Level::kPublic};
auto spec_flags = entry_type_spec_flags_.find(res_id);
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 8832c24..9dc205f 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -24,11 +24,6 @@
namespace aapt {
-using android::BigBuffer;
-using android::Res_value;
-using android::ResTable_entry;
-using android::ResTable_map;
-
struct less_style_entries {
bool operator()(const Style::Entry* a, const Style::Entry* b) const {
if (a->key.id) {
@@ -189,26 +184,40 @@
};
template <typename T>
-void WriteEntry(const FlatEntry* entry, T* out_result) {
+void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
"T must be ResTable_entry or ResTable_entry_ext");
ResTable_entry* out_entry = (ResTable_entry*)out_result;
+ uint16_t flags = 0;
+
if (entry->entry->visibility.level == Visibility::Level::kPublic) {
- out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+ flags |= ResTable_entry::FLAG_PUBLIC;
}
if (entry->value->IsWeak()) {
- out_entry->flags |= ResTable_entry::FLAG_WEAK;
+ flags |= ResTable_entry::FLAG_WEAK;
}
if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
- out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+ flags |= ResTable_entry::FLAG_COMPLEX;
}
- out_entry->flags = android::util::HostToDevice16(out_entry->flags);
- out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
- out_entry->size = android::util::HostToDevice16(sizeof(T));
+ if (!compact) {
+ out_entry->full.flags = android::util::HostToDevice16(flags);
+ out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key);
+ out_entry->full.size = android::util::HostToDevice16(sizeof(T));
+ } else {
+ Res_value value;
+ CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit";
+ CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry";
+ CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed";
+
+ flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8);
+ out_entry->compact.flags = android::util::HostToDevice16(flags);
+ out_entry->compact.key = android::util::HostToDevice16(entry->entry_key);
+ out_entry->compact.data = value.data;
+ }
}
int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
@@ -222,57 +231,26 @@
return offset;
}
-void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
- static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
- "ResEntryValuePair must not have padding between entry and value.");
+template <bool compact_entry, typename T>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) {
+ int32_t offset = buffer->size();
+ T* out_entry = buffer->NextBlock<T>();
- WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
-
- CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
- out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
-}
-
-int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t offset = entries_buffer_->size();
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
- return offset;
-}
-
-std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
- return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
-}
-
-bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
- const ResEntryValuePairRef& b) const {
- return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t initial_offset = entries_buffer_->size();
-
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
-
- auto ref = ResEntryValuePairRef{*out_pair};
- auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
- if (inserted) {
- // If inserted just return a new offset as this is a first time we store
- // this entry.
- return initial_offset;
+ if constexpr (compact_entry) {
+ WriteEntry(item_entry, out_entry, true);
+ } else {
+ WriteEntry(item_entry, &out_entry->entry);
+ CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed";
+ out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
}
- // If not inserted this means that this is a duplicate, backup allocated block to the buffer
- // and return offset of previously stored entry.
- entries_buffer_->BackUp(sizeof(ResEntryValuePair));
- return it->second;
+ return {offset, out_entry};
}
-} // namespace aapt
\ No newline at end of file
+// explicitly specialize both versions
+template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index a36ceec..c11598e 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -27,6 +27,11 @@
namespace aapt {
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
struct FlatEntry {
const ResourceTableEntryView* entry;
const Value* value;
@@ -39,28 +44,42 @@
// We introduce this structure for ResEntryWriter to a have single allocation using
// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
struct ResEntryValuePair {
- android::ResTable_entry entry;
- android::Res_value value;
+ ResTable_entry entry;
+ Res_value value;
};
-// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
-// Allows access to memory address where ResEntryValuePair is stored.
-union ResEntryValuePairRef {
- const std::reference_wrapper<const ResEntryValuePair> pair;
+static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+ "ResEntryValuePair must not have padding between entry and value.");
+
+template <bool compact>
+using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>;
+
+// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValue is stored.
+template <bool compact>
+union ResEntryValueRef {
+ using T = ResEntryValue<compact>;
+ const std::reference_wrapper<const T> ref;
const u_char* ptr;
- explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+ explicit ResEntryValueRef(const T& rev) : ref(rev) {
}
};
-// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
-struct ResEntryValuePairContentHasher {
- std::size_t operator()(const ResEntryValuePairRef& ref) const;
+// Hasher which computes hash of ResEntryValue using its bytes representation in memory.
+struct ResEntryValueContentHasher {
+ template <typename R>
+ std::size_t operator()(const R& ref) const {
+ return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T));
+ }
};
// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
-struct ResEntryValuePairContentEqualTo {
- bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+struct ResEntryValueContentEqualTo {
+ template <typename R>
+ bool operator()(const R& a, const R& b) const {
+ return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0;
+ }
};
// Base class that allows to write FlatEntries into entries_buffer.
@@ -79,9 +98,9 @@
}
protected:
- ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+ ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
}
- android::BigBuffer* entries_buffer_;
+ BigBuffer* entries_buffer_;
virtual int32_t WriteItem(const FlatEntry* entry) = 0;
@@ -91,18 +110,29 @@
DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
};
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer);
+
+template <bool compact_entry, typename T=ResEntryValue<compact_entry>>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer);
+
// ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
// Next entry is always written right after previous one in the buffer.
+template <bool compact_entry = false>
class SequentialResEntryWriter : public ResEntryWriter {
public:
- explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit SequentialResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~SequentialResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+ return result.first;
+ }
- int32_t WriteMap(const FlatEntry* entry) override;
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
@@ -111,25 +141,44 @@
// ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
// Next entry is written into buffer only if there is no entry with the same bytes representation
// in memory written before. Otherwise returns offset of already written entry.
+template <bool compact_entry = false>
class DeduplicateItemsResEntryWriter : public ResEntryWriter {
public:
- explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~DeduplicateItemsResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
- int32_t WriteMap(const FlatEntry* entry) override;
+ auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset});
+ if (inserted) {
+ // If inserted just return a new offset as this is a first time we store
+ // this entry
+ return offset;
+ }
+
+ // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+ // and return offset of previously stored entry
+ entries_buffer_->BackUp(sizeof(*out_entry));
+ return it->second;
+ }
+
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
- std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
- ResEntryValuePairContentEqualTo>
- entry_offsets;
+ using Ref = ResEntryValueRef<compact_entry>;
+ using Map = std::unordered_map<Ref, int32_t,
+ ResEntryValueContentHasher,
+ ResEntryValueContentEqualTo>;
+ Map entry_offsets;
};
} // namespace aapt
-#endif
\ No newline at end of file
+#endif
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
index 56ca133..4cb17c3 100644
--- a/tools/aapt2/format/binary/ResEntryWriter_test.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -56,14 +56,28 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
- 2 * sizeof(ResEntryValuePair)};
- EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+ 2 * sizeof(ResEntryValuePair)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
+ 2 * sizeof(ResTable_entry)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -83,13 +97,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
@@ -100,13 +127,26 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, 0, 0};
- EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -126,13 +166,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
-};
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+ };
} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 318b8b6..c431730 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -67,7 +67,9 @@
public:
PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
const std::map<size_t, std::string>* shared_libs,
- SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+ SparseEntriesMode sparse_entries,
+ bool compact_entries,
+ bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions,
bool deduplicate_entry_values)
: context_(context),
@@ -75,6 +77,7 @@
package_(package),
shared_libs_(shared_libs),
sparse_entries_(sparse_entries),
+ compact_entries_(compact_entries),
collapse_key_stringpool_(collapse_key_stringpool),
name_collapse_exemptions_(name_collapse_exemptions),
deduplicate_entry_values_(deduplicate_entry_values) {
@@ -135,6 +138,33 @@
private:
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
+ // Use compact entries only if
+ // 1) it is enabled, and that
+ // 2) the entries will be accessed on platforms U+, and
+ // 3) all entry keys can be encoded in 16 bits
+ bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
+ return compact_entries_ &&
+ (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
+ std::none_of(entries->cbegin(), entries->cend(),
+ [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+ }
+
+ std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
+ if (dedup) {
+ if (compact) {
+ return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer);
+ }
+ } else {
+ if (compact) {
+ return std::make_unique<SequentialResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<SequentialResEntryWriter<false>>(buffer);
+ }
+ }
+ }
+
bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
@@ -150,15 +180,11 @@
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
+ bool compact_entry = UseCompactEntries(config, entries);
+
android::BigBuffer values_buffer(512);
- std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
- writer_variant;
- ResEntryWriter* res_entry_writer;
- if (deduplicate_entry_values_) {
- res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
- } else {
- res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
- }
+ auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_,
+ compact_entry, &values_buffer);
for (FlatEntry& flat_entry : *entries) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
@@ -512,6 +538,7 @@
const ResourceTablePackageView package_;
const std::map<size_t, std::string>* shared_libs_;
SparseEntriesMode sparse_entries_;
+ bool compact_entries_;
android::StringPool type_pool_;
android::StringPool key_pool_;
bool collapse_key_stringpool_;
@@ -568,7 +595,9 @@
}
PackageFlattener flattener(context, package, &table->included_packages_,
- options_.sparse_entries, options_.collapse_key_stringpool,
+ options_.sparse_entries,
+ options_.use_compact_entries,
+ options_.collapse_key_stringpool,
options_.name_collapse_exemptions,
options_.deduplicate_entry_values);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 6151b7e..35254ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -44,6 +44,9 @@
// as a sparse map of entry ID and offset to actual data.
SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
+ // When true, use compact entries for simple data
+ bool use_compact_entries = false;
+
// When true, the key string pool in the final ResTable
// is collapsed to a single entry. All resource entries
// have name indices that point to this single value