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