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