xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.res.android;
2 
3 import static java.nio.charset.StandardCharsets.UTF_8;
4 import static org.robolectric.res.android.Errors.BAD_TYPE;
5 import static org.robolectric.res.android.Errors.NO_ERROR;
6 import static org.robolectric.res.android.Util.ALOGW;
7 import static org.robolectric.res.android.Util.SIZEOF_INT;
8 import static org.robolectric.res.android.Util.SIZEOF_SHORT;
9 import static org.robolectric.res.android.Util.dtohl;
10 import static org.robolectric.res.android.Util.dtohs;
11 import static org.robolectric.res.android.Util.isTruthy;
12 
13 import java.nio.Buffer;
14 import java.nio.ByteBuffer;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer;
20 
21 // transliterated from
22 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp
23 //   and
24 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h
25 public class ResourceTypes {
26   public static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
27   public static final String AUTO_NS = "http://schemas.android.com/apk/res-auto";
28 
29   static final int kIdmapMagic = 0x504D4449;
30   static final int kIdmapCurrentVersion = 0x00000001;
31 
validate_chunk(ResChunk_header chunk, int minSize, int dataLen, String name)32   static int validate_chunk(ResChunk_header chunk, int minSize, int dataLen, String name) {
33     final short headerSize = dtohs(chunk.headerSize);
34     final int size = dtohl(chunk.size);
35 
36     if (headerSize >= minSize) {
37       if (headerSize <= size) {
38         if (((headerSize | size) & 0x3) == 0) {
39           if (size <= dataLen) {
40             return NO_ERROR;
41           }
42           ALOGW(
43               "%s data size 0x%x extends beyond resource end.",
44               name, size /*, (dataEnd-((const uint8_t*)chunk))*/);
45           return BAD_TYPE;
46         }
47         ALOGW(
48             "%s size 0x%x or headerSize 0x%x is not on an integer boundary.",
49             name, (int) size, (int) headerSize);
50         return BAD_TYPE;
51       }
52       ALOGW("%s size 0x%x is smaller than header size 0x%x.", name, size, headerSize);
53       return BAD_TYPE;
54     }
55     ALOGW("%s header size 0x%04x is too small.", name, headerSize);
56     return BAD_TYPE;
57   }
58 
59   static class WithOffset {
60     private final ByteBuffer buf;
61     private final int offset;
62 
WithOffset(ByteBuffer buf, int offset)63     WithOffset(ByteBuffer buf, int offset) {
64       this.buf = buf;
65       this.offset = offset;
66     }
67 
myBuf()68     public final ByteBuffer myBuf() {
69       return buf;
70     }
71 
myOffset()72     public final int myOffset() {
73       return offset;
74     }
75 
76     @Override
toString()77     public String toString() {
78       return "{buf+" + offset + '}';
79     }
80   }
81 
82   /**
83    * ******************************************************************** Base Types
84    *
85    * <p>These are standard types that are shared between multiple specific resource types.
86    *
87    * <p>**********************************************************************
88    */
89 
90   /** Header that appears at the front of every data chunk in a resource. */
91   public static class ResChunk_header extends WithOffset {
92     static int SIZEOF = 8;
93 
94     // Type identifier for this chunk.  The meaning of this value depends
95     // on the containing chunk.
96     final short type;
97 
98     // Size of the chunk header (in bytes).  Adding this value to
99     // the address of the chunk allows you to find its associated data
100     // (if any).
101     final short headerSize;
102 
103     // Total size of this chunk (in bytes).  This is the chunkSize plus
104     // the size of any data associated with the chunk.  Adding this value
105     // to the chunk allows you to completely skip its contents (including
106     // any child chunks).  If this value is the same as chunkSize, there is
107     // no data associated with the chunk.
108     final int size;
109 
ResChunk_header(ByteBuffer buf, int offset)110     public ResChunk_header(ByteBuffer buf, int offset) {
111       super(buf, offset);
112       this.type = buf.getShort(offset);
113       this.headerSize = buf.getShort(offset + 2);
114       this.size = buf.getInt(offset + 4);
115     }
116 
write(ByteBuffer buf, short type, Runnable header, Runnable contents)117     public static void write(ByteBuffer buf, short type, Runnable header, Runnable contents) {
118       int startPos = buf.position();
119       buf.putShort(type);
120       ShortWriter headerSize = new ShortWriter(buf);
121       IntWriter size = new IntWriter(buf);
122 
123       header.run();
124       headerSize.write((short) (buf.position() - startPos));
125 
126       contents.run();
127 
128       // pad to next int boundary
129       int len = buf.position() - startPos;
130       while ((len & 0x3) != 0) {
131         buf.put((byte) 0);
132         len++;
133       }
134       size.write(len);
135     }
136   }
137 
138   public static final int RES_NULL_TYPE = 0x0000;
139   public static final int RES_STRING_POOL_TYPE = 0x0001;
140   public static final int RES_TABLE_TYPE = 0x0002;
141   public static final int RES_XML_TYPE = 0x0003;
142 
143   // Chunk types in RES_XML_TYPE
144   public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100;
145   public static final int RES_XML_START_NAMESPACE_TYPE = 0x0100;
146   public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101;
147   public static final int RES_XML_START_ELEMENT_TYPE = 0x0102;
148   public static final int RES_XML_END_ELEMENT_TYPE = 0x0103;
149   public static final int RES_XML_CDATA_TYPE = 0x0104;
150   public static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
151   // This contains a uint32_t array mapping strings in the string
152   // pool back to resource identifiers.  It is optional.
153   public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
154 
155   // Chunk types in RES_TABLE_TYPE
156   public static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
157   public static final int RES_TABLE_TYPE_TYPE = 0x0201;
158   public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
159   public static final int RES_TABLE_LIBRARY_TYPE = 0x0203;
160   public static final int RES_TABLE_STAGED_ALIAS_TYPE = 0x0206;
161 
162   /** Macros for building/splitting resource identifiers. */
163   // #define Res_VALIDID(resid) (resid != 0)
164   // #define Res_CHECKID(resid) ((resid&0xFFFF0000) != 0)
165   // #define Res_MAKEID(package, type, entry) \
166   // (((package+1)<<24) | (((type+1)&0xFF)<<16) | (entry&0xFFFF))
167   // #define Res_GETPACKAGE(id) ((id>>24)-1)
168   // #define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
169   // #define Res_GETENTRY(id) (id&0xFFFF)
170 
171   // #define Res_INTERNALID(resid) ((resid&0xFFFF0000) != 0 && (resid&0xFF0000) == 0)
Res_MAKEINTERNAL(int entry)172   private static int Res_MAKEINTERNAL(int entry) {
173     return (0x01000000 | (entry & 0xFFFF));
174   }
175 
176   // #define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF))
177 
178   //  static const size_t Res_MAXPACKAGE = 255;
179   //  static const size_t Res_MAXTYPE = 255;
180 
181   /** Representation of a value in a resource, supplying type information. */
182   public static class Res_value {
183     static final int SIZEOF = 8;
184 
185     // Number of bytes in this structure.
186     final short size;
187 
188     // Always set to 0.
189     //    byte res0;
190 
191     // Type of the data value.
192     //    enum {
193     // The 'data' is either 0 or 1, specifying this resource is either
194     // undefined or empty, respectively.
195     public static final int TYPE_NULL = 0x00;
196     // The 'data' holds a ResTable_ref, a reference to another resource
197     // table entry.
198     public static final int TYPE_REFERENCE = 0x01;
199     // The 'data' holds an attribute resource identifier.
200     public static final int TYPE_ATTRIBUTE = 0x02;
201     // The 'data' holds an index into the containing resource table's
202     // global value string pool.
203     public static final int TYPE_STRING = 0x03;
204     // The 'data' holds a single-precision floating point number.
205     public static final int TYPE_FLOAT = 0x04;
206     // The 'data' holds a complex number encoding a dimension value,
207     // such as "100in".
208     public static final int TYPE_DIMENSION = 0x05;
209     // The 'data' holds a complex number encoding a fraction of a
210     // container.
211     public static final int TYPE_FRACTION = 0x06;
212     // The 'data' holds a dynamic ResTable_ref, which needs to be
213     // resolved before it can be used like a TYPE_REFERENCE.
214     public static final int TYPE_DYNAMIC_REFERENCE = 0x07;
215     // The 'data' holds an attribute resource identifier, which needs to be resolved
216     // before it can be used like a TYPE_ATTRIBUTE.
217     public static final int TYPE_DYNAMIC_ATTRIBUTE = 0x08;
218 
219     // Beginning of integer flavors...
220     public static final int TYPE_FIRST_INT = 0x10;
221 
222     // The 'data' is a raw integer value of the form n..n.
223     public static final int TYPE_INT_DEC = 0x10;
224     // The 'data' is a raw integer value of the form 0xn..n.
225     public static final int TYPE_INT_HEX = 0x11;
226     // The 'data' is either 0 or 1, for input "false" or "true" respectively.
227     public static final int TYPE_INT_BOOLEAN = 0x12;
228 
229     // Beginning of color integer flavors...
230     public static final int TYPE_FIRST_COLOR_INT = 0x1c;
231 
232     // The 'data' is a raw integer value of the form #aarrggbb.
233     public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
234     // The 'data' is a raw integer value of the form #rrggbb.
235     public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
236     // The 'data' is a raw integer value of the form #argb.
237     public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
238     // The 'data' is a raw integer value of the form #rgb.
239     public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
240 
241     // ...end of integer flavors.
242     public static final int TYPE_LAST_COLOR_INT = 0x1f;
243 
244     // ...end of integer flavors.
245     public static final int TYPE_LAST_INT = 0x1f;
246     //  };
247 
248     public final byte dataType;
249 
250     // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
251     //    enum {
252     // Where the unit type information is.  This gives us 16 possible
253     // types, as defined below.
254     public static final int COMPLEX_UNIT_SHIFT = 0;
255     public static final int COMPLEX_UNIT_MASK = 0xf;
256 
257     // TYPE_DIMENSION: Value is raw pixels.
258     public static final int COMPLEX_UNIT_PX = 0;
259     // TYPE_DIMENSION: Value is Device Independent Pixels.
260     public static final int COMPLEX_UNIT_DIP = 1;
261     // TYPE_DIMENSION: Value is a Scaled device independent Pixels.
262     public static final int COMPLEX_UNIT_SP = 2;
263     // TYPE_DIMENSION: Value is in points.
264     public static final int COMPLEX_UNIT_PT = 3;
265     // TYPE_DIMENSION: Value is in inches.
266     public static final int COMPLEX_UNIT_IN = 4;
267     // TYPE_DIMENSION: Value is in millimeters.
268     public static final int COMPLEX_UNIT_MM = 5;
269 
270     // TYPE_FRACTION: A basic fraction of the overall size.
271     public static final int COMPLEX_UNIT_FRACTION = 0;
272     // TYPE_FRACTION: A fraction of the parent size.
273     public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
274 
275     // Where the radix information is, telling where the decimal place
276     // appears in the mantissa.  This give us 4 possible fixed point
277     // representations as defined below.
278     public static final int COMPLEX_RADIX_SHIFT = 4;
279     public static final int COMPLEX_RADIX_MASK = 0x3;
280 
281     // The mantissa is an integral number -- i.e., 0xnnnnnn.0
282     public static final int COMPLEX_RADIX_23p0 = 0;
283     // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
284     public static final int COMPLEX_RADIX_16p7 = 1;
285     // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
286     public static final int COMPLEX_RADIX_8p15 = 2;
287     // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
288     public static final int COMPLEX_RADIX_0p23 = 3;
289 
290     // Where the actual value is.  This gives us 23 bits of
291     // precision.  The top bit is the sign.
292     public static final int COMPLEX_MANTISSA_SHIFT = 8;
293     public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
294     //  };
295 
296     // Possible data values for TYPE_NULL.
297     //    enum {
298     // The value is not defined.
299     public static final int DATA_NULL_UNDEFINED = 0;
300     // The value is explicitly defined as empty.
301     public static final int DATA_NULL_EMPTY = 1;
302     //  };
303 
304     public static final Res_value NULL_VALUE = new Res_value((byte) TYPE_NULL, DATA_NULL_UNDEFINED);
305 
306     // The data for this item, as interpreted according to dataType.
307     //    typedef uint32_t data_type;
308     public final int data;
309 
Res_value()310     public Res_value() {
311       this.size = 0;
312       //      this.res0 = 0;
313       this.dataType = 0;
314       this.data = 0;
315     }
316 
Res_value(ByteBuffer buf, int offset)317     public Res_value(ByteBuffer buf, int offset) {
318       this.size = buf.getShort(offset);
319       byte res0 = buf.get(offset + 2);
320       this.dataType = buf.get(offset + 3);
321       this.data = buf.getInt(offset + 4);
322 
323       if (res0 != 0) {
324         throw new IllegalStateException("res0 != 0 (" + res0 + ")");
325       }
326     }
327 
Res_value(Res_value other)328     public Res_value(Res_value other) {
329       this.size = other.size;
330       //      this.res0 = other.res0;
331       this.dataType = other.dataType;
332       this.data = other.data;
333     }
334 
Res_value(byte dataType, int data)335     public Res_value(byte dataType, int data) {
336       this.size = SIZEOF;
337       //      this.res0 = 0;
338       this.dataType = dataType;
339       this.data = data;
340     }
341 
write(ByteBuffer buf, int dataType, int data)342     public static void write(ByteBuffer buf, int dataType, int data) {
343       buf.putShort((short) SIZEOF); // size
344       buf.put((byte) 0); // res0
345       buf.put((byte) dataType); // dataType
346       buf.putInt(data); // data
347     }
348 
withType(byte dataType)349     public Res_value withType(byte dataType) {
350       return new Res_value(dataType, data);
351     }
352 
withData(int data)353     public Res_value withData(int data) {
354       return new Res_value(dataType, data);
355     }
356 
357     //    public void copyFrom_dtoh(Res_value other) {
358     //      this.size = other.size;
359     // //      this.res0 = other.res0;
360     //      this.dataType = other.dataType;
361     //      this.data = other.data;
362     //    }
363 
copy()364     public Res_value copy() {
365       return new Res_value(this);
366     }
367 
368     @Override
toString()369     public String toString() {
370       return "Res_value{dataType=" + dataType + ", data=" + data + '}';
371     }
372   }
373 
374   /**
375    * This is a reference to a unique entry (a ResTable_entry structure) in a resource table. The
376    * value is structured as: 0xpptteeee, where pp is the package index, tt is the type index in that
377    * package, and eeee is the entry index in that type. The package and type values start at 1 for
378    * the first item, to help catch cases where they have not been supplied.
379    */
380   public static class ResTable_ref {
381     public static final int SIZEOF = 4;
382 
383     public int ident;
384 
ResTable_ref(ByteBuffer buf, int offset)385     public ResTable_ref(ByteBuffer buf, int offset) {
386       ident = buf.getInt(offset);
387     }
388 
ResTable_ref()389     public ResTable_ref() {
390       ident = 0;
391     }
392 
393     @Override
toString()394     public String toString() {
395       return "ResTable_ref{ident=" + ident + '}';
396     }
397   }
398   ;
399 
400   /** Reference to a string in a string pool. */
401   public static class ResStringPool_ref {
402     public static final int SIZEOF = 4;
403 
404     // Index into the string pool table (uint32_t-offset from the indices
405     // immediately after ResStringPool_header) at which to find the location
406     // of the string data in the pool.
407     public final int index;
408 
ResStringPool_ref(ByteBuffer buf, int offset)409     public ResStringPool_ref(ByteBuffer buf, int offset) {
410       this.index = buf.getInt(offset);
411     }
412 
write(ByteBuffer buf, int value)413     public static void write(ByteBuffer buf, int value) {
414       buf.putInt(value);
415     }
416 
417     @Override
toString()418     public String toString() {
419       return "ResStringPool_ref{index=" + index + '}';
420     }
421   }
422 
423   /**
424    * ******************************************************************** String Pool
425    *
426    * <p>A set of strings that can be references by others through a ResStringPool_ref.
427    *
428    * <p>**********************************************************************
429    */
430 
431   /**
432    * Definition for a pool of strings. The data of this chunk is an array of uint32_t providing
433    * indices into the pool, relative to stringsStart. At stringsStart are all of the UTF-16 strings
434    * concatenated together; each starts with a uint16_t of the string's length and each ends with a
435    * 0x0000 terminator. If a string is > 32767 characters, the high bit of the length is set meaning
436    * to take those 15 bits as a high word and it will be followed by another uint16_t containing the
437    * low word.
438    *
439    * <p>If styleCount is not zero, then immediately following the array of uint32_t indices into the
440    * string table is another array of indices into a style table starting at stylesStart. Each entry
441    * in the style table is an array of ResStringPool_span structures.
442    */
443   public static class ResStringPool_header extends WithOffset {
444     public static final int SIZEOF = ResChunk_header.SIZEOF + 20;
445 
446     final ResChunk_header header;
447 
448     // Number of strings in this pool (number of uint32_t indices that follow
449     // in the data).
450     final int stringCount;
451 
452     // Number of style span arrays in the pool (number of uint32_t indices
453     // follow the string indices).
454     final int styleCount;
455 
456     // Flags.
457     //    enum {
458     // If set, the string index is sorted by the string values (based
459     // on strcmp16()).
460     public static final int SORTED_FLAG = 1 << 0;
461 
462     // String pool is encoded in UTF-8
463     public static final int UTF8_FLAG = 1 << 8;
464     //  };
465     final int flags;
466 
467     // Index from header of the string data.
468     final int stringsStart;
469 
470     // Index from header of the style data.
471     final int stylesStart;
472 
ResStringPool_header(ByteBuffer buf, int offset)473     public ResStringPool_header(ByteBuffer buf, int offset) {
474       super(buf, offset);
475 
476       this.header = new ResChunk_header(buf, offset);
477       this.stringCount = buf.getInt(offset + ResChunk_header.SIZEOF);
478       this.styleCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4);
479       this.flags = buf.getInt(offset + ResChunk_header.SIZEOF + 8);
480       this.stringsStart = buf.getInt(offset + ResChunk_header.SIZEOF + 12);
481       this.stylesStart = buf.getInt(offset + ResChunk_header.SIZEOF + 16);
482     }
483 
getByte(int i)484     public int getByte(int i) {
485       return myBuf().get(myOffset() + i);
486     }
487 
getShort(int i)488     public int getShort(int i) {
489       return myBuf().getShort(myOffset() + i);
490     }
491 
492     public static class Writer {
493 
494       private final List<String> strings = new ArrayList<>();
495       private final List<byte[]> stringsAsBytes = new ArrayList<>();
496       private final Map<String, Integer> stringIds = new HashMap<>();
497 
498       private boolean frozen;
499 
string(String s)500       public int string(String s) {
501         if (frozen) {
502           throw new IllegalStateException("string pool is frozen!");
503         }
504 
505         if (s == null) {
506           return -1;
507         }
508 
509         Integer id = stringIds.get(s);
510         if (id == null) {
511           id = strings.size();
512           strings.add(s);
513           stringsAsBytes.add(s.getBytes(UTF_8));
514           stringIds.put(s, id);
515         }
516         return id;
517       }
518 
uniqueString(String s)519       public int uniqueString(String s) {
520         if (frozen) {
521           throw new IllegalStateException("string pool is frozen!");
522         }
523 
524         if (s == null) {
525           return -1;
526         }
527 
528         int id = strings.size();
529         strings.add(s);
530         stringsAsBytes.add(s.getBytes(UTF_8));
531         return id;
532       }
533 
write(ByteBuffer buf)534       public void write(ByteBuffer buf) {
535         freeze();
536 
537         ResChunk_header.write(
538             buf,
539             (short) RES_STRING_POOL_TYPE,
540             () -> {
541               // header
542               int startPos = buf.position();
543               int stringCount = strings.size();
544 
545               // begin string pool...
546               buf.putInt(stringCount); // stringCount
547               buf.putInt(0); // styleCount
548               buf.putInt(UTF8_FLAG); // flags
549               IntWriter stringStart = new IntWriter(buf);
550               buf.putInt(0); // stylesStart
551 
552               stringStart.write(buf.position() - startPos);
553             },
554             () -> {
555               // contents
556               int stringOffset = /*buf.position() + */ 8 + 4 * stringsAsBytes.size();
557               for (int i = 0; i < stringsAsBytes.size(); i++) {
558                 String string = strings.get(i);
559                 byte[] bytes = stringsAsBytes.get(i);
560                 buf.putInt(stringOffset);
561                 stringOffset += lenLen(string.length()) + lenLen(bytes.length) + bytes.length + 1;
562               }
563 
564               for (int i = 0; i < stringsAsBytes.size(); i++) {
565                 // number of chars
566                 writeLen(buf, strings.get(i).length());
567 
568                 // number of bytes
569                 writeLen(buf, stringsAsBytes.get(i).length);
570 
571                 // bytes
572                 buf.put(stringsAsBytes.get(i));
573                 // null terminator
574                 buf.put((byte) '\0');
575               }
576             });
577       }
578 
lenLen(int length)579       private int lenLen(int length) {
580         return length > 0x7f ? 2 : 1;
581       }
582 
writeLen(ByteBuffer buf, int length)583       private void writeLen(ByteBuffer buf, int length) {
584         if (length <= 0x7f) {
585           buf.put((byte) length);
586         } else {
587           buf.put((byte) ((length >> 8) | 0x80));
588           buf.put((byte) (length & 0x7f));
589         }
590       }
591 
freeze()592       public void freeze() {
593         frozen = true;
594       }
595     }
596   }
597 
598   /** This structure defines a span of style information associated with a string in the pool. */
599   public static class ResStringPool_span extends WithOffset {
600     public static final int SIZEOF = ResStringPool_ref.SIZEOF + 8;
601 
602     //    enum {
603     public static final int END = 0xFFFFFFFF;
604     //  };
605 
606     // This is the name of the span -- that is, the name of the XML
607     // tag that defined it.  The special value END (0xFFFFFFFF) indicates
608     // the end of an array of spans.
609     public final ResStringPool_ref name;
610 
611     // The range of characters in the string that this span applies to.
612     final int firstChar;
613     final int lastChar;
614 
ResStringPool_span(ByteBuffer buf, int offset)615     public ResStringPool_span(ByteBuffer buf, int offset) {
616       super(buf, offset);
617 
618       name = new ResStringPool_ref(buf, offset);
619       firstChar = buf.getInt(offset + ResStringPool_ref.SIZEOF);
620       lastChar = buf.getInt(offset + ResStringPool_ref.SIZEOF + 4);
621     }
622 
isEnd()623     public boolean isEnd() {
624       return name.index == END && firstChar == END && lastChar == END;
625     }
626   }
627   ;
628 
629   /**
630    * ******************************************************************** XML Tree
631    *
632    * <p>Binary representation of an XML document. This is designed to express everything in an XML
633    * document, in a form that is much easier to parse on the device.
634    *
635    * <p>**********************************************************************
636    */
637 
638   /**
639    * XML tree header. This appears at the front of an XML tree, describing its content. It is
640    * followed by a flat array of ResXMLTree_node structures; the hierarchy of the XML document is
641    * described by the occurrance of RES_XML_START_ELEMENT_TYPE and corresponding
642    * RES_XML_END_ELEMENT_TYPE nodes in the array.
643    */
644   public static class ResXMLTree_header extends WithOffset {
645     public final ResChunk_header header;
646 
ResXMLTree_header(ByteBuffer buf, int offset)647     ResXMLTree_header(ByteBuffer buf, int offset) {
648       super(buf, offset);
649       header = new ResChunk_header(buf, offset);
650     }
651 
write(ByteBuffer buf, Writer resStringPoolWriter, Runnable contents)652     public static void write(ByteBuffer buf, Writer resStringPoolWriter, Runnable contents) {
653       ResChunk_header.write(
654           buf,
655           (short) RES_XML_TYPE,
656           () -> {},
657           () -> {
658             resStringPoolWriter.write(buf);
659             contents.run();
660           });
661     }
662   }
663 
664   /**
665    * Basic XML tree node. A single item in the XML document. Extended info about the node can be
666    * found after header.headerSize.
667    */
668   public static class ResXMLTree_node extends WithOffset {
669     final ResChunk_header header;
670 
671     // Line number in original source file at which this element appeared.
672     final int lineNumber;
673 
674     // Optional XML comment that was associated with this element; -1 if none.
675     final ResStringPool_ref comment;
676 
ResXMLTree_node(ByteBuffer buf, int offset)677     ResXMLTree_node(ByteBuffer buf, int offset) {
678       super(buf, offset);
679 
680       this.header = new ResChunk_header(buf, offset);
681       this.lineNumber = buf.getInt(offset + ResChunk_header.SIZEOF);
682       this.comment = new ResStringPool_ref(buf, offset + 12);
683     }
684 
ResXMLTree_node(ByteBuffer buf, ResChunk_header header)685     ResXMLTree_node(ByteBuffer buf, ResChunk_header header) {
686       super(buf, header.myOffset());
687 
688       this.header = header;
689       this.lineNumber = buf.getInt(myOffset() + ResChunk_header.SIZEOF);
690       this.comment = new ResStringPool_ref(buf, myOffset() + ResChunk_header.SIZEOF + 4);
691     }
692 
write(ByteBuffer buf, int type, Runnable contents)693     public static void write(ByteBuffer buf, int type, Runnable contents) {
694       ResChunk_header.write(
695           buf,
696           (short) type,
697           () -> {
698             buf.putInt(-1); // lineNumber
699             ResStringPool_ref.write(buf, -1); // comment
700           },
701           contents);
702     }
703   }
704   ;
705 
706   /**
707    * Extended XML tree node for CDATA tags -- includes the CDATA string. Appears header.headerSize
708    * bytes after a ResXMLTree_node.
709    */
710   static class ResXMLTree_cdataExt {
711     // The raw CDATA character data.
712     final ResStringPool_ref data;
713 
714     // The typed value of the character data if this is a CDATA node.
715     final Res_value typedData;
716 
ResXMLTree_cdataExt(ByteBuffer buf, int offset)717     public ResXMLTree_cdataExt(ByteBuffer buf, int offset) {
718       this.data = new ResStringPool_ref(buf, offset);
719 
720       int dataType = buf.getInt(offset + 4);
721       int data = buf.getInt(offset + 8);
722       this.typedData = new Res_value((byte) dataType, data);
723     }
724   }
725   ;
726 
727   /**
728    * Extended XML tree node for namespace start/end nodes. Appears header.headerSize bytes after a
729    * ResXMLTree_node.
730    */
731   static class ResXMLTree_namespaceExt {
732     // The prefix of the namespace.
733     final ResStringPool_ref prefix;
734 
735     // The URI of the namespace.
736     final ResStringPool_ref uri;
737 
ResXMLTree_namespaceExt(ByteBuffer buf, int offset)738     public ResXMLTree_namespaceExt(ByteBuffer buf, int offset) {
739       this.prefix = new ResStringPool_ref(buf, offset);
740       this.uri = new ResStringPool_ref(buf, offset + 4);
741     }
742   }
743   ;
744 
745   /**
746    * Extended XML tree node for element start/end nodes. Appears header.headerSize bytes after a
747    * ResXMLTree_node.
748    */
749   public static class ResXMLTree_endElementExt {
750     static final int SIZEOF = 8;
751 
752     // String of the full namespace of this element.
753     final ResStringPool_ref ns;
754 
755     // String name of this node if it is an ELEMENT; the raw
756     // character data if this is a CDATA node.
757     final ResStringPool_ref name;
758 
ResXMLTree_endElementExt(ByteBuffer buf, int offset)759     public ResXMLTree_endElementExt(ByteBuffer buf, int offset) {
760       this.ns = new ResStringPool_ref(buf, offset);
761       this.name = new ResStringPool_ref(buf, offset + ResStringPool_ref.SIZEOF);
762     }
763 
764     public static class Writer {
765       private final ByteBuffer buf;
766       private final int ns;
767       private final int name;
768 
Writer( ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, String ns, String name)769       public Writer(
770           ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, String ns, String name) {
771         this.buf = buf;
772         this.ns = resStringPoolWriter.string(ns);
773         this.name = resStringPoolWriter.string(name);
774       }
775 
write()776       public void write() {
777         ResStringPool_ref.write(buf, ns);
778         ResStringPool_ref.write(buf, name);
779       }
780     }
781   }
782   ;
783 
784   /**
785    * Extended XML tree node for start tags -- includes attribute information. Appears
786    * header.headerSize bytes after a ResXMLTree_node.
787    */
788   public static class ResXMLTree_attrExt extends WithOffset {
789     private final ByteBuffer buf;
790 
791     // String of the full namespace of this element.
792     final ResStringPool_ref ns;
793 
794     // String name of this node if it is an ELEMENT; the raw
795     // character data if this is a CDATA node.
796     final ResStringPool_ref name;
797 
798     // Byte offset from the start of this structure where the attributes start.
799     final short attributeStart;
800 
801     // Size of the ResXMLTree_attribute structures that follow.
802     final short attributeSize;
803 
804     // Number of attributes associated with an ELEMENT.  These are
805     // available as an array of ResXMLTree_attribute structures
806     // immediately following this node.
807     final short attributeCount;
808 
809     // Index (1-based) of the "id" attribute. 0 if none.
810     final short idIndex;
811 
812     // Index (1-based) of the "class" attribute. 0 if none.
813     final short classIndex;
814 
815     // Index (1-based) of the "style" attribute. 0 if none.
816     final short styleIndex;
817 
ResXMLTree_attrExt(ByteBuffer buf, int offset)818     public ResXMLTree_attrExt(ByteBuffer buf, int offset) {
819       super(buf, offset);
820       this.buf = buf;
821 
822       this.ns = new ResStringPool_ref(buf, offset);
823       this.name = new ResStringPool_ref(buf, offset + 4);
824       this.attributeStart = buf.getShort(offset + 8);
825       this.attributeSize = buf.getShort(offset + 10);
826       this.attributeCount = buf.getShort(offset + 12);
827       this.idIndex = buf.getShort(offset + 14);
828       this.classIndex = buf.getShort(offset + 16);
829       this.styleIndex = buf.getShort(offset + 18);
830     }
831 
attributeAt(int idx)832     ResXMLTree_attribute attributeAt(int idx) {
833       return new ResXMLTree_attribute(
834           buf, myOffset() + dtohs(attributeStart) + dtohs(attributeSize) * idx);
835     }
836 
837     public static class Writer {
838       private final ByteBuffer buf;
839       private final int ns;
840       private final int name;
841 
842       private short idIndex;
843       private short classIndex;
844       private short styleIndex;
845 
846       private final List<Attr> attrs = new ArrayList<>();
847 
Writer( ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, String ns, String name)848       public Writer(
849           ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, String ns, String name) {
850         this.buf = buf;
851         this.ns = resStringPoolWriter.string(ns);
852         this.name = resStringPoolWriter.string(name);
853       }
854 
attr(int ns, int name, int value, Res_value resValue, String fullName)855       public void attr(int ns, int name, int value, Res_value resValue, String fullName) {
856         attrs.add(new Attr(ns, name, value, resValue, fullName));
857       }
858 
write()859       public void write() {
860         int startPos = buf.position();
861         int attributeCount = attrs.size();
862 
863         ResStringPool_ref.write(buf, ns);
864         ResStringPool_ref.write(buf, name);
865         ShortWriter attributeStartWriter = new ShortWriter(buf);
866         buf.putShort((short) ResXMLTree_attribute.SIZEOF); // attributeSize
867         buf.putShort((short) attributeCount); // attributeCount
868         ShortWriter idIndexWriter = new ShortWriter(buf);
869         ShortWriter classIndexWriter = new ShortWriter(buf);
870         ShortWriter styleIndexWriter = new ShortWriter(buf);
871 
872         attributeStartWriter.write((short) (buf.position() - startPos));
873         for (int i = 0; i < attributeCount; i++) {
874           Attr attr = attrs.get(i);
875 
876           switch (attr.fullName) {
877             case ":id":
878               idIndex = (short) (i + 1);
879               break;
880             case ":style":
881               styleIndex = (short) (i + 1);
882               break;
883             case ":class":
884               classIndex = (short) (i + 1);
885               break;
886           }
887 
888           attr.write(buf);
889         }
890 
891         idIndexWriter.write(idIndex);
892         classIndexWriter.write(classIndex);
893         styleIndexWriter.write(styleIndex);
894       }
895 
896       private static class Attr {
897         final int ns;
898         final int name;
899         final int value;
900         final int resValueDataType;
901         final int resValueData;
902         final String fullName;
903 
Attr(int ns, int name, int value, Res_value resValue, String fullName)904         public Attr(int ns, int name, int value, Res_value resValue, String fullName) {
905           this.ns = ns;
906           this.name = name;
907           this.value = value;
908           this.resValueDataType = resValue.dataType;
909           this.resValueData = resValue.data;
910           this.fullName = fullName;
911         }
912 
write(ByteBuffer buf)913         public void write(ByteBuffer buf) {
914           ResXMLTree_attribute.write(buf, ns, name, value, resValueDataType, resValueData);
915         }
916       }
917     }
918   }
919   ;
920 
921   static class ResXMLTree_attribute {
922     public static final int SIZEOF = 12 + ResourceTypes.Res_value.SIZEOF;
923 
924     // Namespace of this attribute.
925     final ResStringPool_ref ns;
926 
927     // Name of this attribute.
928     final ResStringPool_ref name;
929 
930     // The original raw string value of this attribute.
931     final ResStringPool_ref rawValue;
932 
933     // Processesd typed value of this attribute.
934     final Res_value typedValue;
935 
ResXMLTree_attribute(ByteBuffer buf, int offset)936     public ResXMLTree_attribute(ByteBuffer buf, int offset) {
937       this.ns = new ResStringPool_ref(buf, offset);
938       this.name = new ResStringPool_ref(buf, offset + 4);
939       this.rawValue = new ResStringPool_ref(buf, offset + 8);
940       this.typedValue = new Res_value(buf, offset + 12);
941     }
942 
write( ByteBuffer buf, int ns, int name, int value, int resValueDataType, int resValueData)943     public static void write(
944         ByteBuffer buf, int ns, int name, int value, int resValueDataType, int resValueData) {
945       ResStringPool_ref.write(buf, ns);
946       ResStringPool_ref.write(buf, name);
947       ResStringPool_ref.write(buf, value);
948       ResourceTypes.Res_value.write(buf, resValueDataType, resValueData);
949     }
950   }
951   ;
952 
953   /**
954    * ******************************************************************** RESOURCE TABLE
955    *
956    * <p>**********************************************************************
957    */
958 
959   /**
960    * Header for a resource table. Its data contains a series of additional chunks: * A
961    * ResStringPool_header containing all table values. This string pool contains all of the string
962    * values in the entire resource table (not the names of entries or type identifiers however). *
963    * One or more ResTable_package chunks.
964    *
965    * <p>Specific entries within a resource table can be uniquely identified with a single integer as
966    * defined by the ResTable_ref structure.
967    */
968   static class ResTable_header extends WithOffset {
969     public static final int SIZEOF = ResChunk_header.SIZEOF + 4;
970 
971     final ResChunk_header header;
972 
973     // The number of ResTable_package structures.
974     final int packageCount;
975 
ResTable_header(ByteBuffer buf, int offset)976     public ResTable_header(ByteBuffer buf, int offset) {
977       super(buf, offset);
978       this.header = new ResChunk_header(buf, offset);
979       this.packageCount = buf.getInt(offset + ResChunk_header.SIZEOF);
980     }
981   }
982 
983   /**
984    * A collection of resource data types within a package. Followed by one or more ResTable_type and
985    * ResTable_typeSpec structures containing the entry values for each resource type.
986    */
987   static class ResTable_package extends WithOffset {
988     public static final int SIZEOF = ResChunk_header.SIZEOF + 4 + 128 + 20;
989 
990     final ResChunk_header header;
991 
992     // If this is a base package, its ID.  Package IDs start
993     // at 1 (corresponding to the value of the package bits in a
994     // resource identifier).  0 means this is not a base package.
995     public final int id;
996 
997     // Actual name of this package, \0-terminated.
998     public final char[] name = new char[128];
999 
1000     // Offset to a ResStringPool_header defining the resource
1001     // type symbol table.  If zero, this package is inheriting from
1002     // another base package (overriding specific values in it).
1003     public final int typeStrings;
1004 
1005     // Last index into typeStrings that is for public use by others.
1006     public final int lastPublicType;
1007 
1008     // Offset to a ResStringPool_header defining the resource
1009     // key symbol table.  If zero, this package is inheriting from
1010     // another base package (overriding specific values in it).
1011     public final int keyStrings;
1012 
1013     // Last index into keyStrings that is for public use by others.
1014     public final int lastPublicKey;
1015 
1016     public final int typeIdOffset;
1017 
ResTable_package(ByteBuffer buf, int offset)1018     public ResTable_package(ByteBuffer buf, int offset) {
1019       super(buf, offset);
1020       header = new ResChunk_header(buf, offset);
1021       id = buf.getInt(offset + ResChunk_header.SIZEOF);
1022       for (int i = 0; i < name.length; i++) {
1023         name[i] = buf.getChar(offset + ResChunk_header.SIZEOF + 4 + i * 2);
1024       }
1025       typeStrings = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256);
1026       lastPublicType = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 4);
1027       keyStrings = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 8);
1028       lastPublicKey = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 12);
1029       typeIdOffset = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 16);
1030     }
1031   }
1032   ;
1033 
1034   // The most specific locale can consist of:
1035   //
1036   // - a 3 char language code
1037   // - a 3 char region code prefixed by a 'r'
1038   // - a 4 char script code prefixed by a 's'
1039   // - a 8 char variant code prefixed by a 'v'
1040   //
1041   // each separated by a single char separator, which sums up to a total of 24
1042   // chars, (25 include the string terminator). Numbering system specificator,
1043   // if present, can add up to 14 bytes (-u-nu-xxxxxxxx), giving 39 bytes,
1044   // or 40 bytes to make it 4 bytes aligned.
1045   public static final int RESTABLE_MAX_LOCALE_LEN = 40;
1046 
1047   /**
1048    * A specification of the resources defined by a particular type.
1049    *
1050    * <p>There should be one of these chunks for each resource type.
1051    *
1052    * <p>This structure is followed by an array of integers providing the set of configuration change
1053    * flags (ResTable_config::CONFIG_*) that have multiple resources for that configuration. In
1054    * addition, the high bit is set if that resource has been made public.
1055    */
1056   static class ResTable_typeSpec extends WithOffset {
1057     public static final int SIZEOF = ResChunk_header.SIZEOF + 8;
1058 
1059     final ResChunk_header header;
1060 
1061     // The type identifier this chunk is holding.  Type IDs start
1062     // at 1 (corresponding to the value of the type bits in a
1063     // resource identifier).  0 is invalid.
1064     final byte id;
1065 
1066     // Must be 0.
1067     final byte res0;
1068     // Must be 0.
1069     final short res1;
1070 
1071     // Number of uint32_t entry configuration masks that follow.
1072     final int entryCount;
1073 
1074     // enum : uint32_t {
1075     // Additional flag indicating an entry is public.
1076     static final int SPEC_PUBLIC = 0x40000000;
1077 
1078     // Additional flag indicating an entry is overlayable at runtime.
1079     // Added in Android-P.
1080     static final int SPEC_OVERLAYABLE = 0x80000000;
1081 
1082     //    };
1083 
ResTable_typeSpec(ByteBuffer buf, int offset)1084     public ResTable_typeSpec(ByteBuffer buf, int offset) {
1085       super(buf, offset);
1086 
1087       header = new ResChunk_header(buf, offset);
1088       id = buf.get(offset + ResChunk_header.SIZEOF);
1089       res0 = buf.get(offset + ResChunk_header.SIZEOF + 1);
1090       res1 = buf.getShort(offset + ResChunk_header.SIZEOF + 2);
1091       entryCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4);
1092     }
1093 
getSpecFlags()1094     public int[] getSpecFlags() {
1095       int[] ints = new int[(header.size - header.headerSize) / 4];
1096       for (int i = 0; i < ints.length; i++) {
1097         ints[i] = myBuf().getInt(myOffset() + header.headerSize + i * 4);
1098       }
1099       return ints;
1100     }
1101   }
1102   ;
1103 
1104   /**
1105    * A collection of resource entries for a particular resource data type.
1106    *
1107    * <p>If the flag FLAG_SPARSE is not set in `flags`, then this struct is followed by an array of
1108    * uint32_t defining the resource values, corresponding to the array of type strings in the
1109    * ResTable_package::typeStrings string block. Each of these hold an index from entriesStart; a
1110    * value of NO_ENTRY means that entry is not defined.
1111    *
1112    * <p>If the flag FLAG_SPARSE is set in `flags`, then this struct is followed by an array of
1113    * ResTable_sparseTypeEntry defining only the entries that have values for this type. Each entry
1114    * is sorted by their entry ID such that a binary search can be performed over the entries. The ID
1115    * and offset are encoded in a uint32_t. See ResTabe_sparseTypeEntry.
1116    *
1117    * <p>There may be multiple of these chunks for a particular resource type, supply different
1118    * configuration variations for the resource values of that type.
1119    *
1120    * <p>It would be nice to have an additional ordered index of entries, so we can do a binary
1121    * search if trying to find a resource by string name.
1122    */
1123   static class ResTable_type extends WithOffset {
1124     //      public static final int SIZEOF = ResChunk_header.SIZEOF + 12 + ResTable_config.SIZ;
1125     public static final int SIZEOF_WITHOUT_CONFIG = ResChunk_header.SIZEOF + 12;
1126 
1127     final ResChunk_header header;
1128 
1129     // enum {
1130     public static final int NO_ENTRY = 0xFFFFFFFF;
1131     //    };
1132 
1133     // The type identifier this chunk is holding.  Type IDs start
1134     // at 1 (corresponding to the value of the type bits in a
1135     // resource identifier).  0 is invalid.
1136     final byte id;
1137 
1138     //      enum {
1139     // If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
1140     // and a binary search is used to find the key. Only available on platforms >= O.
1141     // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
1142     // platforms.
1143     public static final int FLAG_SPARSE = 0x01;
1144 
1145     // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
1146     // An 16-bit offset of 0xffffu means a NO_ENTRY
1147     public static final int FLAG_OFFSET16 = 0x02;
1148 
1149     //    };
1150     final byte flags;
1151 
1152     // Must be 0.
1153     final short reserved;
1154 
1155     // Number of uint32_t entry indices that follow.
1156     final int entryCount;
1157 
1158     // Offset from header where ResTable_entry data starts.
1159     final int entriesStart;
1160 
1161     // Configuration this collection of entries is designed for. This must always be last.
1162     final ResTable_config config;
1163 
ResTable_type(ByteBuffer buf, int offset)1164     ResTable_type(ByteBuffer buf, int offset) {
1165       super(buf, offset);
1166 
1167       header = new ResChunk_header(buf, offset);
1168       id = buf.get(offset + ResChunk_header.SIZEOF);
1169       flags = buf.get(offset + ResChunk_header.SIZEOF + 1);
1170       reserved = buf.getShort(offset + ResChunk_header.SIZEOF + 2);
1171       entryCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4);
1172       entriesStart = buf.getInt(offset + ResChunk_header.SIZEOF + 8);
1173 
1174       // Cast to Buffer because generated covariant return type that returns ByteBuffer is not
1175       // available on Java 8
1176       ((Buffer) buf).position(offset + ResChunk_header.SIZEOF + 12);
1177       config = ResTable_config.createConfig(buf);
1178     }
1179 
findEntryByResName(int stringId)1180     public int findEntryByResName(int stringId) {
1181       for (int i = 0; i < entryCount; i++) {
1182         if (entryNameIndex(i) == stringId) {
1183           if (isTruthy(flags & ResTable_type.FLAG_SPARSE)) {
1184             ResTable_sparseTypeEntry sparseEntry = getSparseEntry(i);
1185             return sparseEntry.idx;
1186           } else {
1187             return i;
1188           }
1189         }
1190       }
1191       return -1;
1192     }
1193 
entryOffset(int entryIndex)1194     int entryOffset(int entryIndex) {
1195       ByteBuffer byteBuffer = myBuf();
1196       int offset = myOffset();
1197       if (isTruthy(flags & ResTable_type.FLAG_OFFSET16)) {
1198         short off16 = byteBuffer.getShort(offset + header.headerSize + entryIndex * 2);
1199         // Check for no entry (0xffff short)
1200         if (dtohs(off16) == -1) {
1201           return ResTable_type.NO_ENTRY;
1202         }
1203         return Short.toUnsignedInt(dtohs(off16)) * 4;
1204       } else if (isTruthy(flags & ResTable_type.FLAG_SPARSE)) {
1205         ResTable_sparseTypeEntry sparseEntry = getSparseEntry(entryIndex);
1206         // if (!sparse_entry) {
1207         //   return base::unexpected(IOError::PAGES_MISSING);
1208         // }
1209         // TODO: implement above
1210         // offset = dtohs(sparse_entry->offset) * 4u;
1211         return dtohs(sparseEntry.offset) * 4;
1212       } else {
1213         return byteBuffer.getInt(offset + header.headerSize + entryIndex * 4);
1214       }
1215     }
1216 
1217     // Gets the sparse entry index item at position 'entryIndex'
getSparseEntry(int entryIndex)1218     private ResTable_sparseTypeEntry getSparseEntry(int entryIndex) {
1219       return new ResTable_sparseTypeEntry(
1220           myBuf(), myOffset() + header.headerSize + entryIndex * ResTable_sparseTypeEntry.SIZEOF);
1221     }
1222 
entryNameIndex(int entryIndex)1223     private int entryNameIndex(int entryIndex) {
1224       ByteBuffer byteBuffer = myBuf();
1225       int offset = myOffset();
1226 
1227       // from ResTable cpp:
1228       //            const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
1229       //            reinterpret_cast<const uint8_t*>(thisType) +
1230       // dtohs(thisType->header.headerSize));
1231       //
1232       //        uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
1233 
1234       int entryOffset = entryOffset(entryIndex);
1235       if (entryOffset == -1) {
1236         return -1;
1237       }
1238 
1239       int STRING_POOL_REF_OFFSET = 4;
1240       return dtohl(byteBuffer.getInt(offset + entriesStart + entryOffset + STRING_POOL_REF_OFFSET));
1241     }
1242   }
1243   ;
1244 
1245   // The minimum size required to read any version of ResTable_type.
1246   //   constexpr size_t kResTableTypeMinSize =
1247   //   sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size);
1248   static final int kResTableTypeMinSize =
1249       ResTable_type.SIZEOF_WITHOUT_CONFIG
1250           - ResTable_config.SIZEOF
1251           + SIZEOF_INT /*sizeof(ResTable_config::size)*/;
1252 
1253   /** An entry in a ResTable_type with the flag `FLAG_SPARSE` set. */
1254   static class ResTable_sparseTypeEntry extends WithOffset {
1255     public static final int SIZEOF = 4;
1256 
1257     // Holds the raw uint32_t encoded value. Do not read this.
1258     // int entry;
1259 
1260     short idx;
1261     short offset;
1262 
1263     //    struct {
1264     // The index of the entry.
1265     //      uint16_t idx;
1266 
1267     // The offset from ResTable_type::entriesStart, divided by 4.
1268     //      uint16_t offset;
1269     //    };
1270 
ResTable_sparseTypeEntry(ByteBuffer buf, int offset)1271     public ResTable_sparseTypeEntry(ByteBuffer buf, int offset) {
1272       super(buf, offset);
1273 
1274       this.idx = buf.getShort(offset);
1275       this.offset = buf.getShort(offset + 2);
1276     }
1277   }
1278   ;
1279 
1280   /**
1281    * This is the beginning of information about an entry in the resource table. It holds the
1282    * reference to the name of this entry, and is immediately followed by one of: * A Res_value
1283    * structure, if FLAG_COMPLEX is -not- set. * An array of ResTable_map structures, if FLAG_COMPLEX
1284    * is set. These supply a set of name/value mappings of data.
1285    */
1286   static class ResTable_entry extends WithOffset {
1287     public static final int SIZEOF = 4 + ResStringPool_ref.SIZEOF;
1288 
1289     // Number of bytes in this structure.
1290     short size;
1291 
1292     // If set, this is a complex entry, holding a set of name/value
1293     // mappings.  It is followed by an array of ResTable_map structures.
1294     public static final int FLAG_COMPLEX = 0x0001;
1295     // If set, this resource has been declared public, so libraries
1296     // are allowed to reference it.
1297     public static final int FLAG_PUBLIC = 0x0002;
1298     // If set, this is a weak resource and may be overriden by strong
1299     // resources of the same name/type. This is only useful during
1300     // linking with other resource tables.
1301     public static final int FLAG_WEAK = 0x0004;
1302     // If set, this is a compact entry with data type and value directly
1303     // encoded in the this entry, see ResTable_entry::compact
1304     public static final int FLAG_COMPACT = 0x0008;
1305 
1306     final short flags;
1307 
1308     // Reference into ResTable_package::keyStrings identifying this entry.
1309     ResStringPool_ref key;
1310 
1311     int compactData;
1312     short compactKey;
1313 
ResTable_entry(ByteBuffer buf, int offset)1314     ResTable_entry(ByteBuffer buf, int offset) {
1315       super(buf, offset);
1316 
1317       flags = buf.getShort(offset + 2);
1318 
1319       if (isCompact()) {
1320         compactKey = buf.getShort(offset);
1321         compactData = buf.getInt(offset + 4);
1322       } else {
1323         size = buf.getShort(offset);
1324         key = new ResStringPool_ref(buf, offset + 4);
1325       }
1326     }
1327 
getKeyIndex()1328     public int getKeyIndex() {
1329       if (isCompact()) {
1330         return dtohs(compactKey);
1331       } else {
1332         return key.index;
1333       }
1334     }
1335 
isCompact()1336     public boolean isCompact() {
1337       return (flags & FLAG_COMPACT) == FLAG_COMPACT;
1338     }
1339 
getResValue()1340     public Res_value getResValue() {
1341       // something like:
1342 
1343       // final Res_value device_value = reinterpret_cast<final Res_value>(
1344       //     reinterpret_cast<final byte*>(entry) + dtohs(entry.size));
1345 
1346       if (isCompact()) {
1347         byte type = (byte) (dtohs(flags) >> 8);
1348         return new Res_value(type, compactData);
1349       } else {
1350         return new Res_value(myBuf(), myOffset() + dtohs(size));
1351       }
1352     }
1353   }
1354 
1355   /**
1356    * Extended form of a ResTable_entry for map entries, defining a parent map resource from which to
1357    * inherit values.
1358    */
1359   static class ResTable_map_entry extends ResTable_entry {
1360 
1361     /** Indeterminate size, calculate using {@link #size} instead. */
1362     public static final Void SIZEOF = null;
1363 
1364     public static final int BASE_SIZEOF = ResTable_entry.SIZEOF + 8;
1365 
1366     // Resource identifier of the parent mapping, or 0 if there is none.
1367     // This is always treated as a TYPE_DYNAMIC_REFERENCE.
1368     ResTable_ref parent;
1369     // Number of name/value pairs that follow for FLAG_COMPLEX.
1370     int count;
1371 
ResTable_map_entry(ByteBuffer buf, int offset)1372     ResTable_map_entry(ByteBuffer buf, int offset) {
1373       super(buf, offset);
1374 
1375       parent = new ResTable_ref(buf, offset + ResTable_entry.SIZEOF);
1376       count = buf.getInt(offset + ResTable_entry.SIZEOF + ResTable_ref.SIZEOF);
1377     }
1378   }
1379   ;
1380 
1381   /** A single name/value mapping that is part of a complex resource entry. */
1382   public static class ResTable_map extends WithOffset {
1383     public static final int SIZEOF = ResTable_ref.SIZEOF + ResourceTypes.Res_value.SIZEOF;
1384 
1385     // The resource identifier defining this mapping's name.  For attribute
1386     // resources, 'name' can be one of the following special resource types
1387     // to supply meta-data about the attribute; for all other resource types
1388     // it must be an attribute resource.
1389     public final ResTable_ref name;
1390 
1391     // Special values for 'name' when defining attribute resources.
1392     // enum {
1393     // This entry holds the attribute's type code.
1394     public static final int ATTR_TYPE = Res_MAKEINTERNAL(0);
1395 
1396     // For integral attributes, this is the minimum value it can hold.
1397     public static final int ATTR_MIN = Res_MAKEINTERNAL(1);
1398 
1399     // For integral attributes, this is the maximum value it can hold.
1400     public static final int ATTR_MAX = Res_MAKEINTERNAL(2);
1401 
1402     // Localization of this resource is can be encouraged or required with
1403     // an aapt flag if this is set
1404     public static final int ATTR_L10N = Res_MAKEINTERNAL(3);
1405 
1406     // for plural support, see android.content.res.PluralRules#attrForQuantity(int)
1407     public static final int ATTR_OTHER = Res_MAKEINTERNAL(4);
1408     public static final int ATTR_ZERO = Res_MAKEINTERNAL(5);
1409     public static final int ATTR_ONE = Res_MAKEINTERNAL(6);
1410     public static final int ATTR_TWO = Res_MAKEINTERNAL(7);
1411     public static final int ATTR_FEW = Res_MAKEINTERNAL(8);
1412     public static final int ATTR_MANY = Res_MAKEINTERNAL(9);
1413 
1414     //    };
1415 
1416     // Bit mask of allowed types, for use with ATTR_TYPE.
1417     // enum {
1418     // No type has been defined for this attribute, use generic
1419     // type handling.  The low 16 bits are for types that can be
1420     // handled generically; the upper 16 require additional information
1421     // in the bag so can not be handled generically for TYPE_ANY.
1422     public static final int TYPE_ANY = 0x0000FFFF;
1423 
1424     // Attribute holds a references to another resource.
1425     public static final int TYPE_REFERENCE = 1 << 0;
1426 
1427     // Attribute holds a generic string.
1428     public static final int TYPE_STRING = 1 << 1;
1429 
1430     // Attribute holds an integer value.  ATTR_MIN and ATTR_MIN can
1431     // optionally specify a constrained range of possible integer values.
1432     public static final int TYPE_INTEGER = 1 << 2;
1433 
1434     // Attribute holds a boolean integer.
1435     public static final int TYPE_BOOLEAN = 1 << 3;
1436 
1437     // Attribute holds a color value.
1438     public static final int TYPE_COLOR = 1 << 4;
1439 
1440     // Attribute holds a floating point value.
1441     public static final int TYPE_FLOAT = 1 << 5;
1442 
1443     // Attribute holds a dimension value, such as "20px".
1444     public static final int TYPE_DIMENSION = 1 << 6;
1445 
1446     // Attribute holds a fraction value, such as "20%".
1447     public static final int TYPE_FRACTION = 1 << 7;
1448 
1449     // Attribute holds an enumeration.  The enumeration values are
1450     // supplied as additional entries in the map.
1451     public static final int TYPE_ENUM = 1 << 16;
1452 
1453     // Attribute holds a bitmaks of flags.  The flag bit values are
1454     // supplied as additional entries in the map.
1455     public static final int TYPE_FLAGS = 1 << 17;
1456     //    };
1457 
1458     // Enum of localization modes, for use with ATTR_L10N.
1459     // enum {
1460     public static final int L10N_NOT_REQUIRED = 0;
1461     public static final int L10N_SUGGESTED = 1;
1462     //    };
1463 
1464     // This mapping's value.
1465     public Res_value value;
1466 
ResTable_map(ByteBuffer buf, int offset)1467     public ResTable_map(ByteBuffer buf, int offset) {
1468       super(buf, offset);
1469 
1470       name = new ResTable_ref(buf, offset);
1471       value = new Res_value(buf, offset + ResTable_ref.SIZEOF);
1472     }
1473 
ResTable_map()1474     public ResTable_map() {
1475       super(null, 0);
1476       this.name = new ResTable_ref();
1477       this.value = new Res_value();
1478     }
1479 
1480     @Override
toString()1481     public String toString() {
1482       return "ResTable_map{" + "name=" + name + ", value=" + value + '}';
1483     }
1484   }
1485   ;
1486 
1487   /**
1488    * A package-id to package name mapping for any shared libraries used in this resource table. The
1489    * package-id's encoded in this resource table may be different than the id's assigned at runtime.
1490    * We must be able to translate the package-id's based on the package name.
1491    */
1492   static class ResTable_lib_header extends WithOffset {
1493     static final int SIZEOF = ResChunk_header.SIZEOF + 4;
1494 
1495     ResChunk_header header;
1496 
1497     // The number of shared libraries linked in this resource table.
1498     int count;
1499 
ResTable_lib_header(ByteBuffer buf, int offset)1500     ResTable_lib_header(ByteBuffer buf, int offset) {
1501       super(buf, offset);
1502 
1503       header = new ResChunk_header(buf, offset);
1504       count = buf.getInt(offset + ResChunk_header.SIZEOF);
1505     }
1506   }
1507   ;
1508 
1509   /** A shared library package-id to package name entry. */
1510   static class ResTable_lib_entry extends WithOffset {
1511     public static final int SIZEOF = 4 + 128 * SIZEOF_SHORT;
1512 
1513     // The package-id this shared library was assigned at build time.
1514     // We use a uint32 to keep the structure aligned on a uint32 boundary.
1515     int packageId;
1516 
1517     // The package name of the shared library. \0 terminated.
1518     char[] packageName = new char[128];
1519 
ResTable_lib_entry(ByteBuffer buf, int offset)1520     ResTable_lib_entry(ByteBuffer buf, int offset) {
1521       super(buf, offset);
1522 
1523       packageId = buf.getInt(offset);
1524 
1525       for (int i = 0; i < packageName.length; i++) {
1526         packageName[i] = buf.getChar(offset + 4 + i * SIZEOF_SHORT);
1527       }
1528     }
1529   }
1530   ;
1531 
1532   /**
1533    * A map that allows rewriting staged (non-finalized) resource ids to their finalized
1534    * counterparts.
1535    */
1536   static class ResTableStagedAliasHeader extends WithOffset {
1537     public static final int SIZEOF = ResChunk_header.SIZEOF + 4;
1538 
1539     ResChunk_header header;
1540 
1541     // The number of ResTableStagedAliasEntry that follow this header.
1542     int count;
1543 
ResTableStagedAliasHeader(ByteBuffer buf, int offset)1544     ResTableStagedAliasHeader(ByteBuffer buf, int offset) {
1545       super(buf, offset);
1546 
1547       header = new ResChunk_header(buf, offset);
1548       count = buf.getInt(offset + ResChunk_header.SIZEOF);
1549     }
1550   }
1551 
1552   /** Maps the staged (non-finalized) resource id to its finalized resource id. */
1553   static class ResTableStagedAliasEntry extends WithOffset {
1554     public static final int SIZEOF = 8;
1555 
1556     // The compile-time staged resource id to rewrite.
1557     int stagedResId;
1558 
1559     // The compile-time finalized resource id to which the staged resource id should be rewritten.
1560     int finalizedResId;
1561 
ResTableStagedAliasEntry(ByteBuffer buf, int offset)1562     ResTableStagedAliasEntry(ByteBuffer buf, int offset) {
1563       super(buf, offset);
1564 
1565       stagedResId = buf.getInt(offset);
1566       finalizedResId = buf.getInt(offset + 4);
1567     }
1568   }
1569 
1570   // struct alignas(uint32_t) Idmap_header {
1571   static class Idmap_header extends WithOffset {
1572     // Always 0x504D4449 ('IDMP')
1573     int magic;
1574 
1575     int version;
1576 
1577     int target_crc32;
1578     int overlay_crc32;
1579 
1580     final byte[] target_path = new byte[256];
1581     final byte[] overlay_path = new byte[256];
1582 
1583     short target_package_id;
1584     short type_count;
1585 
Idmap_header(ByteBuffer buf, int offset)1586     Idmap_header(ByteBuffer buf, int offset) {
1587       super(buf, offset);
1588 
1589       magic = buf.getInt(offset);
1590       version = buf.getInt(offset + 4);
1591       target_crc32 = buf.getInt(offset + 8);
1592       overlay_crc32 = buf.getInt(offset + 12);
1593 
1594       buf.get(target_path, offset + 16, 256);
1595       buf.get(overlay_path, offset + 16 + 256, 256);
1596 
1597       target_package_id = buf.getShort(offset + 16 + 256 + 256);
1598       type_count = buf.getShort(offset + 16 + 256 + 256 + 2);
1599     }
1600   } // __attribute__((packed));
1601 
1602   // struct alignas(uint32_t) IdmapEntry_header {
1603   static class IdmapEntry_header extends WithOffset {
1604     static final int SIZEOF = 2 * 4;
1605 
1606     short target_type_id;
1607     short overlay_type_id;
1608     short entry_count;
1609     short entry_id_offset;
1610     int entries[];
1611 
IdmapEntry_header(ByteBuffer buf, int offset)1612     IdmapEntry_header(ByteBuffer buf, int offset) {
1613       super(buf, offset);
1614 
1615       target_type_id = buf.getShort(offset);
1616       overlay_type_id = buf.getShort(offset + 2);
1617       entry_count = buf.getShort(offset + 4);
1618       entry_id_offset = buf.getShort(offset + 6);
1619       entries = new int[entry_count];
1620       for (int i = 0; i < entries.length; i++) {
1621         entries[i] = buf.getInt(offset + 8 + i * SIZEOF_INT);
1622       }
1623     }
1624   } // __attribute__((packed));
1625 
1626   private abstract static class FutureWriter<T> {
1627     protected final ByteBuffer buf;
1628     private final int position;
1629 
FutureWriter(ByteBuffer buf, int size)1630     public FutureWriter(ByteBuffer buf, int size) {
1631       this.buf = buf;
1632       this.position = buf.position();
1633       // Cast to Buffer because generated covariant return type that returns ByteBuffer is not
1634       // available on Java 8
1635       ((Buffer) buf).position(position + size);
1636     }
1637 
put(int position, T value)1638     protected abstract void put(int position, T value);
1639 
write(T value)1640     public void write(T value) {
1641       put(position, value);
1642     }
1643   }
1644 
1645   private static class IntWriter extends FutureWriter<Integer> {
IntWriter(ByteBuffer buf)1646     public IntWriter(ByteBuffer buf) {
1647       super(buf, 4);
1648     }
1649 
1650     @Override
put(int position, Integer value)1651     protected void put(int position, Integer value) {
1652       buf.putInt(position, value);
1653     }
1654   }
1655 
1656   private static class ShortWriter extends FutureWriter<Short> {
ShortWriter(ByteBuffer buf)1657     public ShortWriter(ByteBuffer buf) {
1658       super(buf, 2);
1659     }
1660 
1661     @Override
put(int position, Short value)1662     protected void put(int position, Short value) {
1663       buf.putShort(position, value);
1664     }
1665   }
1666 }
1667