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