Add support for ARSC resource table compact encoding

Add support for a ResTable type chunk encoded using FLAG_OFFSET16 and a
ResTable entry type chunk encoded using FLAG_COMPACT.

Test: atest BackupFrameworksServicesRoboTests
Test: atest ClockworkSettingsRoboTests
Test: atest PixelSetupWizardRoboTests

Bug: 275572186
Change-Id: I4e3e6139c76f016dcdec645fa1d1dabd095bde14
diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
index 33bf96a..0f5affb 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
@@ -760,7 +760,7 @@
     out_entry_.type_flags = type_flags;
     out_entry_.type_string_ref = new StringPoolRef(best_package.GetTypeStringPool(), best_type.id - 1);
     out_entry_.entry_string_ref =
-        new StringPoolRef(best_package.GetKeyStringPool(), best_entry.key.index);
+        new StringPoolRef(best_package.GetKeyStringPool(), best_entry.getKeyIndex());
     out_entry_.dynamic_ref_table = package_group.dynamic_ref_table;
     out_entry.set(out_entry_);
     return best_cookie;
diff --git a/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java b/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
index b79f70a..0c41714 100644
--- a/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
+++ b/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
@@ -238,7 +238,9 @@
     // Make sure that there is enough room for the entry offsets.
     int offsets_offset = dtohs(header.header.headerSize);
     int entries_offset = dtohl(header.entriesStart);
-    int offsets_length = 4 * entry_count;
+    int offsets_length = isTruthy(header.flags & ResTable_type.FLAG_OFFSET16)
+                                    ? 2 * entry_count
+                                    : 4 * entry_count;
 
     if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
       logError("RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.");
@@ -285,7 +287,7 @@
     //       reinterpret_cast<uint8_t*>(type) + entry_offset);
     ResTable_entry entry = new ResTable_entry(type.myBuf(), type.myOffset() + entry_offset);
 
-    int entry_size = dtohs(entry.size);
+    int entry_size = entry.isCompact() ? 8 : dtohs(entry.size);
     // if (entry_size < sizeof(*entry)) {
     if (entry_size < ResTable_entry.SIZEOF) {
       logError("ResTable_entry size " + entry_size + " at offset " + entry_offset
@@ -299,6 +301,10 @@
       return false;
     }
 
+    if (entry.isCompact()) {
+      return true;
+    }
+
     if (entry_size < ResTable_map_entry.BASE_SIZEOF) {
       // There needs to be room for one Res_value struct.
       if (entry_offset + entry_size > chunk_size - Res_value.SIZEOF) {
@@ -309,8 +315,7 @@
 
       // Res_value value =
       //       reinterpret_cast<Res_value*>(reinterpret_cast<uint8_t*>(entry) + entry_size);
-      Res_value value =
-          new Res_value(entry.myBuf(), entry.myOffset() + ResTable_entry.SIZEOF);
+      Res_value value = entry.getResValue();
       int value_size = dtohs(value.size);
       if (value_size < Res_value.SIZEOF) {
         logError("Res_value at offset " + entry_offset + " is too small.");
@@ -532,7 +537,7 @@
             ResTable_entry entry =
                 new ResTable_entry(type.myBuf(), type.myOffset() +
                     dtohl(type.entriesStart) + offset);
-            if (dtohl(entry.key.index) == key_idx) {
+            if (dtohl(entry.getKeyIndex()) == key_idx) {
               // The package ID will be overridden by the caller (due to runtime assignment of package
               // IDs for shared libraries).
               return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), (short) entry_idx);
diff --git a/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
index 7e802e1..5438f33 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
@@ -340,7 +340,7 @@
     }
 
     public Res_value(byte dataType, int data) {
-      this.size = 0;
+      this.size = SIZEOF;
 //      this.res0 = 0;
       this.dataType = dataType;
       this.data = data;
@@ -1167,6 +1167,11 @@
     // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
     // platforms.
     public static final int FLAG_SPARSE = 0x01;
+
+    // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
+    // An 16-bit offset of 0xffffu means a NO_ENTRY
+    public static final int FLAG_OFFSET16 = 0x02;
+
     //    };
     final byte flags;
 
@@ -1208,13 +1213,21 @@
     int entryOffset(int entryIndex) {
       ByteBuffer byteBuffer = myBuf();
       int offset = myOffset();
-
+      boolean isOffset16 = (flags & ResTable_type.FLAG_OFFSET16) == ResTable_type.FLAG_OFFSET16;
+      if (isOffset16) {
+        short off16 = byteBuffer.getShort(offset + header.headerSize + entryIndex * 2);
+        if (off16 == -1) {
+          return -1;
+        }
+        return dtohs(off16) == 0xffff ? ResTable_type.NO_ENTRY : dtohs(off16) * 4;
+      } else {
+        return byteBuffer.getInt(offset + header.headerSize + entryIndex * 4);
+      }
       // from ResTable cpp:
 //            const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
 //            reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
 //
 //        uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
-      return byteBuffer.getInt(offset + header.headerSize + entryIndex * 4);
     }
 
     private int entryNameIndex(int entryIndex) {
@@ -1282,7 +1295,7 @@
     public static final int SIZEOF = 4 + ResStringPool_ref.SIZEOF;
 
     // Number of bytes in this structure.
-    final short size;
+    short size;
 
     //enum {
     // If set, this is a complex entry, holding a set of name/value
@@ -1295,18 +1308,41 @@
     // resources of the same name/type. This is only useful during
     // linking with other resource tables.
     public static final int FLAG_WEAK = 0x0004;
+
+    public static final int FLAG_COMPACT = 0x0008;
     //    };
     final short flags;
 
     // Reference into ResTable_package::keyStrings identifying this entry.
-    final ResStringPool_ref key;
+    ResStringPool_ref key;
+
+    int compactData;
+    short compactKey;
 
     ResTable_entry(ByteBuffer buf, int offset) {
       super(buf, offset);
 
-      size = buf.getShort(offset);
       flags = buf.getShort(offset + 2);
-      key = new ResStringPool_ref(buf, offset + 4);
+
+      if (isCompact()) {
+        compactKey = buf.getShort(offset);
+        compactData = buf.getInt(offset + 4);
+      } else {
+        size = buf.getShort(offset);
+        key = new ResStringPool_ref(buf, offset + 4);
+      }
+    }
+
+    public boolean isCompact() {
+      return (flags & FLAG_COMPACT) == FLAG_COMPACT;
+    }
+
+    public int getKeyIndex() {
+      if (isCompact()) {
+        return dtohs(compactKey);
+      } else {
+        return key.index;
+      }
     }
 
     public Res_value getResValue() {
@@ -1315,7 +1351,12 @@
       // final Res_value device_value = reinterpret_cast<final Res_value>(
       //     reinterpret_cast<final byte*>(entry) + dtohs(entry.size));
 
-      return new Res_value(myBuf(), myOffset() + dtohs(size));
+      if (isCompact()) {
+        byte type = (byte)(dtohs(flags) >> 8);
+        return new Res_value((byte)(dtohs(flags) >> 8), compactData);
+      } else {
+       return new Res_value(myBuf(), myOffset() + dtohs(size));
+      }
     }
   }