| package org.robolectric.res.android; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static org.robolectric.res.android.Errors.BAD_TYPE; |
| import static org.robolectric.res.android.Errors.NO_ERROR; |
| import static org.robolectric.res.android.Util.ALOGW; |
| import static org.robolectric.res.android.Util.SIZEOF_INT; |
| import static org.robolectric.res.android.Util.SIZEOF_SHORT; |
| import static org.robolectric.res.android.Util.dtohl; |
| import static org.robolectric.res.android.Util.dtohs; |
| |
| import java.nio.Buffer; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer; |
| |
| // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp |
| // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h |
| public class ResourceTypes { |
| public static final String ANDROID_NS = "http://schemas.android.com/apk/res/android"; |
| public static final String AUTO_NS = "http://schemas.android.com/apk/res-auto"; |
| |
| static final int kIdmapMagic = 0x504D4449; |
| static final int kIdmapCurrentVersion = 0x00000001; |
| |
| static int validate_chunk(ResChunk_header chunk, |
| int minSize, |
| int dataLen, |
| String name) |
| { |
| final short headerSize = dtohs(chunk.headerSize); |
| final int size = dtohl(chunk.size); |
| |
| if (headerSize >= minSize) { |
| if (headerSize <= size) { |
| if (((headerSize|size)&0x3) == 0) { |
| if (size <= dataLen) { |
| return NO_ERROR; |
| } |
| ALOGW("%s data size 0x%x extends beyond resource end.", |
| name, size /*, (dataEnd-((const uint8_t*)chunk))*/); |
| return BAD_TYPE; |
| } |
| ALOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.", |
| name, (int)size, (int)headerSize); |
| return BAD_TYPE; |
| } |
| ALOGW("%s size 0x%x is smaller than header size 0x%x.", |
| name, size, headerSize); |
| return BAD_TYPE; |
| } |
| ALOGW("%s header size 0x%04x is too small.", |
| name, headerSize); |
| return BAD_TYPE; |
| } |
| |
| static class WithOffset { |
| private final ByteBuffer buf; |
| private final int offset; |
| |
| WithOffset(ByteBuffer buf, int offset) { |
| this.buf = buf; |
| this.offset = offset; |
| } |
| |
| public final ByteBuffer myBuf() { |
| return buf; |
| } |
| |
| public final int myOffset() { |
| return offset; |
| } |
| |
| @Override |
| public String toString() { |
| return "{buf+" + offset + '}'; |
| } |
| } |
| |
| /** ******************************************************************** |
| * Base Types |
| * |
| * These are standard types that are shared between multiple specific |
| * resource types. |
| * |
| *********************************************************************** */ |
| |
| /** |
| * Header that appears at the front of every data chunk in a resource. |
| */ |
| public static class ResChunk_header extends WithOffset |
| { |
| static int SIZEOF = 8; |
| |
| // Type identifier for this chunk. The meaning of this value depends |
| // on the containing chunk. |
| final short type; |
| |
| // Size of the chunk header (in bytes). Adding this value to |
| // the address of the chunk allows you to find its associated data |
| // (if any). |
| final short headerSize; |
| |
| // Total size of this chunk (in bytes). This is the chunkSize plus |
| // the size of any data associated with the chunk. Adding this value |
| // to the chunk allows you to completely skip its contents (including |
| // any child chunks). If this value is the same as chunkSize, there is |
| // no data associated with the chunk. |
| final int size; |
| |
| public ResChunk_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| this.type = buf.getShort(offset); |
| this.headerSize = buf.getShort(offset + 2); |
| this.size = buf.getInt(offset + 4); |
| } |
| |
| public static void write(ByteBuffer buf, short type, Runnable header, Runnable contents) { |
| int startPos = buf.position(); |
| buf.putShort(type); |
| ShortWriter headerSize = new ShortWriter(buf); |
| IntWriter size = new IntWriter(buf); |
| |
| header.run(); |
| headerSize.write((short) (buf.position() - startPos)); |
| |
| contents.run(); |
| |
| // pad to next int boundary |
| int len = buf.position() - startPos; |
| while ((len & 0x3) != 0) { |
| buf.put((byte) 0); |
| len++; |
| } |
| size.write(len); |
| } |
| } |
| |
| public static final int RES_NULL_TYPE = 0x0000; |
| public static final int RES_STRING_POOL_TYPE = 0x0001; |
| public static final int RES_TABLE_TYPE = 0x0002; |
| public static final int RES_XML_TYPE = 0x0003; |
| |
| // Chunk types in RES_XML_TYPE |
| public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100; |
| public static final int RES_XML_START_NAMESPACE_TYPE= 0x0100; |
| public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101; |
| public static final int RES_XML_START_ELEMENT_TYPE = 0x0102; |
| public static final int RES_XML_END_ELEMENT_TYPE = 0x0103; |
| public static final int RES_XML_CDATA_TYPE = 0x0104; |
| public static final int RES_XML_LAST_CHUNK_TYPE = 0x017f; |
| // This contains a uint32_t array mapping strings in the string |
| // pool back to resource identifiers. It is optional. |
| public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180; |
| |
| // Chunk types in RES_TABLE_TYPE |
| public static final int RES_TABLE_PACKAGE_TYPE = 0x0200; |
| public static final int RES_TABLE_TYPE_TYPE = 0x0201; |
| public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; |
| public static final int RES_TABLE_LIBRARY_TYPE = 0x0203; |
| public static final int RES_TABLE_STAGED_ALIAS_TYPE = 0x0206; |
| |
| /** |
| * Macros for building/splitting resource identifiers. |
| */ |
| //#define Res_VALIDID(resid) (resid != 0) |
| //#define Res_CHECKID(resid) ((resid&0xFFFF0000) != 0) |
| //#define Res_MAKEID(package, type, entry) \ |
| //(((package+1)<<24) | (((type+1)&0xFF)<<16) | (entry&0xFFFF)) |
| //#define Res_GETPACKAGE(id) ((id>>24)-1) |
| //#define Res_GETTYPE(id) (((id>>16)&0xFF)-1) |
| //#define Res_GETENTRY(id) (id&0xFFFF) |
| |
| //#define Res_INTERNALID(resid) ((resid&0xFFFF0000) != 0 && (resid&0xFF0000) == 0) |
| private static int Res_MAKEINTERNAL(int entry) { |
| return (0x01000000 | (entry & 0xFFFF)); |
| } |
| //#define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF)) |
| |
| // static const size_t Res_MAXPACKAGE = 255; |
| // static const size_t Res_MAXTYPE = 255; |
| |
| /** |
| * Representation of a value in a resource, supplying type |
| * information. |
| */ |
| public static class Res_value |
| { |
| static final int SIZEOF = 8; |
| |
| // Number of bytes in this structure. |
| final short size; |
| |
| // Always set to 0. |
| // byte res0; |
| |
| // Type of the data value. |
| // enum { |
| // The 'data' is either 0 or 1, specifying this resource is either |
| // undefined or empty, respectively. |
| public static final int TYPE_NULL = 0x00; |
| // The 'data' holds a ResTable_ref, a reference to another resource |
| // table entry. |
| public static final int TYPE_REFERENCE = 0x01; |
| // The 'data' holds an attribute resource identifier. |
| public static final int TYPE_ATTRIBUTE = 0x02; |
| // The 'data' holds an index into the containing resource table's |
| // global value string pool. |
| public static final int TYPE_STRING = 0x03; |
| // The 'data' holds a single-precision floating point number. |
| public static final int TYPE_FLOAT = 0x04; |
| // The 'data' holds a complex number encoding a dimension value, |
| // such as "100in". |
| public static final int TYPE_DIMENSION = 0x05; |
| // The 'data' holds a complex number encoding a fraction of a |
| // container. |
| public static final int TYPE_FRACTION = 0x06; |
| // The 'data' holds a dynamic ResTable_ref, which needs to be |
| // resolved before it can be used like a TYPE_REFERENCE. |
| public static final int TYPE_DYNAMIC_REFERENCE = 0x07; |
| // The 'data' holds an attribute resource identifier, which needs to be resolved |
| // before it can be used like a TYPE_ATTRIBUTE. |
| public static final int TYPE_DYNAMIC_ATTRIBUTE = 0x08; |
| |
| // Beginning of integer flavors... |
| public static final int TYPE_FIRST_INT = 0x10; |
| |
| // The 'data' is a raw integer value of the form n..n. |
| public static final int TYPE_INT_DEC = 0x10; |
| // The 'data' is a raw integer value of the form 0xn..n. |
| public static final int TYPE_INT_HEX = 0x11; |
| // The 'data' is either 0 or 1, for input "false" or "true" respectively. |
| public static final int TYPE_INT_BOOLEAN = 0x12; |
| |
| // Beginning of color integer flavors... |
| public static final int TYPE_FIRST_COLOR_INT = 0x1c; |
| |
| // The 'data' is a raw integer value of the form #aarrggbb. |
| public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; |
| // The 'data' is a raw integer value of the form #rrggbb. |
| public static final int TYPE_INT_COLOR_RGB8 = 0x1d; |
| // The 'data' is a raw integer value of the form #argb. |
| public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; |
| // The 'data' is a raw integer value of the form #rgb. |
| public static final int TYPE_INT_COLOR_RGB4 = 0x1f; |
| |
| // ...end of integer flavors. |
| public static final int TYPE_LAST_COLOR_INT = 0x1f; |
| |
| // ...end of integer flavors. |
| public static final int TYPE_LAST_INT = 0x1f; |
| // }; |
| |
| public final byte dataType; |
| |
| // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION) |
| // enum { |
| // Where the unit type information is. This gives us 16 possible |
| // types, as defined below. |
| public static final int COMPLEX_UNIT_SHIFT = 0; |
| public static final int COMPLEX_UNIT_MASK = 0xf; |
| |
| // TYPE_DIMENSION: Value is raw pixels. |
| public static final int COMPLEX_UNIT_PX = 0; |
| // TYPE_DIMENSION: Value is Device Independent Pixels. |
| public static final int COMPLEX_UNIT_DIP = 1; |
| // TYPE_DIMENSION: Value is a Scaled device independent Pixels. |
| public static final int COMPLEX_UNIT_SP = 2; |
| // TYPE_DIMENSION: Value is in points. |
| public static final int COMPLEX_UNIT_PT = 3; |
| // TYPE_DIMENSION: Value is in inches. |
| public static final int COMPLEX_UNIT_IN = 4; |
| // TYPE_DIMENSION: Value is in millimeters. |
| public static final int COMPLEX_UNIT_MM = 5; |
| |
| // TYPE_FRACTION: A basic fraction of the overall size. |
| public static final int COMPLEX_UNIT_FRACTION = 0; |
| // TYPE_FRACTION: A fraction of the parent size. |
| public static final int COMPLEX_UNIT_FRACTION_PARENT = 1; |
| |
| // Where the radix information is, telling where the decimal place |
| // appears in the mantissa. This give us 4 possible fixed point |
| // representations as defined below. |
| public static final int COMPLEX_RADIX_SHIFT = 4; |
| public static final int COMPLEX_RADIX_MASK = 0x3; |
| |
| // The mantissa is an integral number -- i.e., 0xnnnnnn.0 |
| public static final int COMPLEX_RADIX_23p0 = 0; |
| // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn |
| public static final int COMPLEX_RADIX_16p7 = 1; |
| // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn |
| public static final int COMPLEX_RADIX_8p15 = 2; |
| // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn |
| public static final int COMPLEX_RADIX_0p23 = 3; |
| |
| // Where the actual value is. This gives us 23 bits of |
| // precision. The top bit is the sign. |
| public static final int COMPLEX_MANTISSA_SHIFT = 8; |
| public static final int COMPLEX_MANTISSA_MASK = 0xffffff; |
| // }; |
| |
| // Possible data values for TYPE_NULL. |
| // enum { |
| // The value is not defined. |
| public static final int DATA_NULL_UNDEFINED = 0; |
| // The value is explicitly defined as empty. |
| public static final int DATA_NULL_EMPTY = 1; |
| // }; |
| |
| public static final Res_value NULL_VALUE = new Res_value((byte) TYPE_NULL, DATA_NULL_UNDEFINED); |
| |
| // The data for this item, as interpreted according to dataType. |
| // typedef uint32_t data_type; |
| public final int data; |
| |
| public Res_value() { |
| this.size = 0; |
| // this.res0 = 0; |
| this.dataType = 0; |
| this.data = 0; |
| } |
| |
| public Res_value(ByteBuffer buf, int offset) { |
| this.size = buf.getShort(offset); |
| byte res0 = buf.get(offset + 2); |
| this.dataType = buf.get(offset + 3); |
| this.data = buf.getInt(offset + 4); |
| |
| if (res0 != 0) { |
| throw new IllegalStateException("res0 != 0 (" + res0 + ")"); |
| } |
| } |
| |
| public Res_value(Res_value other) { |
| this.size = other.size; |
| // this.res0 = other.res0; |
| this.dataType = other.dataType; |
| this.data = other.data; |
| } |
| |
| public Res_value(byte dataType, int data) { |
| this.size = SIZEOF; |
| // this.res0 = 0; |
| this.dataType = dataType; |
| this.data = data; |
| } |
| |
| public static void write(ByteBuffer buf, int dataType, int data) { |
| buf.putShort((short) SIZEOF); // size |
| buf.put((byte) 0); // res0 |
| buf.put((byte) dataType); // dataType |
| buf.putInt(data); // data |
| } |
| |
| public Res_value withType(byte dataType) { |
| return new Res_value(dataType, data); |
| } |
| |
| public Res_value withData(int data) { |
| return new Res_value(dataType, data); |
| } |
| |
| // public void copyFrom_dtoh(Res_value other) { |
| // this.size = other.size; |
| // // this.res0 = other.res0; |
| // this.dataType = other.dataType; |
| // this.data = other.data; |
| // } |
| |
| public Res_value copy() { |
| return new Res_value(this); |
| } |
| |
| @Override |
| public String toString() { |
| return "Res_value{dataType=" + dataType + ", data=" + data + '}'; |
| } |
| } |
| |
| /** |
| * This is a reference to a unique entry (a ResTable_entry structure) |
| * in a resource table. The value is structured as: 0xpptteeee, |
| * where pp is the package index, tt is the type index in that |
| * package, and eeee is the entry index in that type. The package |
| * and type values start at 1 for the first item, to help catch cases |
| * where they have not been supplied. |
| */ |
| public static class ResTable_ref |
| { |
| public static final int SIZEOF = 4; |
| |
| public int ident; |
| |
| public ResTable_ref(ByteBuffer buf, int offset) { |
| ident = buf.getInt(offset); |
| } |
| |
| public ResTable_ref() { |
| ident = 0; |
| } |
| |
| @Override |
| public String toString() { |
| return "ResTable_ref{ident=" + ident + '}'; |
| } |
| }; |
| |
| /** |
| * Reference to a string in a string pool. |
| */ |
| public static class ResStringPool_ref |
| { |
| public static final int SIZEOF = 4; |
| |
| // Index into the string pool table (uint32_t-offset from the indices |
| // immediately after ResStringPool_header) at which to find the location |
| // of the string data in the pool. |
| public final int index; |
| |
| public ResStringPool_ref(ByteBuffer buf, int offset) { |
| this.index = buf.getInt(offset); |
| } |
| |
| public static void write(ByteBuffer buf, int value) { |
| buf.putInt(value); |
| } |
| |
| @Override |
| public String toString() { |
| return "ResStringPool_ref{index=" + index + '}'; |
| } |
| } |
| |
| /** ******************************************************************** |
| * String Pool |
| * |
| * A set of strings that can be references by others through a |
| * ResStringPool_ref. |
| * |
| *********************************************************************** */ |
| |
| |
| /** |
| * Definition for a pool of strings. The data of this chunk is an |
| * array of uint32_t providing indices into the pool, relative to |
| * stringsStart. At stringsStart are all of the UTF-16 strings |
| * concatenated together; each starts with a uint16_t of the string's |
| * length and each ends with a 0x0000 terminator. If a string is > |
| * 32767 characters, the high bit of the length is set meaning to take |
| * those 15 bits as a high word and it will be followed by another |
| * uint16_t containing the low word. |
| * |
| * If styleCount is not zero, then immediately following the array of |
| * uint32_t indices into the string table is another array of indices |
| * into a style table starting at stylesStart. Each entry in the |
| * style table is an array of ResStringPool_span structures. |
| */ |
| public static class ResStringPool_header extends WithOffset |
| { |
| public static final int SIZEOF = ResChunk_header.SIZEOF + 20; |
| |
| final ResChunk_header header; |
| |
| // Number of strings in this pool (number of uint32_t indices that follow |
| // in the data). |
| final int stringCount; |
| |
| // Number of style span arrays in the pool (number of uint32_t indices |
| // follow the string indices). |
| final int styleCount; |
| |
| // Flags. |
| // enum { |
| // If set, the string index is sorted by the string values (based |
| // on strcmp16()). |
| public static final int SORTED_FLAG = 1<<0; |
| |
| // String pool is encoded in UTF-8 |
| public static final int UTF8_FLAG = 1<<8; |
| // }; |
| final int flags; |
| |
| // Index from header of the string data. |
| final int stringsStart; |
| |
| // Index from header of the style data. |
| final int stylesStart; |
| |
| public ResStringPool_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| this.header = new ResChunk_header(buf, offset); |
| this.stringCount = buf.getInt(offset + ResChunk_header.SIZEOF); |
| this.styleCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4); |
| this.flags = buf.getInt(offset + ResChunk_header.SIZEOF + 8); |
| this.stringsStart = buf.getInt(offset + ResChunk_header.SIZEOF + 12); |
| this.stylesStart = buf.getInt(offset + ResChunk_header.SIZEOF + 16); |
| } |
| |
| public int getByte(int i) { |
| return myBuf().get(myOffset() + i); |
| } |
| |
| public int getShort(int i) { |
| return myBuf().getShort(myOffset() + i); |
| } |
| |
| public static class Writer { |
| |
| private final List<String> strings = new ArrayList<>(); |
| private final List<byte[]> stringsAsBytes = new ArrayList<>(); |
| private final Map<String, Integer> stringIds = new HashMap<>(); |
| |
| private boolean frozen; |
| |
| public int string(String s) { |
| if (frozen) { |
| throw new IllegalStateException("string pool is frozen!"); |
| } |
| |
| if (s == null) { |
| return -1; |
| } |
| |
| Integer id = stringIds.get(s); |
| if (id == null) { |
| id = strings.size(); |
| strings.add(s); |
| stringsAsBytes.add(s.getBytes(UTF_8)); |
| stringIds.put(s, id); |
| } |
| return id; |
| } |
| |
| public int uniqueString(String s) { |
| if (frozen) { |
| throw new IllegalStateException("string pool is frozen!"); |
| } |
| |
| if (s == null) { |
| return -1; |
| } |
| |
| int id = strings.size(); |
| strings.add(s); |
| stringsAsBytes.add(s.getBytes(UTF_8)); |
| return id; |
| } |
| |
| public void write(ByteBuffer buf) { |
| freeze(); |
| |
| ResChunk_header.write(buf, (short) RES_STRING_POOL_TYPE, () -> { |
| // header |
| int startPos = buf.position(); |
| int stringCount = strings.size(); |
| |
| // begin string pool... |
| buf.putInt(stringCount); // stringCount |
| buf.putInt(0); // styleCount |
| buf.putInt(UTF8_FLAG); // flags |
| IntWriter stringStart = new IntWriter(buf); |
| buf.putInt(0); // stylesStart |
| |
| stringStart.write(buf.position() - startPos); |
| }, () -> { |
| // contents |
| int stringOffset = /*buf.position() + */8 + 4 * stringsAsBytes.size(); |
| for (int i = 0; i < stringsAsBytes.size(); i++) { |
| String string = strings.get(i); |
| byte[] bytes = stringsAsBytes.get(i); |
| buf.putInt(stringOffset); |
| stringOffset += lenLen(string.length()) + lenLen(bytes.length) + bytes.length + 1; |
| } |
| |
| for (int i = 0; i < stringsAsBytes.size(); i++) { |
| // number of chars |
| writeLen(buf, strings.get(i).length()); |
| |
| // number of bytes |
| writeLen(buf, stringsAsBytes.get(i).length); |
| |
| // bytes |
| buf.put(stringsAsBytes.get(i)); |
| // null terminator |
| buf.put((byte) '\0'); |
| } |
| }); |
| } |
| |
| private int lenLen(int length) { |
| return length > 0x7f ? 2 : 1; |
| } |
| |
| private void writeLen(ByteBuffer buf, int length) { |
| if (length <= 0x7f) { |
| buf.put((byte) length); |
| } else { |
| buf.put((byte) ((length >> 8) | 0x80)); |
| buf.put((byte) (length & 0x7f)); |
| } |
| } |
| |
| public void freeze() { |
| frozen = true; |
| } |
| } |
| } |
| |
| /** |
| * This structure defines a span of style information associated with |
| * a string in the pool. |
| */ |
| public static class ResStringPool_span extends WithOffset |
| { |
| public static final int SIZEOF = ResStringPool_ref.SIZEOF + 8; |
| |
| // enum { |
| public static final int END = 0xFFFFFFFF; |
| // }; |
| |
| // This is the name of the span -- that is, the name of the XML |
| // tag that defined it. The special value END (0xFFFFFFFF) indicates |
| // the end of an array of spans. |
| public final ResStringPool_ref name; |
| |
| // The range of characters in the string that this span applies to. |
| final int firstChar; |
| final int lastChar; |
| |
| public ResStringPool_span(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| name = new ResStringPool_ref(buf, offset); |
| firstChar = buf.getInt(offset + ResStringPool_ref.SIZEOF); |
| lastChar = buf.getInt(offset + ResStringPool_ref.SIZEOF + 4); |
| } |
| |
| public boolean isEnd() { |
| return name.index == END && firstChar == END && lastChar == END; |
| } |
| }; |
| |
| |
| /** ******************************************************************** |
| * XML Tree |
| * |
| * Binary representation of an XML document. This is designed to |
| * express everything in an XML document, in a form that is much |
| * easier to parse on the device. |
| * |
| *********************************************************************** */ |
| |
| /** |
| * XML tree header. This appears at the front of an XML tree, |
| * describing its content. It is followed by a flat array of |
| * ResXMLTree_node structures; the hierarchy of the XML document |
| * is described by the occurrance of RES_XML_START_ELEMENT_TYPE |
| * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array. |
| */ |
| public static class ResXMLTree_header extends WithOffset |
| { |
| public final ResChunk_header header; |
| |
| ResXMLTree_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| header = new ResChunk_header(buf, offset); |
| } |
| |
| public static void write(ByteBuffer buf, Writer resStringPoolWriter, Runnable contents) { |
| ResChunk_header.write(buf, (short) RES_XML_TYPE, ()-> {}, () -> { |
| resStringPoolWriter.write(buf); |
| contents.run(); |
| }); |
| } |
| } |
| |
| /** |
| * Basic XML tree node. A single item in the XML document. Extended info |
| * about the node can be found after header.headerSize. |
| */ |
| public static class ResXMLTree_node extends WithOffset |
| { |
| final ResChunk_header header; |
| |
| // Line number in original source file at which this element appeared. |
| final int lineNumber; |
| |
| // Optional XML comment that was associated with this element; -1 if none. |
| final ResStringPool_ref comment; |
| |
| ResXMLTree_node(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| this.header = new ResChunk_header(buf, offset); |
| this.lineNumber = buf.getInt(offset + ResChunk_header.SIZEOF); |
| this.comment = new ResStringPool_ref(buf, offset + 12); |
| } |
| |
| ResXMLTree_node(ByteBuffer buf, ResChunk_header header) { |
| super(buf, header.myOffset()); |
| |
| this.header = header; |
| this.lineNumber = buf.getInt(myOffset() + ResChunk_header.SIZEOF); |
| this.comment = new ResStringPool_ref(buf, myOffset() + ResChunk_header.SIZEOF + 4); |
| } |
| |
| public static void write(ByteBuffer buf, int type, Runnable contents) { |
| ResChunk_header.write(buf, (short) type, () -> { |
| buf.putInt(-1); // lineNumber |
| ResStringPool_ref.write(buf, -1); // comment |
| }, contents); |
| } |
| }; |
| |
| /** |
| * Extended XML tree node for CDATA tags -- includes the CDATA string. |
| * Appears header.headerSize bytes after a ResXMLTree_node. |
| */ |
| static class ResXMLTree_cdataExt |
| { |
| // The raw CDATA character data. |
| final ResStringPool_ref data; |
| |
| // The typed value of the character data if this is a CDATA node. |
| final Res_value typedData; |
| |
| public ResXMLTree_cdataExt(ByteBuffer buf, int offset) { |
| this.data = new ResStringPool_ref(buf, offset); |
| |
| int dataType = buf.getInt(offset + 4); |
| int data = buf.getInt(offset + 8); |
| this.typedData = new Res_value((byte) dataType, data); |
| } |
| }; |
| |
| /** |
| * Extended XML tree node for namespace start/end nodes. |
| * Appears header.headerSize bytes after a ResXMLTree_node. |
| */ |
| static class ResXMLTree_namespaceExt |
| { |
| // The prefix of the namespace. |
| final ResStringPool_ref prefix; |
| |
| // The URI of the namespace. |
| final ResStringPool_ref uri; |
| |
| public ResXMLTree_namespaceExt(ByteBuffer buf, int offset) { |
| this.prefix = new ResStringPool_ref(buf, offset); |
| this.uri = new ResStringPool_ref(buf, offset + 4); |
| } |
| }; |
| |
| /** |
| * Extended XML tree node for element start/end nodes. |
| * Appears header.headerSize bytes after a ResXMLTree_node. |
| */ |
| public static class ResXMLTree_endElementExt |
| { |
| static final int SIZEOF = 8; |
| |
| // String of the full namespace of this element. |
| final ResStringPool_ref ns; |
| |
| // String name of this node if it is an ELEMENT; the raw |
| // character data if this is a CDATA node. |
| final ResStringPool_ref name; |
| |
| public ResXMLTree_endElementExt(ByteBuffer buf, int offset) { |
| this.ns = new ResStringPool_ref(buf, offset); |
| this.name = new ResStringPool_ref(buf, offset + ResStringPool_ref.SIZEOF); |
| } |
| |
| public static class Writer { |
| private final ByteBuffer buf; |
| private final int ns; |
| private final int name; |
| |
| public Writer(ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, |
| String ns, String name) { |
| this.buf = buf; |
| this.ns = resStringPoolWriter.string(ns); |
| this.name = resStringPoolWriter.string(name); |
| } |
| |
| public void write() { |
| ResStringPool_ref.write(buf, ns); |
| ResStringPool_ref.write(buf, name); |
| } |
| } |
| }; |
| |
| /** |
| * Extended XML tree node for start tags -- includes attribute |
| * information. |
| * Appears header.headerSize bytes after a ResXMLTree_node. |
| */ |
| public static class ResXMLTree_attrExt extends WithOffset |
| { |
| private final ByteBuffer buf; |
| |
| // String of the full namespace of this element. |
| final ResStringPool_ref ns; |
| |
| // String name of this node if it is an ELEMENT; the raw |
| // character data if this is a CDATA node. |
| final ResStringPool_ref name; |
| |
| // Byte offset from the start of this structure where the attributes start. |
| final short attributeStart; |
| |
| // Size of the ResXMLTree_attribute structures that follow. |
| final short attributeSize; |
| |
| // Number of attributes associated with an ELEMENT. These are |
| // available as an array of ResXMLTree_attribute structures |
| // immediately following this node. |
| final short attributeCount; |
| |
| // Index (1-based) of the "id" attribute. 0 if none. |
| final short idIndex; |
| |
| // Index (1-based) of the "class" attribute. 0 if none. |
| final short classIndex; |
| |
| // Index (1-based) of the "style" attribute. 0 if none. |
| final short styleIndex; |
| |
| public ResXMLTree_attrExt(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| this.buf = buf; |
| |
| this.ns = new ResStringPool_ref(buf, offset); |
| this.name = new ResStringPool_ref(buf, offset + 4); |
| this.attributeStart = buf.getShort(offset + 8); |
| this.attributeSize = buf.getShort(offset + 10); |
| this.attributeCount = buf.getShort(offset + 12); |
| this.idIndex = buf.getShort(offset + 14); |
| this.classIndex = buf.getShort(offset + 16); |
| this.styleIndex = buf.getShort(offset + 18); |
| } |
| |
| ResXMLTree_attribute attributeAt(int idx) { |
| return new ResXMLTree_attribute(buf, |
| myOffset() + dtohs(attributeStart) + dtohs(attributeSize) * idx); |
| } |
| |
| public static class Writer { |
| private final ByteBuffer buf; |
| private final int ns; |
| private final int name; |
| |
| private short idIndex; |
| private short classIndex; |
| private short styleIndex; |
| |
| private final List<Attr> attrs = new ArrayList<>(); |
| |
| public Writer(ByteBuffer buf, ResStringPool_header.Writer resStringPoolWriter, |
| String ns, String name) { |
| this.buf = buf; |
| this.ns = resStringPoolWriter.string(ns); |
| this.name = resStringPoolWriter.string(name); |
| } |
| |
| public void attr(int ns, int name, int value, Res_value resValue, String fullName) { |
| attrs.add(new Attr(ns, name, value, resValue, fullName)); |
| } |
| |
| public void write() { |
| int startPos = buf.position(); |
| int attributeCount = attrs.size(); |
| |
| ResStringPool_ref.write(buf, ns); |
| ResStringPool_ref.write(buf, name); |
| ShortWriter attributeStartWriter = new ShortWriter(buf); |
| buf.putShort((short) ResXMLTree_attribute.SIZEOF); // attributeSize |
| buf.putShort((short) attributeCount); // attributeCount |
| ShortWriter idIndexWriter = new ShortWriter(buf); |
| ShortWriter classIndexWriter = new ShortWriter(buf); |
| ShortWriter styleIndexWriter = new ShortWriter(buf); |
| |
| attributeStartWriter.write((short) (buf.position() - startPos)); |
| for (int i = 0; i < attributeCount; i++) { |
| Attr attr = attrs.get(i); |
| |
| switch (attr.fullName) { |
| case ":id": |
| idIndex = (short) (i + 1); |
| break; |
| case ":style": |
| styleIndex = (short) (i + 1); |
| break; |
| case ":class": |
| classIndex = (short) (i + 1); |
| break; |
| } |
| |
| attr.write(buf); |
| } |
| |
| idIndexWriter.write(idIndex); |
| classIndexWriter.write(classIndex); |
| styleIndexWriter.write(styleIndex); |
| } |
| |
| private static class Attr { |
| final int ns; |
| final int name; |
| final int value; |
| final int resValueDataType; |
| final int resValueData; |
| final String fullName; |
| |
| public Attr(int ns, int name, int value, Res_value resValue, String fullName) { |
| this.ns = ns; |
| this.name = name; |
| this.value = value; |
| this.resValueDataType = resValue.dataType; |
| this.resValueData = resValue.data; |
| this.fullName = fullName; |
| } |
| |
| public void write(ByteBuffer buf) { |
| ResXMLTree_attribute.write(buf, ns, name, value, resValueDataType, resValueData); |
| } |
| } |
| } |
| }; |
| |
| static class ResXMLTree_attribute |
| { |
| public static final int SIZEOF = 12+ ResourceTypes.Res_value.SIZEOF; |
| |
| // Namespace of this attribute. |
| final ResStringPool_ref ns; |
| |
| // Name of this attribute. |
| final ResStringPool_ref name; |
| |
| // The original raw string value of this attribute. |
| final ResStringPool_ref rawValue; |
| |
| // Processesd typed value of this attribute. |
| final Res_value typedValue; |
| |
| public ResXMLTree_attribute(ByteBuffer buf, int offset) { |
| this.ns = new ResStringPool_ref(buf, offset); |
| this.name = new ResStringPool_ref(buf, offset + 4); |
| this.rawValue = new ResStringPool_ref(buf, offset + 8); |
| this.typedValue = new Res_value(buf, offset + 12); |
| } |
| |
| public static void write(ByteBuffer buf, int ns, int name, int value, int resValueDataType, |
| int resValueData) { |
| ResStringPool_ref.write(buf, ns); |
| ResStringPool_ref.write(buf, name); |
| ResStringPool_ref.write(buf, value); |
| ResourceTypes.Res_value.write(buf, resValueDataType, resValueData); |
| } |
| }; |
| |
| /** ******************************************************************** |
| * RESOURCE TABLE |
| * |
| *********************************************************************** */ |
| |
| /** |
| * Header for a resource table. Its data contains a series of |
| * additional chunks: |
| * * A ResStringPool_header containing all table values. This string pool |
| * contains all of the string values in the entire resource table (not |
| * the names of entries or type identifiers however). |
| * * One or more ResTable_package chunks. |
| * |
| * Specific entries within a resource table can be uniquely identified |
| * with a single integer as defined by the ResTable_ref structure. |
| */ |
| static class ResTable_header extends WithOffset |
| { |
| public static final int SIZEOF = ResChunk_header.SIZEOF + 4; |
| |
| final ResChunk_header header; |
| |
| // The number of ResTable_package structures. |
| final int packageCount; |
| |
| public ResTable_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| this.header = new ResChunk_header(buf, offset); |
| this.packageCount = buf.getInt(offset + ResChunk_header.SIZEOF); |
| } |
| } |
| |
| /** |
| * A collection of resource data types within a package. Followed by |
| * one or more ResTable_type and ResTable_typeSpec structures containing the |
| * entry values for each resource type. |
| */ |
| static class ResTable_package extends WithOffset |
| { |
| public static final int SIZEOF = ResChunk_header.SIZEOF + 4 + 128 + 20; |
| |
| final ResChunk_header header; |
| |
| // If this is a base package, its ID. Package IDs start |
| // at 1 (corresponding to the value of the package bits in a |
| // resource identifier). 0 means this is not a base package. |
| public final int id; |
| |
| // Actual name of this package, \0-terminated. |
| public final char[] name = new char[128]; |
| |
| // Offset to a ResStringPool_header defining the resource |
| // type symbol table. If zero, this package is inheriting from |
| // another base package (overriding specific values in it). |
| public final int typeStrings; |
| |
| // Last index into typeStrings that is for public use by others. |
| public final int lastPublicType; |
| |
| // Offset to a ResStringPool_header defining the resource |
| // key symbol table. If zero, this package is inheriting from |
| // another base package (overriding specific values in it). |
| public final int keyStrings; |
| |
| // Last index into keyStrings that is for public use by others. |
| public final int lastPublicKey; |
| |
| public final int typeIdOffset; |
| |
| public ResTable_package(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| header = new ResChunk_header(buf, offset); |
| id = buf.getInt(offset + ResChunk_header.SIZEOF); |
| for (int i = 0; i < name.length; i++) { |
| name[i] = buf.getChar(offset + ResChunk_header.SIZEOF + 4 + i * 2); |
| } |
| typeStrings = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256); |
| lastPublicType = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 4); |
| keyStrings = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 8); |
| lastPublicKey = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 12); |
| typeIdOffset = buf.getInt(offset + ResChunk_header.SIZEOF + 4 + 256 + 16); |
| } |
| }; |
| |
| // The most specific locale can consist of: |
| // |
| // - a 3 char language code |
| // - a 3 char region code prefixed by a 'r' |
| // - a 4 char script code prefixed by a 's' |
| // - a 8 char variant code prefixed by a 'v' |
| // |
| // each separated by a single char separator, which sums up to a total of 24 |
| // chars, (25 include the string terminator). Numbering system specificator, |
| // if present, can add up to 14 bytes (-u-nu-xxxxxxxx), giving 39 bytes, |
| // or 40 bytes to make it 4 bytes aligned. |
| public static final int RESTABLE_MAX_LOCALE_LEN = 40; |
| |
| /** |
| * A specification of the resources defined by a particular type. |
| * |
| * There should be one of these chunks for each resource type. |
| * |
| * This structure is followed by an array of integers providing the set of |
| * configuration change flags (ResTable_config::CONFIG_*) that have multiple |
| * resources for that configuration. In addition, the high bit is set if that |
| * resource has been made public. |
| */ |
| static class ResTable_typeSpec extends WithOffset |
| { |
| public static final int SIZEOF = ResChunk_header.SIZEOF + 8; |
| |
| final ResChunk_header header; |
| |
| // The type identifier this chunk is holding. Type IDs start |
| // at 1 (corresponding to the value of the type bits in a |
| // resource identifier). 0 is invalid. |
| final byte id; |
| |
| // Must be 0. |
| final byte res0; |
| // Must be 0. |
| final short res1; |
| |
| // Number of uint32_t entry configuration masks that follow. |
| final int entryCount; |
| |
| //enum : uint32_t { |
| // Additional flag indicating an entry is public. |
| static final int SPEC_PUBLIC = 0x40000000; |
| |
| // Additional flag indicating an entry is overlayable at runtime. |
| // Added in Android-P. |
| static final int SPEC_OVERLAYABLE = 0x80000000; |
| // }; |
| |
| public ResTable_typeSpec(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| header = new ResChunk_header(buf, offset); |
| id = buf.get(offset + ResChunk_header.SIZEOF); |
| res0 = buf.get(offset + ResChunk_header.SIZEOF + 1); |
| res1 = buf.getShort(offset + ResChunk_header.SIZEOF + 2); |
| entryCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4); |
| } |
| |
| public int[] getSpecFlags() { |
| int[] ints = new int[(header.size - header.headerSize) / 4]; |
| for (int i = 0; i < ints.length; i++) { |
| ints[i] = myBuf().getInt(myOffset() + header.headerSize + i * 4); |
| |
| } |
| return ints; |
| } |
| }; |
| |
| /** |
| * A collection of resource entries for a particular resource data |
| * type. |
| * |
| * If the flag FLAG_SPARSE is not set in `flags`, then this struct is |
| * followed by an array of uint32_t defining the resource |
| * values, corresponding to the array of type strings in the |
| * ResTable_package::typeStrings string block. Each of these hold an |
| * index from entriesStart; a value of NO_ENTRY means that entry is |
| * not defined. |
| * |
| * If the flag FLAG_SPARSE is set in `flags`, then this struct is followed |
| * by an array of ResTable_sparseTypeEntry defining only the entries that |
| * have values for this type. Each entry is sorted by their entry ID such |
| * that a binary search can be performed over the entries. The ID and offset |
| * are encoded in a uint32_t. See ResTabe_sparseTypeEntry. |
| * |
| * There may be multiple of these chunks for a particular resource type, |
| * supply different configuration variations for the resource values of |
| * that type. |
| * |
| * It would be nice to have an additional ordered index of entries, so |
| * we can do a binary search if trying to find a resource by string name. |
| */ |
| static class ResTable_type extends WithOffset |
| { |
| // public static final int SIZEOF = ResChunk_header.SIZEOF + 12 + ResTable_config.SIZ; |
| public static final int SIZEOF_WITHOUT_CONFIG = ResChunk_header.SIZEOF + 12; |
| |
| final ResChunk_header header; |
| |
| //enum { |
| public static final int NO_ENTRY = 0xFFFFFFFF; |
| // }; |
| |
| // The type identifier this chunk is holding. Type IDs start |
| // at 1 (corresponding to the value of the type bits in a |
| // resource identifier). 0 is invalid. |
| final byte id; |
| |
| // enum { |
| // If set, the entry is sparse, and encodes both the entry ID and offset into each entry, |
| // and a binary search is used to find the key. Only available on platforms >= O. |
| // Mark any types that use this with a v26 qualifier to prevent runtime issues on older |
| // platforms. |
| public static final int FLAG_SPARSE = 0x01; |
| |
| // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u |
| // An 16-bit offset of 0xffffu means a NO_ENTRY |
| public static final int FLAG_OFFSET16 = 0x02; |
| |
| // }; |
| final byte flags; |
| |
| // Must be 0. |
| final short reserved; |
| |
| // Number of uint32_t entry indices that follow. |
| final int entryCount; |
| |
| // Offset from header where ResTable_entry data starts. |
| final int entriesStart; |
| |
| // Configuration this collection of entries is designed for. This must always be last. |
| final ResTable_config config; |
| |
| ResTable_type(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| header = new ResChunk_header(buf, offset); |
| id = buf.get(offset + ResChunk_header.SIZEOF); |
| flags = buf.get(offset + ResChunk_header.SIZEOF + 1); |
| reserved = buf.getShort(offset + ResChunk_header.SIZEOF + 2); |
| entryCount = buf.getInt(offset + ResChunk_header.SIZEOF + 4); |
| entriesStart = buf.getInt(offset + ResChunk_header.SIZEOF + 8); |
| |
| // Cast to Buffer because generated covariant return type that returns ByteBuffer is not |
| // available on Java 8 |
| ((Buffer) buf).position(offset + ResChunk_header.SIZEOF + 12); |
| config = ResTable_config.createConfig(buf); |
| } |
| |
| public int findEntryByResName(int stringId) { |
| for (int i = 0; i < entryCount; i++) { |
| if (entryNameIndex(i) == stringId) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| int entryOffset(int entryIndex) { |
| ByteBuffer byteBuffer = myBuf(); |
| int offset = myOffset(); |
| boolean isOffset16 = (flags & ResTable_type.FLAG_OFFSET16) == ResTable_type.FLAG_OFFSET16; |
| if (isOffset16) { |
| short off16 = byteBuffer.getShort(offset + header.headerSize + entryIndex * 2); |
| if (off16 == -1) { |
| return -1; |
| } |
| // Check for no entry (0xffff short) |
| return dtohs(off16) == 0xffff ? ResTable_type.NO_ENTRY : dtohs(off16) * 4; |
| } else { |
| return byteBuffer.getInt(offset + header.headerSize + entryIndex * 4); |
| } |
| // from ResTable cpp: |
| // const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( |
| // reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); |
| // |
| // uint32_t thisOffset = dtohl(eindex[realEntryIndex]); |
| } |
| |
| private int entryNameIndex(int entryIndex) { |
| ByteBuffer byteBuffer = myBuf(); |
| int offset = myOffset(); |
| |
| // from ResTable cpp: |
| // const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( |
| // reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); |
| // |
| // uint32_t thisOffset = dtohl(eindex[realEntryIndex]); |
| int entryOffset = byteBuffer.getInt(offset + header.headerSize + entryIndex * 4); |
| if (entryOffset == -1) { |
| return -1; |
| } |
| |
| int STRING_POOL_REF_OFFSET = 4; |
| return dtohl(byteBuffer.getInt(offset + entriesStart + entryOffset + STRING_POOL_REF_OFFSET)); |
| } |
| }; |
| |
| // The minimum size required to read any version of ResTable_type. |
| // constexpr size_t kResTableTypeMinSize = |
| // sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size); |
| static final int kResTableTypeMinSize = |
| ResTable_type.SIZEOF_WITHOUT_CONFIG - ResTable_config.SIZEOF + SIZEOF_INT /*sizeof(ResTable_config::size)*/; |
| |
| /** |
| * An entry in a ResTable_type with the flag `FLAG_SPARSE` set. |
| */ |
| static class ResTable_sparseTypeEntry extends WithOffset { |
| public static final int SIZEOF = 4; |
| |
| // Holds the raw uint32_t encoded value. Do not read this. |
| // int entry; |
| |
| short idx; |
| short offset; |
| // struct { |
| // The index of the entry. |
| // uint16_t idx; |
| |
| // The offset from ResTable_type::entriesStart, divided by 4. |
| // uint16_t offset; |
| // }; |
| |
| public ResTable_sparseTypeEntry(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| this.idx = buf.getShort(offset); |
| this.offset = buf.getShort(offset + 2); |
| } |
| }; |
| |
| /** |
| * This is the beginning of information about an entry in the resource |
| * table. It holds the reference to the name of this entry, and is |
| * immediately followed by one of: |
| * * A Res_value structure, if FLAG_COMPLEX is -not- set. |
| * * An array of ResTable_map structures, if FLAG_COMPLEX is set. |
| * These supply a set of name/value mappings of data. |
| */ |
| static class ResTable_entry extends WithOffset |
| { |
| public static final int SIZEOF = 4 + ResStringPool_ref.SIZEOF; |
| |
| // Number of bytes in this structure. |
| short size; |
| |
| // If set, this is a complex entry, holding a set of name/value |
| // mappings. It is followed by an array of ResTable_map structures. |
| public static final int FLAG_COMPLEX = 0x0001; |
| // If set, this resource has been declared public, so libraries |
| // are allowed to reference it. |
| public static final int FLAG_PUBLIC = 0x0002; |
| // If set, this is a weak resource and may be overriden by strong |
| // resources of the same name/type. This is only useful during |
| // linking with other resource tables. |
| public static final int FLAG_WEAK = 0x0004; |
| // If set, this is a compact entry with data type and value directly |
| // encoded in the this entry, see ResTable_entry::compact |
| public static final int FLAG_COMPACT = 0x0008; |
| |
| final short flags; |
| |
| // Reference into ResTable_package::keyStrings identifying this entry. |
| ResStringPool_ref key; |
| |
| int compactData; |
| short compactKey; |
| |
| ResTable_entry(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| flags = buf.getShort(offset + 2); |
| |
| if (isCompact()) { |
| compactKey = buf.getShort(offset); |
| compactData = buf.getInt(offset + 4); |
| } else { |
| size = buf.getShort(offset); |
| key = new ResStringPool_ref(buf, offset + 4); |
| } |
| } |
| |
| public int getKeyIndex() { |
| if (isCompact()) { |
| return dtohs(compactKey); |
| } else { |
| return key.index; |
| } |
| } |
| |
| public boolean isCompact() { |
| return (flags & FLAG_COMPACT) == FLAG_COMPACT; |
| } |
| |
| public Res_value getResValue() { |
| // something like: |
| |
| // final Res_value device_value = reinterpret_cast<final Res_value>( |
| // reinterpret_cast<final byte*>(entry) + dtohs(entry.size)); |
| |
| if (isCompact()) { |
| byte type = (byte) (dtohs(flags) >> 8); |
| return new Res_value((byte)(dtohs(flags) >> 8), compactData); |
| } else { |
| return new Res_value(myBuf(), myOffset() + dtohs(size)); |
| } |
| } |
| } |
| |
| /** |
| * Extended form of a ResTable_entry for map entries, defining a parent map |
| * resource from which to inherit values. |
| */ |
| static class ResTable_map_entry extends ResTable_entry |
| { |
| |
| /** |
| * Indeterminate size, calculate using {@link #size} instead. |
| */ |
| public static final Void SIZEOF = null; |
| |
| public static final int BASE_SIZEOF = ResTable_entry.SIZEOF + 8; |
| |
| // Resource identifier of the parent mapping, or 0 if there is none. |
| // This is always treated as a TYPE_DYNAMIC_REFERENCE. |
| ResTable_ref parent; |
| // Number of name/value pairs that follow for FLAG_COMPLEX. |
| int count; |
| |
| ResTable_map_entry(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| parent = new ResTable_ref(buf, offset + ResTable_entry.SIZEOF); |
| count = buf.getInt(offset + ResTable_entry.SIZEOF + ResTable_ref.SIZEOF); |
| } |
| }; |
| |
| /** |
| * A single name/value mapping that is part of a complex resource |
| * entry. |
| */ |
| public static class ResTable_map extends WithOffset |
| { |
| public static final int SIZEOF = ResTable_ref.SIZEOF + ResourceTypes.Res_value.SIZEOF; |
| |
| // The resource identifier defining this mapping's name. For attribute |
| // resources, 'name' can be one of the following special resource types |
| // to supply meta-data about the attribute; for all other resource types |
| // it must be an attribute resource. |
| public final ResTable_ref name; |
| |
| // Special values for 'name' when defining attribute resources. |
| //enum { |
| // This entry holds the attribute's type code. |
| public static final int ATTR_TYPE = Res_MAKEINTERNAL(0); |
| |
| // For integral attributes, this is the minimum value it can hold. |
| public static final int ATTR_MIN = Res_MAKEINTERNAL(1); |
| |
| // For integral attributes, this is the maximum value it can hold. |
| public static final int ATTR_MAX = Res_MAKEINTERNAL(2); |
| |
| // Localization of this resource is can be encouraged or required with |
| // an aapt flag if this is set |
| public static final int ATTR_L10N = Res_MAKEINTERNAL(3); |
| |
| // for plural support, see android.content.res.PluralRules#attrForQuantity(int) |
| public static final int ATTR_OTHER = Res_MAKEINTERNAL(4); |
| public static final int ATTR_ZERO = Res_MAKEINTERNAL(5); |
| public static final int ATTR_ONE = Res_MAKEINTERNAL(6); |
| public static final int ATTR_TWO = Res_MAKEINTERNAL(7); |
| public static final int ATTR_FEW = Res_MAKEINTERNAL(8); |
| public static final int ATTR_MANY = Res_MAKEINTERNAL(9); |
| |
| // }; |
| |
| // Bit mask of allowed types, for use with ATTR_TYPE. |
| //enum { |
| // No type has been defined for this attribute, use generic |
| // type handling. The low 16 bits are for types that can be |
| // handled generically; the upper 16 require additional information |
| // in the bag so can not be handled generically for TYPE_ANY. |
| public static final int TYPE_ANY = 0x0000FFFF; |
| |
| // Attribute holds a references to another resource. |
| public static final int TYPE_REFERENCE = 1<<0; |
| |
| // Attribute holds a generic string. |
| public static final int TYPE_STRING = 1<<1; |
| |
| // Attribute holds an integer value. ATTR_MIN and ATTR_MIN can |
| // optionally specify a constrained range of possible integer values. |
| public static final int TYPE_INTEGER = 1<<2; |
| |
| // Attribute holds a boolean integer. |
| public static final int TYPE_BOOLEAN = 1<<3; |
| |
| // Attribute holds a color value. |
| public static final int TYPE_COLOR = 1<<4; |
| |
| // Attribute holds a floating point value. |
| public static final int TYPE_FLOAT = 1<<5; |
| |
| // Attribute holds a dimension value, such as "20px". |
| public static final int TYPE_DIMENSION = 1<<6; |
| |
| // Attribute holds a fraction value, such as "20%". |
| public static final int TYPE_FRACTION = 1<<7; |
| |
| // Attribute holds an enumeration. The enumeration values are |
| // supplied as additional entries in the map. |
| public static final int TYPE_ENUM = 1<<16; |
| |
| // Attribute holds a bitmaks of flags. The flag bit values are |
| // supplied as additional entries in the map. |
| public static final int TYPE_FLAGS = 1<<17; |
| // }; |
| |
| // Enum of localization modes, for use with ATTR_L10N. |
| //enum { |
| public static final int L10N_NOT_REQUIRED = 0; |
| public static final int L10N_SUGGESTED = 1; |
| // }; |
| |
| // This mapping's value. |
| public Res_value value; |
| |
| public ResTable_map(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| name = new ResTable_ref(buf, offset); |
| value = new Res_value(buf, offset + ResTable_ref.SIZEOF); |
| } |
| |
| public ResTable_map() { |
| super(null, 0); |
| this.name = new ResTable_ref(); |
| this.value = new Res_value(); |
| } |
| |
| @Override |
| public String toString() { |
| return "ResTable_map{" + "name=" + name + ", value=" + value + '}'; |
| } |
| }; |
| |
| /** |
| * A package-id to package name mapping for any shared libraries used |
| * in this resource table. The package-id's encoded in this resource |
| * table may be different than the id's assigned at runtime. We must |
| * be able to translate the package-id's based on the package name. |
| */ |
| static class ResTable_lib_header extends WithOffset |
| { |
| static final int SIZEOF = ResChunk_header.SIZEOF + 4; |
| |
| ResChunk_header header; |
| |
| // The number of shared libraries linked in this resource table. |
| int count; |
| |
| ResTable_lib_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| header = new ResChunk_header(buf, offset); |
| count = buf.getInt(offset + ResChunk_header.SIZEOF); |
| } |
| }; |
| |
| /** |
| * A shared library package-id to package name entry. |
| */ |
| static class ResTable_lib_entry extends WithOffset |
| { |
| public static final int SIZEOF = 4 + 128 * SIZEOF_SHORT; |
| |
| // The package-id this shared library was assigned at build time. |
| // We use a uint32 to keep the structure aligned on a uint32 boundary. |
| int packageId; |
| |
| // The package name of the shared library. \0 terminated. |
| char[] packageName = new char[128]; |
| |
| ResTable_lib_entry(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| packageId = buf.getInt(offset); |
| |
| for (int i = 0; i < packageName.length; i++) { |
| packageName[i] = buf.getChar(offset + 4 + i * SIZEOF_SHORT); |
| } |
| } |
| }; |
| |
| /** |
| * A map that allows rewriting staged (non-finalized) resource ids to their finalized |
| * counterparts. |
| */ |
| static class ResTableStagedAliasHeader extends WithOffset { |
| public static final int SIZEOF = ResChunk_header.SIZEOF + 4; |
| |
| ResChunk_header header; |
| |
| // The number of ResTableStagedAliasEntry that follow this header. |
| int count; |
| |
| ResTableStagedAliasHeader(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| header = new ResChunk_header(buf, offset); |
| count = buf.getInt(offset + ResChunk_header.SIZEOF); |
| } |
| } |
| |
| /** Maps the staged (non-finalized) resource id to its finalized resource id. */ |
| static class ResTableStagedAliasEntry extends WithOffset { |
| public static final int SIZEOF = 8; |
| |
| // The compile-time staged resource id to rewrite. |
| int stagedResId; |
| |
| // The compile-time finalized resource id to which the staged resource id should be rewritten. |
| int finalizedResId; |
| |
| ResTableStagedAliasEntry(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| stagedResId = buf.getInt(offset); |
| finalizedResId = buf.getInt(offset + 4); |
| } |
| } |
| |
| // struct alignas(uint32_t) Idmap_header { |
| static class Idmap_header extends WithOffset { |
| // Always 0x504D4449 ('IDMP') |
| int magic; |
| |
| int version; |
| |
| int target_crc32; |
| int overlay_crc32; |
| |
| final byte[] target_path = new byte[256]; |
| final byte[] overlay_path = new byte[256]; |
| |
| short target_package_id; |
| short type_count; |
| |
| Idmap_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| magic = buf.getInt(offset); |
| version = buf.getInt(offset + 4); |
| target_crc32 = buf.getInt(offset + 8); |
| overlay_crc32 = buf.getInt(offset + 12); |
| |
| buf.get(target_path, offset + 16, 256); |
| buf.get(overlay_path, offset + 16 + 256, 256); |
| |
| target_package_id = buf.getShort(offset + 16 + 256 + 256); |
| type_count = buf.getShort(offset + 16 + 256 + 256 + 2); |
| } |
| } // __attribute__((packed)); |
| |
| // struct alignas(uint32_t) IdmapEntry_header { |
| static class IdmapEntry_header extends WithOffset { |
| static final int SIZEOF = 2 * 4; |
| |
| short target_type_id; |
| short overlay_type_id; |
| short entry_count; |
| short entry_id_offset; |
| int entries[]; |
| |
| IdmapEntry_header(ByteBuffer buf, int offset) { |
| super(buf, offset); |
| |
| target_type_id = buf.getShort(offset); |
| overlay_type_id = buf.getShort(offset + 2); |
| entry_count = buf.getShort(offset + 4); |
| entry_id_offset = buf.getShort(offset + 6); |
| entries = new int[entry_count]; |
| for (int i = 0; i < entries.length; i++) { |
| entries[i] = buf.getInt(offset + 8 + i * SIZEOF_INT); |
| } |
| } |
| } // __attribute__((packed)); |
| |
| |
| abstract private static class FutureWriter<T> { |
| protected final ByteBuffer buf; |
| private final int position; |
| |
| public FutureWriter(ByteBuffer buf, int size) { |
| this.buf = buf; |
| this.position = buf.position(); |
| // Cast to Buffer because generated covariant return type that returns ByteBuffer is not |
| // available on Java 8 |
| ((Buffer) buf).position(position + size); |
| } |
| |
| abstract protected void put(int position, T value); |
| |
| public void write(T value) { |
| put(position, value); |
| } |
| } |
| |
| private static class IntWriter extends FutureWriter<Integer> { |
| public IntWriter(ByteBuffer buf) { |
| super(buf, 4); |
| } |
| |
| @Override |
| protected void put(int position, Integer value) { |
| buf.putInt(position, value); |
| } |
| } |
| |
| private static class ShortWriter extends FutureWriter<Short> { |
| public ShortWriter(ByteBuffer buf) { |
| super(buf, 2); |
| } |
| |
| @Override |
| protected void put(int position, Short value) { |
| buf.putShort(position, value); |
| } |
| } |
| } |