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