├── settings.gradle
├── xml
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ ├── common
│ │ └── utils
│ │ │ ├── StringUtils.java
│ │ │ └── FileUtils.java
│ │ ├── com
│ │ └── axml
│ │ │ ├── chunk
│ │ │ ├── base
│ │ │ │ ├── ChunkType.java
│ │ │ │ ├── BaseContentChunk.java
│ │ │ │ └── BaseChunk.java
│ │ │ ├── EndTagChunk.java
│ │ │ ├── ResourceChunk.java
│ │ │ ├── NamespaceChunk.java
│ │ │ ├── StringChunk.java
│ │ │ └── StartTagChunk.java
│ │ │ └── AndroidBinaryXml.java
│ │ └── android
│ │ └── util
│ │ └── TypedValue.java
│ └── test
│ └── java
│ └── Main.java
├── alipay.png
├── wechatpay.png
├── AndroidBinaryXml.png
├── .gitignore
└── README.md
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'xml'
2 |
--------------------------------------------------------------------------------
/xml/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /build
--------------------------------------------------------------------------------
/alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/alipay.png
--------------------------------------------------------------------------------
/wechatpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/wechatpay.png
--------------------------------------------------------------------------------
/AndroidBinaryXml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/senswrong/AndroidBinaryXml/HEAD/AndroidBinaryXml.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | .DS_Store
5 | /build
6 | /out
7 | /gradle
8 | gradlew
9 | gradlew.bat
10 |
--------------------------------------------------------------------------------
/xml/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | version 'unspecified'
6 |
7 | sourceCompatibility = 1.8
8 |
9 | repositories {
10 | mavenCentral()
11 | }
12 |
13 | dependencies {
14 | testCompile group: 'junit', name: 'junit', version: '4.12'
15 | }
16 |
--------------------------------------------------------------------------------
/xml/src/main/java/common/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package common.utils;
2 |
3 | /**
4 | * Created by Sens on 2021/8/27.
5 | */
6 | public class StringUtils {
7 | public static boolean isEmpty(String string) {
8 | return string == null || string.trim().length() == 0;
9 | }
10 |
11 | public static boolean isNotEmpty(String string) {
12 | return !isEmpty(string);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/xml/src/test/java/Main.java:
--------------------------------------------------------------------------------
1 | import com.axml.AndroidBinaryXml;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Created by Sens on 2021/8/27.
8 | */
9 | public class Main {
10 |
11 | public static void main(String[] args) throws IOException {
12 | File file = new File(args[0]);
13 | System.out.println("file->" + file);
14 |
15 | AndroidBinaryXml androidBinaryXml = new AndroidBinaryXml(file);
16 |
17 | byte[] datas = androidBinaryXml.toBytes();
18 |
19 | AndroidBinaryXml manifest = new AndroidBinaryXml(datas);
20 | System.out.println(manifest);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/xml/src/main/java/common/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package common.utils;
2 |
3 | import java.io.*;
4 |
5 | /**
6 | * Created by Sens on 2021/8/27.
7 | */
8 | public class FileUtils {
9 | public static byte[] getFileData(File file) {
10 | try {
11 | BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
12 | int available = inputStream.available();
13 | byte[] datas = new byte[available];
14 | inputStream.read(datas);
15 | inputStream.close();
16 | return datas;
17 | } catch (IOException e) {
18 | e.printStackTrace();
19 | }
20 | return null;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/base/ChunkType.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk.base;
2 |
3 | /**
4 | * Created by Sens on 2021/8/27.
5 | */
6 | public enum ChunkType {
7 | CHUNK_STRING /*chunk type*/(0x0001),
8 | CHUNK_RESOURCE /*chunk type*/(0x0180),
9 | CHUNK_START_NAMESPACE /*chunk type*/(0x0100),
10 | CHUNK_END_NAMESPACE /*chunk type*/(0x0101),
11 | CHUNK_START_TAG /*chunk type*/(0x0102),
12 | CHUNK_END_TAG /*chunk type*/(0x0103),
13 | ;
14 | public final int TYPE;
15 |
16 | ChunkType(int TYPE) {
17 | this.TYPE = TYPE;
18 | }
19 |
20 | public static ChunkType valueOf(int TYPE) {
21 | for (ChunkType value : ChunkType.values())
22 | if (value.TYPE == TYPE) return value;
23 | return null;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GOTO[CSDN](https://blog.csdn.net/song6974272/article/details/119991751)
2 | 
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## 杨
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/EndTagChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk;
2 |
3 | import com.axml.chunk.base.BaseContentChunk;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 |
10 | /**
11 | * Created by Sens on 2021/8/27.
12 | */
13 | public class EndTagChunk extends BaseContentChunk {
14 | public final int namespaceUri;
15 | public final int name;
16 |
17 | public EndTagChunk(ByteBuffer byteBuffer, StringChunk stringChunk) {
18 | super(byteBuffer, stringChunk);
19 | namespaceUri = byteBuffer.getInt();
20 | name = byteBuffer.getInt();
21 |
22 | byteBuffer.position(ChunkStartPosition + chunkSize);
23 | }
24 |
25 | @Override
26 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
27 | super.toBytes(stream);
28 | ByteBuffer byteBuffer = ByteBuffer.allocate(8);
29 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
30 | byteBuffer.putInt(this.namespaceUri);
31 | byteBuffer.putInt(this.name);
32 | stream.write(byteBuffer.array());
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return "" + getString(name) + ">\n";
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/ResourceChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk;
2 |
3 | import com.axml.chunk.base.BaseChunk;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * Created by Sens on 2021/8/27.
14 | * Used system resource ID [android.R.**]
15 | */
16 | public class ResourceChunk extends BaseChunk {
17 | public final List resourceIDs;
18 |
19 | public ResourceChunk(ByteBuffer byteBuffer) {
20 | super(byteBuffer);
21 | int idCount = chunkSize / 4 - 2;
22 | resourceIDs = new ArrayList<>(idCount);
23 | for (int i = 0; i < idCount; i++)
24 | resourceIDs.add(byteBuffer.getInt());
25 | byteBuffer.position(ChunkStartPosition + chunkSize);
26 | }
27 |
28 | @Override
29 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
30 | ByteBuffer byteBuffer = ByteBuffer.allocate(resourceIDs.size() * 4);
31 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
32 | for (Integer integer : resourceIDs)
33 | byteBuffer.putInt(integer);
34 | stream.write(byteBuffer.array());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/base/BaseContentChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk.base;
2 |
3 | import com.axml.chunk.StringChunk;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 |
10 | /**
11 | * Created by Sens on 2021/8/27.
12 | */
13 | public class BaseContentChunk extends BaseChunk {
14 | public final int lineNumber;
15 | public final int comment;
16 |
17 | protected final StringChunk stringChunk;
18 |
19 | public BaseContentChunk(ByteBuffer byteBuffer, StringChunk stringChunk) {
20 | super(byteBuffer);
21 | lineNumber = byteBuffer.getInt();
22 | comment = byteBuffer.getInt();
23 |
24 | this.stringChunk = stringChunk;
25 | }
26 |
27 | @Override
28 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
29 | ByteBuffer byteBuffer = ByteBuffer.allocate(8);
30 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
31 | byteBuffer.putInt(this.lineNumber);
32 | byteBuffer.putInt(this.comment);
33 | stream.write(byteBuffer.array());
34 | }
35 |
36 | protected String getString(int index) {
37 | if (index == -1) return "";
38 | return stringChunk.getString(index);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/NamespaceChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk;
2 |
3 | import com.axml.chunk.base.BaseContentChunk;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 |
10 | /**
11 | * Created by Sens on 2021/8/27.
12 | */
13 | public class NamespaceChunk extends BaseContentChunk {
14 | public final int prefix;
15 | public final int uri;
16 |
17 | public NamespaceChunk(ByteBuffer byteBuffer, StringChunk stringChunk) {
18 | super(byteBuffer, stringChunk);
19 | this.prefix = byteBuffer.getInt();
20 | this.uri = byteBuffer.getInt();
21 | byteBuffer.position(ChunkStartPosition + chunkSize);
22 | }
23 |
24 | @Override
25 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
26 | super.toBytes(stream);
27 | ByteBuffer byteBuffer = ByteBuffer.allocate(8);
28 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
29 | byteBuffer.putInt(this.prefix);
30 | byteBuffer.putInt(this.uri);
31 | stream.write(byteBuffer.array());
32 | }
33 |
34 | public String getXmlNameSpace() {
35 | return new StringBuilder().append("xmlns:").append(getString(prefix)).append("=\"").append(getString(uri)).append('"').toString();
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return "";
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/base/BaseChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk.base;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.nio.ByteBuffer;
6 | import java.nio.ByteOrder;
7 |
8 | /**
9 | * Created by Sens on 2021/8/27.
10 | */
11 | public abstract class BaseChunk {
12 | public final int ChunkStartPosition;
13 |
14 | public final short chunkType;
15 | public final short headerSize;
16 | public int chunkSize;
17 |
18 | public BaseChunk(ByteBuffer byteBuffer) {
19 | byteBuffer.position(byteBuffer.position() - 2);
20 | ChunkStartPosition = byteBuffer.position();
21 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
22 | this.chunkType = byteBuffer.getShort();
23 | this.headerSize = byteBuffer.getShort();
24 | this.chunkSize = byteBuffer.getInt();
25 | }
26 |
27 | protected abstract void toBytes(ByteArrayOutputStream stream) throws IOException;
28 |
29 | public byte[] toBytes() throws IOException {
30 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
31 | toBytes(stream);
32 | byte[] bytes = stream.toByteArray();
33 | this.chunkSize = bytes.length + 8;
34 | ByteBuffer byteBuffer = ByteBuffer.allocate(8 + bytes.length);
35 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
36 | byteBuffer.putShort(this.chunkType);
37 | byteBuffer.putShort(this.headerSize);
38 | byteBuffer.putInt(this.chunkSize);
39 | byteBuffer.put(bytes);
40 | return byteBuffer.array();
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | return "";
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/AndroidBinaryXml.java:
--------------------------------------------------------------------------------
1 | package com.axml;
2 |
3 | import com.axml.chunk.*;
4 | import com.axml.chunk.base.BaseChunk;
5 | import com.axml.chunk.base.ChunkType;
6 | import common.utils.FileUtils;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.nio.ByteBuffer;
12 | import java.nio.ByteOrder;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * Created by Sens on 2021/8/27.
18 | * {@see AndroidBinaryXml}
19 | */
20 | public class AndroidBinaryXml {
21 | public short fileType;
22 | public short headerSize;
23 | public int fileSize;
24 | public StringChunk stringChunk;
25 | public ResourceChunk resourceChunk;
26 | public List structList = new ArrayList();
27 |
28 | public AndroidBinaryXml(File androidManifest) {
29 | this(FileUtils.getFileData(androidManifest));
30 | }
31 |
32 | public AndroidBinaryXml(byte[] datas) {
33 | int available = datas.length;
34 | ByteBuffer byteBuffer = ByteBuffer.wrap(datas);
35 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
36 | fileType = byteBuffer.getShort();
37 | headerSize = byteBuffer.getShort();
38 | fileSize = byteBuffer.getInt();
39 | List namespaceChunkList = new ArrayList<>();
40 | while (byteBuffer.position() < available) {
41 | short Type = byteBuffer.getShort();
42 | ChunkType chunkType = ChunkType.valueOf(Type);
43 | if (chunkType == null) break;
44 | switch (chunkType) {
45 | case CHUNK_STRING:
46 | stringChunk = new StringChunk(byteBuffer);
47 | break;
48 | case CHUNK_RESOURCE:
49 | resourceChunk = new ResourceChunk(byteBuffer);
50 | break;
51 | case CHUNK_START_NAMESPACE:
52 | NamespaceChunk namespaceChunk = new NamespaceChunk(byteBuffer, stringChunk);
53 | namespaceChunkList.add(namespaceChunk);
54 | structList.add(namespaceChunk);
55 | break;
56 | case CHUNK_START_TAG:
57 | structList.add(new StartTagChunk(byteBuffer, stringChunk, namespaceChunkList));
58 | break;
59 | case CHUNK_END_TAG:
60 | structList.add(new EndTagChunk(byteBuffer, stringChunk));
61 | break;
62 | case CHUNK_END_NAMESPACE:
63 | structList.add(new NamespaceChunk(byteBuffer, stringChunk));
64 | break;
65 | }
66 | }
67 | }
68 |
69 | public byte[] toBytes() throws IOException {
70 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
71 | if (stringChunk != null) stream.write(stringChunk.toBytes());
72 | if (resourceChunk != null) stream.write(resourceChunk.toBytes());
73 | for (BaseChunk chunk : structList)
74 | stream.write(chunk.toBytes());
75 | fileSize = 8 + stream.size();
76 | ByteBuffer byteBuffer = ByteBuffer.allocate(fileSize);
77 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
78 | byteBuffer.putShort(fileType);
79 | byteBuffer.putShort(headerSize);
80 | byteBuffer.putInt(fileSize);
81 | byteBuffer.put(stream.toByteArray());
82 | return byteBuffer.array();
83 | }
84 |
85 | @Override
86 | public String toString() {
87 | StringBuilder sb = new StringBuilder("\n");
88 | boolean hasXmlns = false;
89 | for (BaseChunk baseChunk : structList) {
90 | if (!hasXmlns && baseChunk instanceof StartTagChunk) {
91 | ((StartTagChunk) baseChunk).addXmlns();
92 | hasXmlns = true;
93 | }
94 | sb.append(baseChunk);
95 | }
96 | return sb.toString();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/StringChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk;
2 |
3 | import com.axml.chunk.base.BaseChunk;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by Sens on 2021/8/27.
15 | */
16 | public class StringChunk extends BaseChunk {
17 | public int stringCount;
18 | public int styleCount;
19 | public boolean isUTF8;
20 | public boolean isSorted;
21 | public int stringStart;
22 | public int styleStart;
23 |
24 | public int[] stringOffsets;
25 | public int[] styleOffsets;
26 |
27 | public List stringList;
28 |
29 | public StringChunk(ByteBuffer byteBuffer) {
30 | super(byteBuffer);
31 | this.stringCount = byteBuffer.getInt();
32 | this.styleCount = byteBuffer.getInt();
33 | //utf8(0x0100) or default utf16le(0x0000)
34 | this.isUTF8 = byteBuffer.getShort() != 0;
35 | this.isSorted = byteBuffer.getShort() != 0;
36 | this.stringStart = byteBuffer.getInt();
37 | this.styleStart = byteBuffer.getInt();
38 |
39 | stringOffsets = new int[stringCount];
40 | for (int i = 0; i < stringOffsets.length; i++)
41 | stringOffsets[i] = byteBuffer.getInt();
42 |
43 | styleOffsets = new int[styleCount];
44 | for (int i = 0; i < styleOffsets.length; i++)
45 | styleOffsets[i] = byteBuffer.getInt();
46 |
47 | stringList = new ArrayList<>(stringCount);
48 |
49 | for (int i = 0; i < stringCount; i++) {
50 | byteBuffer.position(ChunkStartPosition + stringStart + stringOffsets[i]);
51 | int byteCount;
52 | if (isUTF8) {
53 | int strCount = byteBuffer.get() & 0xFF;
54 | byteCount = byteBuffer.get() & 0xFF;
55 | } else
56 | byteCount = byteBuffer.getShort() * 2;
57 |
58 | byte[] string = new byte[byteCount];
59 | byteBuffer.get(string);
60 | if (isUTF8) stringList.add(new String(string, StandardCharsets.UTF_8));
61 | else stringList.add(new String(string, StandardCharsets.UTF_16LE));
62 | }
63 | //styleCount always = 0 [skip]
64 | byteBuffer.position(ChunkStartPosition + chunkSize);
65 | }
66 |
67 | public String getString(int index) {
68 | return stringList.get(index);
69 | }
70 |
71 | private void stringToBytes(ByteArrayOutputStream stream, String str) throws IOException {
72 | ByteBuffer byteBuffer;
73 | if (isUTF8) {
74 | byte[] chars = str.getBytes(StandardCharsets.UTF_8);
75 | byteBuffer = ByteBuffer.allocate(2 + chars.length + 1);
76 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
77 | byteBuffer.put((byte) str.length());
78 | byteBuffer.put((byte) chars.length);
79 | byteBuffer.put(chars);
80 | byteBuffer.put((byte) 0);
81 | } else {
82 | byte[] chars = str.getBytes(StandardCharsets.UTF_16LE);
83 | byteBuffer = ByteBuffer.allocate(2 + chars.length + 2);
84 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
85 | byteBuffer.putShort((short) str.length());
86 | byteBuffer.put(chars);
87 | byteBuffer.putShort((short) 0);
88 | }
89 | stream.write(byteBuffer.array());
90 | }
91 |
92 | @Override
93 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
94 | stringCount = stringList.size();
95 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4 + stringOffsets.length * 4 + styleOffsets.length * 4);
96 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
97 | byteBuffer.putInt(stringCount);
98 | byteBuffer.putInt(styleCount);
99 | byteBuffer.order(ByteOrder.BIG_ENDIAN);
100 | byteBuffer.putShort((short) (isUTF8 ? 1 : 0));
101 | byteBuffer.putShort((short) (isSorted ? 1 : 0));
102 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
103 | byteBuffer.putInt(stringStart);
104 | byteBuffer.putInt(styleStart);
105 | int stringOffset = 0;
106 | if (stringOffsets.length != stringCount)
107 | stringOffsets = new int[stringCount];
108 | for (int i = 0; i < stringCount; i++) {
109 | stringOffsets[i] = stringOffset;
110 | byteBuffer.putInt(stringOffset);
111 | if (isUTF8)
112 | stringOffset += 3 + stringList.get(i).getBytes(StandardCharsets.UTF_8).length;
113 | else
114 | stringOffset += 4 + stringList.get(i).getBytes(StandardCharsets.UTF_16LE).length;
115 | }
116 | //styleCount always = 0 [skip]
117 | for (int offset : styleOffsets) byteBuffer.putInt(offset);
118 | stream.write(byteBuffer.array());
119 | for (String str : stringList)
120 | stringToBytes(stream, str);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/xml/src/main/java/com/axml/chunk/StartTagChunk.java:
--------------------------------------------------------------------------------
1 | package com.axml.chunk;
2 |
3 | import android.util.TypedValue;
4 | import com.axml.chunk.base.BaseContentChunk;
5 | import common.utils.StringUtils;
6 |
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.IOException;
9 | import java.nio.ByteBuffer;
10 | import java.nio.ByteOrder;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * Created by Sens on 2021/8/27.
16 | */
17 | public class StartTagChunk extends BaseContentChunk {
18 | public final int namespaceUri;
19 | public final int name;
20 |
21 | public short attributeStart;
22 | public short attributeSize;
23 | public short attributeCount;
24 | public short idIndex;
25 | public short classIndex;
26 | public short styleIndex;
27 |
28 | public List attributes;
29 |
30 | public StartTagChunk(ByteBuffer byteBuffer, StringChunk stringChunk, List namespaceChunkList) {
31 | super(byteBuffer, stringChunk);
32 | namespaceUri = byteBuffer.getInt();
33 | name = byteBuffer.getInt();
34 | attributeStart = byteBuffer.getShort();
35 | attributeSize = byteBuffer.getShort();
36 | attributeCount = byteBuffer.getShort();
37 | idIndex = byteBuffer.getShort();
38 | classIndex = byteBuffer.getShort();
39 | styleIndex = byteBuffer.getShort();
40 |
41 | attributes = new ArrayList<>(attributeCount);
42 | for (int i = 0; i < attributeCount; i++)
43 | attributes.add(new Attribute(byteBuffer));
44 |
45 | this.namespaceChunkList = namespaceChunkList;
46 | byteBuffer.position(ChunkStartPosition + chunkSize);
47 | }
48 |
49 | public static class Attribute {
50 | public final int namespaceUri;
51 | public final int name;
52 | public final int value;
53 | public final short structureSize;
54 | public final int Res0;
55 | public final int type;
56 | public final int data;
57 |
58 | public Attribute(ByteBuffer byteBuffer) {
59 | namespaceUri = byteBuffer.getInt();
60 | name = byteBuffer.getInt();
61 | value = byteBuffer.getInt();
62 | structureSize = byteBuffer.getShort();
63 | Res0 = byteBuffer.get() & 0xFF;
64 | type = byteBuffer.get() & 0xFF;
65 | data = byteBuffer.getInt();
66 | }
67 |
68 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
69 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4);
70 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
71 | byteBuffer.putInt(namespaceUri);
72 | byteBuffer.putInt(name);
73 | byteBuffer.putInt(value);
74 | byteBuffer.putShort(structureSize);
75 | byteBuffer.put((byte) Res0);
76 | byteBuffer.put((byte) type);
77 | byteBuffer.putInt(data);
78 | stream.write(byteBuffer.array());
79 | }
80 | }
81 |
82 | @Override
83 | protected void toBytes(ByteArrayOutputStream stream) throws IOException {
84 | super.toBytes(stream);
85 | this.attributeCount = (short) attributes.size();
86 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4);
87 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
88 | byteBuffer.putInt(namespaceUri);
89 | byteBuffer.putInt(name);
90 | byteBuffer.putShort(attributeStart);
91 | byteBuffer.putShort(attributeSize);
92 | byteBuffer.putShort(attributeCount);
93 | byteBuffer.putShort(idIndex);
94 | byteBuffer.putShort(classIndex);
95 | byteBuffer.putShort(styleIndex);
96 | stream.write(byteBuffer.array());
97 | for (Attribute attribute : attributes)
98 | attribute.toBytes(stream);
99 | }
100 |
101 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
102 | protected final List namespaceChunkList;
103 |
104 | protected String getPrefix(int uri) {
105 | for (NamespaceChunk namespaceChunk : namespaceChunkList)
106 | if (namespaceChunk.uri == uri)
107 | return getString(namespaceChunk.prefix);
108 | return null;
109 | }
110 |
111 | protected String getNameSpace() {
112 | StringBuilder nsBuilder = new StringBuilder();
113 | if (namespaceChunkList != null)//add namespace
114 | for (NamespaceChunk namespaceChunk : namespaceChunkList)
115 | nsBuilder.append(" ").append(namespaceChunk.getXmlNameSpace());
116 | return nsBuilder.toString();
117 | }
118 |
119 | private boolean addxmlns = false;
120 |
121 | public void addXmlns() {
122 | addxmlns = true;
123 | }
124 |
125 | @Override
126 | public String toString() {
127 | StringBuilder tagBuilder = new StringBuilder();
128 | if (comment > -1) tagBuilder.append("").append("\n");
129 | tagBuilder.append('<');
130 |
131 | String tagName = getString(name);
132 | tagBuilder.append(tagName);
133 |
134 | if (addxmlns) tagBuilder.append(getNameSpace());
135 |
136 | for (Attribute attribute : attributes) {
137 | tagBuilder.append(" ");
138 | if (attribute.namespaceUri != -1) tagBuilder.append(getPrefix(attribute.namespaceUri)).append(":");
139 | String data = TypedValue.coerceToString(attribute.type, attribute.data);
140 | tagBuilder.append(getString(attribute.name))
141 | .append("=")
142 | .append('"')
143 | .append(StringUtils.isEmpty(data) ? getString(attribute.value) : data)
144 | .append('"');
145 | }
146 | return tagBuilder.append(">\n").toString();
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/xml/src/main/java/android/util/TypedValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.util;
18 |
19 |
20 | /**
21 | * Container for a dynamically typed data value. Primarily used with
22 | * {@link android.content.res.Resources} for holding resource values.
23 | */
24 | public class TypedValue {
25 | /**
26 | * The value contains no data.
27 | */
28 | public static final int TYPE_NULL = 0x00;
29 |
30 | /**
31 | * The data field holds a resource identifier.
32 | */
33 | public static final int TYPE_REFERENCE = 0x01;
34 | /**
35 | * The data field holds an attribute resource
36 | * identifier (referencing an attribute in the current theme
37 | * style, not a resource entry).
38 | */
39 | public static final int TYPE_ATTRIBUTE = 0x02;
40 | /**
41 | * The string field holds string data. In addition, if
42 | * data is non-zero then it is the string block
43 | * index of the string and assetCookie is the set of
44 | * assets the string came from.
45 | */
46 | public static final int TYPE_STRING = 0x03;
47 | /**
48 | * The data field holds an IEEE 754 floating point number.
49 | */
50 | public static final int TYPE_FLOAT = 0x04;
51 | /**
52 | * The data field holds a complex number encoding a
53 | * dimension value.
54 | */
55 | public static final int TYPE_DIMENSION = 0x05;
56 | /**
57 | * The data field holds a complex number encoding a fraction
58 | * of a container.
59 | */
60 | public static final int TYPE_FRACTION = 0x06;
61 |
62 | /**
63 | * Identifies the start of plain integer values. Any type value
64 | * from this to {@link #TYPE_LAST_INT} means the
65 | * data field holds a generic integer value.
66 | */
67 | public static final int TYPE_FIRST_INT = 0x10;
68 |
69 | /**
70 | * The data field holds a number that was
71 | * originally specified in decimal.
72 | */
73 | public static final int TYPE_INT_DEC = 0x10;
74 | /**
75 | * The data field holds a number that was
76 | * originally specified in hexadecimal (0xn).
77 | */
78 | public static final int TYPE_INT_HEX = 0x11;
79 | /**
80 | * The data field holds 0 or 1 that was originally
81 | * specified as "false" or "true".
82 | */
83 | public static final int TYPE_INT_BOOLEAN = 0x12;
84 |
85 | /**
86 | * Identifies the start of integer values that were specified as
87 | * color constants (starting with '#').
88 | */
89 | public static final int TYPE_FIRST_COLOR_INT = 0x1c;
90 |
91 | /**
92 | * The data field holds a color that was originally
93 | * specified as #aarrggbb.
94 | */
95 | public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
96 | /**
97 | * The data field holds a color that was originally
98 | * specified as #rrggbb.
99 | */
100 | public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
101 | /**
102 | * The data field holds a color that was originally
103 | * specified as #argb.
104 | */
105 | public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
106 | /**
107 | * The data field holds a color that was originally
108 | * specified as #rgb.
109 | */
110 | public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
111 |
112 | /**
113 | * Identifies the end of integer values that were specified as color
114 | * constants.
115 | */
116 | public static final int TYPE_LAST_COLOR_INT = 0x1f;
117 |
118 | /**
119 | * Identifies the end of plain integer values.
120 | */
121 | public static final int TYPE_LAST_INT = 0x1f;
122 |
123 | /* ------------------------------------------------------------ */
124 |
125 | /**
126 | * Complex data: bit location of unit information.
127 | */
128 | public static final int COMPLEX_UNIT_SHIFT = 0;
129 | /**
130 | * Complex data: mask to extract unit information (after shifting by
131 | * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as
132 | * defined below.
133 | */
134 | public static final int COMPLEX_UNIT_MASK = 0xf;
135 |
136 | /**
137 | * {@link #TYPE_DIMENSION} complex unit: Value is raw pixels.
138 | */
139 | public static final int COMPLEX_UNIT_PX = 0;
140 | /**
141 | * {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
142 | * Pixels.
143 | */
144 | public static final int COMPLEX_UNIT_DIP = 1;
145 | /**
146 | * {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel.
147 | */
148 | public static final int COMPLEX_UNIT_SP = 2;
149 | /**
150 | * {@link #TYPE_DIMENSION} complex unit: Value is in points.
151 | */
152 | public static final int COMPLEX_UNIT_PT = 3;
153 | /**
154 | * {@link #TYPE_DIMENSION} complex unit: Value is in inches.
155 | */
156 | public static final int COMPLEX_UNIT_IN = 4;
157 | /**
158 | * {@link #TYPE_DIMENSION} complex unit: Value is in millimeters.
159 | */
160 | public static final int COMPLEX_UNIT_MM = 5;
161 |
162 | /**
163 | * {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
164 | * size.
165 | */
166 | public static final int COMPLEX_UNIT_FRACTION = 0;
167 | /**
168 | * {@link #TYPE_FRACTION} complex unit: A fraction of the parent size.
169 | */
170 | public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
171 |
172 | /**
173 | * Complex data: where the radix information is, telling where the decimal
174 | * place appears in the mantissa.
175 | */
176 | public static final int COMPLEX_RADIX_SHIFT = 4;
177 | /**
178 | * Complex data: mask to extract radix information (after shifting by
179 | * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point
180 | * representations as defined below.
181 | */
182 | public static final int COMPLEX_RADIX_MASK = 0x3;
183 |
184 | /**
185 | * Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0
186 | */
187 | public static final int COMPLEX_RADIX_23p0 = 0;
188 | /**
189 | * Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
190 | */
191 | public static final int COMPLEX_RADIX_16p7 = 1;
192 | /**
193 | * Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
194 | */
195 | public static final int COMPLEX_RADIX_8p15 = 2;
196 | /**
197 | * Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
198 | */
199 | public static final int COMPLEX_RADIX_0p23 = 3;
200 |
201 | /**
202 | * Complex data: bit location of mantissa information.
203 | */
204 | public static final int COMPLEX_MANTISSA_SHIFT = 8;
205 | /**
206 | * Complex data: mask to extract mantissa information (after shifting by
207 | * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision;
208 | * the top bit is the sign.
209 | */
210 | public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
211 |
212 | /* ------------------------------------------------------------ */
213 |
214 | /**
215 | * {@link #TYPE_NULL} data indicating the value was not specified.
216 | */
217 | public static final int DATA_NULL_UNDEFINED = 0;
218 | /**
219 | * {@link #TYPE_NULL} data indicating the value was explicitly set to null.
220 | */
221 | public static final int DATA_NULL_EMPTY = 1;
222 |
223 | /* ------------------------------------------------------------ */
224 |
225 | /**
226 | * If {@link #density} is equal to this value, then the density should be
227 | * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
228 | */
229 | public static final int DENSITY_DEFAULT = 0;
230 |
231 | /**
232 | * If {@link #density} is equal to this value, then there is no density
233 | * associated with the resource and it should not be scaled.
234 | */
235 | public static final int DENSITY_NONE = 0xffff;
236 |
237 | /* ------------------------------------------------------------ */
238 |
239 | /**
240 | * The type held by this value, as defined by the constants here.
241 | * This tells you how to interpret the other fields in the object.
242 | */
243 | public int type;
244 |
245 | /**
246 | * If the value holds a string, this is it.
247 | */
248 | public CharSequence string;
249 |
250 | /**
251 | * Basic data in the value, interpreted according to {@link #type}
252 | */
253 | public int data;
254 |
255 | /**
256 | * Additional information about where the value came from; only
257 | * set for strings.
258 | */
259 | public int assetCookie;
260 |
261 | /**
262 | * If the Value came from a resource, this holds the corresponding pixel density.
263 | */
264 | public int density;
265 |
266 | /* ------------------------------------------------------------ */
267 |
268 | /**
269 | * Return the data for this value as a float. Only use for values
270 | * whose type is {@link #TYPE_FLOAT}.
271 | */
272 | public final float getFloat() {
273 | return Float.intBitsToFloat(data);
274 | }
275 |
276 | private static final float MANTISSA_MULT =
277 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT);
278 | private static final float[] RADIX_MULTS = new float[]{
279 | 1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT,
280 | 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT
281 | };
282 |
283 | /**
284 | * Retrieve the base value from a complex data integer. This uses the
285 | * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of
286 | * the data to compute a floating point representation of the number they
287 | * describe. The units are ignored.
288 | *
289 | * @param complex A complex data value.
290 | * @return A floating point value corresponding to the complex data.
291 | */
292 | public static float complexToFloat(int complex) {
293 | return (complex & (TypedValue.COMPLEX_MANTISSA_MASK
294 | << TypedValue.COMPLEX_MANTISSA_SHIFT))
295 | * RADIX_MULTS[(complex >> TypedValue.COMPLEX_RADIX_SHIFT)
296 | & TypedValue.COMPLEX_RADIX_MASK];
297 | }
298 |
299 | /**
300 | * Return the complex unit type for this value. For example, a dimen type
301 | * with value 12sp will return {@link #COMPLEX_UNIT_SP}. Only use for values
302 | * whose type is {@link #TYPE_DIMENSION}.
303 | *
304 | * @return The complex unit type.
305 | */
306 | public int getComplexUnit() {
307 | return COMPLEX_UNIT_MASK & (data >> TypedValue.COMPLEX_UNIT_SHIFT);
308 | }
309 |
310 |
311 | /**
312 | * Converts a complex data value holding a fraction to its final floating
313 | * point value. The given data must be structured as a
314 | * {@link #TYPE_FRACTION}.
315 | *
316 | * @param data A complex data value holding a unit, magnitude, and
317 | * mantissa.
318 | * @param base The base value of this fraction. In other words, a
319 | * standard fraction is multiplied by this value.
320 | * @param pbase The parent base value of this fraction. In other
321 | * words, a parent fraction (nn%p) is multiplied by this
322 | * value.
323 | * @return The complex floating point value multiplied by the appropriate
324 | * base value depending on its unit.
325 | */
326 | public static float complexToFraction(int data, float base, float pbase) {
327 | switch ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) {
328 | case COMPLEX_UNIT_FRACTION:
329 | return complexToFloat(data) * base;
330 | case COMPLEX_UNIT_FRACTION_PARENT:
331 | return complexToFloat(data) * pbase;
332 | }
333 | return 0;
334 | }
335 |
336 | /**
337 | * Return the data for this value as a fraction. Only use for values whose
338 | * type is {@link #TYPE_FRACTION}.
339 | *
340 | * @param base The base value of this fraction. In other words, a
341 | * standard fraction is multiplied by this value.
342 | * @param pbase The parent base value of this fraction. In other
343 | * words, a parent fraction (nn%p) is multiplied by this
344 | * value.
345 | * @return The complex floating point value multiplied by the appropriate
346 | * base value depending on its unit.
347 | */
348 | public float getFraction(float base, float pbase) {
349 | return complexToFraction(data, base, pbase);
350 | }
351 |
352 | /**
353 | * Regardless of the actual type of the value, try to convert it to a
354 | * string value. For example, a color type will be converted to a
355 | * string of the form #aarrggbb.
356 | *
357 | * @return CharSequence The coerced string value. If the value is
358 | * null or the type is not known, null is returned.
359 | */
360 | public final CharSequence coerceToString() {
361 | int t = type;
362 | if (t == TYPE_STRING) {
363 | return string;
364 | }
365 | return coerceToString(t, data);
366 | }
367 |
368 | private static final String[] DIMENSION_UNIT_STRS = new String[]{
369 | "px", "dip", "sp", "pt", "in", "mm"
370 | };
371 | private static final String[] FRACTION_UNIT_STRS = new String[]{
372 | "%", "%p"
373 | };
374 |
375 | /**
376 | * Perform type conversion as per {@link #coerceToString()} on an
377 | * explicitly supplied type and data.
378 | *
379 | * @param type The data type identifier.
380 | * @param data The data value.
381 | * @return String The coerced string value. If the value is
382 | * null or the type is not known, null is returned.
383 | */
384 | public static final String coerceToString(int type, int data) {
385 | switch (type) {
386 | case TYPE_NULL:
387 | return null;
388 | case TYPE_REFERENCE:
389 | return "@" + data;
390 | case TYPE_ATTRIBUTE:
391 | return "?" + data;
392 | case TYPE_FLOAT:
393 | return Float.toString(Float.intBitsToFloat(data));
394 | case TYPE_DIMENSION:
395 | return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[
396 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK];
397 | case TYPE_FRACTION:
398 | return Float.toString(complexToFloat(data) * 100) + FRACTION_UNIT_STRS[
399 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK];
400 | case TYPE_INT_HEX:
401 | return "0x" + Integer.toHexString(data);
402 | case TYPE_INT_BOOLEAN:
403 | return data != 0 ? "true" : "false";
404 | }
405 |
406 | if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) {
407 | return "#" + Integer.toHexString(data);
408 | } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
409 | return Integer.toString(data);
410 | }
411 |
412 | return null;
413 | }
414 |
415 |
416 | }
417 |
--------------------------------------------------------------------------------