├── README.md └── src ├── main ├── Main.iml └── java │ ├── com │ └── googlecode │ │ └── dex2jar │ │ └── reader │ │ └── io │ │ ├── ArrayDataIn.java │ │ ├── DataIn.java │ │ ├── DataOut.java │ │ └── LeDataOut.java │ └── pxb │ └── android │ └── axml │ ├── Axml.java │ ├── AxmlReader.java │ ├── AxmlVisitor.java │ ├── AxmlWriter.java │ ├── DumpAdapter.java │ ├── EnableDebugger.java │ ├── NodeVisitor.java │ ├── StringItem.java │ └── StringItems.java └── test ├── java └── pxb │ └── android │ └── axml │ └── test │ ├── Test2.java │ ├── Test3.java │ ├── Test4.java │ └── Test5.java └── resources ├── a.axml ├── b.axml ├── c.axml ├── d.axml └── manifest1.axml /README.md: -------------------------------------------------------------------------------- 1 | # axml 2 | A mini project to customize existing AXML library. The original source code can be found from following URL: 3 | http://code.google.com/p/axml/ 4 | 5 | Note: The latest version if now available from the original author's github repository. Thank you, pxb1988! 6 | https://github.com/pxb1988/axml 7 | -------------------------------------------------------------------------------- /src/main/Main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/dex2jar/reader/io/ArrayDataIn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package com.googlecode.dex2jar.reader.io; 17 | 18 | import java.io.ByteArrayInputStream; 19 | import java.io.IOException; 20 | import java.util.Stack; 21 | 22 | /** 23 | * 24 | * @author Panxiaobo 25 | * @version $Rev: bc7edba47c4f $ 26 | */ 27 | public class ArrayDataIn extends ByteArrayInputStream implements DataIn { 28 | public static ArrayDataIn be(byte[] data) { 29 | return new ArrayDataIn(data, false); 30 | } 31 | 32 | public static ArrayDataIn be(byte[] data, int offset, int length) { 33 | return new ArrayDataIn(data, offset, length, false); 34 | } 35 | 36 | public static ArrayDataIn le(byte[] data) { 37 | return new ArrayDataIn(data, true); 38 | } 39 | 40 | public static ArrayDataIn le(byte[] data, int offset, int length) { 41 | return new ArrayDataIn(data, offset, length, true); 42 | } 43 | 44 | private boolean isLE; 45 | 46 | private Stack stack = new Stack(); 47 | 48 | public ArrayDataIn(byte[] data, boolean isLE) { 49 | super(data); 50 | this.isLE = isLE; 51 | } 52 | 53 | public ArrayDataIn(byte[] buf, int offset, int length, boolean isLE) { 54 | super(buf, offset, length); 55 | this.isLE = isLE; 56 | } 57 | 58 | @Override 59 | public int getCurrentPosition() { 60 | return super.pos - super.mark; 61 | } 62 | 63 | @Override 64 | public void move(int absOffset) { 65 | super.pos = absOffset + super.mark; 66 | } 67 | 68 | @Override 69 | public void pop() { 70 | super.pos = stack.pop(); 71 | } 72 | 73 | @Override 74 | public void push() { 75 | stack.push(super.pos); 76 | } 77 | 78 | @Override 79 | public void pushMove(int absOffset) { 80 | this.push(); 81 | this.move(absOffset); 82 | } 83 | 84 | @Override 85 | public int readByte() { 86 | return (byte) readUByte(); 87 | } 88 | 89 | @Override 90 | public byte[] readBytes(int size) { 91 | byte[] data = new byte[size]; 92 | try { 93 | super.read(data); 94 | } catch (IOException e) { 95 | throw new RuntimeException(e); 96 | } 97 | return data; 98 | } 99 | 100 | @Override 101 | public int readIntx() { 102 | return readUIntx(); 103 | } 104 | 105 | @Override 106 | public long readLeb128() { 107 | int bitpos = 0; 108 | long vln = 0L; 109 | do { 110 | int inp = readUByte(); 111 | vln |= ((long) (inp & 0x7F)) << bitpos; 112 | bitpos += 7; 113 | if ((inp & 0x80) == 0) { 114 | break; 115 | } 116 | } while (true); 117 | if (((1L << (bitpos - 1)) & vln) != 0) { 118 | vln -= (1L << bitpos); 119 | } 120 | return vln; 121 | } 122 | 123 | @Override 124 | public int readShortx() { 125 | return (short) readUShortx(); 126 | } 127 | 128 | @Override 129 | public int readUByte() { 130 | if (super.pos >= super.count) { 131 | throw new RuntimeException("EOF"); 132 | } 133 | return super.read(); 134 | } 135 | 136 | @Override 137 | public int readUIntx() { 138 | if (isLE) { 139 | return readUByte() | (readUByte() << 8) | (readUByte() << 16) | (readUByte() << 24); 140 | } else { 141 | return (readUByte() << 24) | (readUByte() << 16) | (readUByte() << 8) | readUByte(); 142 | } 143 | } 144 | 145 | @Override 146 | public long readULeb128() { 147 | long value = 0; 148 | int count = 0; 149 | int b = readUByte(); 150 | while ((b & 0x80) != 0) { 151 | value |= (b & 0x7f) << count; 152 | count += 7; 153 | b = readUByte(); 154 | } 155 | value |= (b & 0x7f) << count; 156 | return value; 157 | } 158 | 159 | @Override 160 | public int readUShortx() { 161 | if (isLE) { 162 | return readUByte() | (readUByte() << 8); 163 | } else { 164 | return (readUByte() << 8) | readUByte(); 165 | } 166 | } 167 | 168 | @Override 169 | public void skip(int bytes) { 170 | super.skip(bytes); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/dex2jar/reader/io/DataIn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package com.googlecode.dex2jar.reader.io; 17 | 18 | /** 19 | * 输入流 20 | * 21 | * @author Panxiaobo 22 | * @version $Rev: 3b20152caede $ 23 | */ 24 | public interface DataIn { 25 | 26 | /** 27 | * 获取当前位置 28 | * 29 | * @return 30 | */ 31 | int getCurrentPosition(); 32 | 33 | void move(int absOffset); 34 | 35 | void pop(); 36 | 37 | void push(); 38 | 39 | /** 40 | * equals to 41 | * 42 | *
43 |      * push();
44 |      * move(absOffset);
45 |      * 
46 | * 47 | * @see #push() 48 | * @see #move(int) 49 | * @param absOffset 50 | */ 51 | void pushMove(int absOffset); 52 | 53 | /** 54 | * 55 | */ 56 | int readByte(); 57 | 58 | byte[] readBytes(int size); 59 | 60 | int readIntx(); 61 | 62 | int readUIntx(); 63 | 64 | int readShortx(); 65 | 66 | int readUShortx(); 67 | 68 | long readLeb128(); 69 | 70 | /** 71 | * @return 72 | */ 73 | int readUByte(); 74 | 75 | long readULeb128(); 76 | 77 | /** 78 | * @param i 79 | */ 80 | void skip(int bytes); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/dex2jar/reader/io/DataOut.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.dex2jar.reader.io; 2 | 3 | import java.io.IOException; 4 | 5 | public interface DataOut { 6 | void writeByte(int b) throws IOException; 7 | 8 | void writeBytes(byte[] bs) throws IOException; 9 | 10 | void writeInt(int i) throws IOException; 11 | 12 | void writeShort(int i) throws IOException; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/dex2jar/reader/io/LeDataOut.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.dex2jar.reader.io; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class LeDataOut implements DataOut { 7 | 8 | private OutputStream os; 9 | 10 | public LeDataOut(OutputStream os) { 11 | super(); 12 | this.os = os; 13 | } 14 | 15 | @Override 16 | public void writeByte(int v) throws IOException { 17 | os.write(v); 18 | } 19 | 20 | @Override 21 | public void writeBytes(byte[] bs) throws IOException { 22 | os.write(bs); 23 | } 24 | 25 | @Override 26 | public void writeInt(int v) throws IOException { 27 | os.write(v); 28 | os.write(v >> 8); 29 | os.write(v >> 16); 30 | os.write(v >>> 24); 31 | } 32 | 33 | @Override 34 | public void writeShort(int v) throws IOException { 35 | os.write(v); 36 | os.write(v >> 8); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/Axml.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | public class Axml extends AxmlVisitor { 22 | 23 | public static class Node extends NodeVisitor { 24 | public static class Attr { 25 | public String ns, name; 26 | public int resourceId, type; 27 | public Object value; 28 | 29 | public void accept(NodeVisitor nodeVisitor) { 30 | nodeVisitor.visitContentAttr(ns, name, resourceId, type, value); 31 | } 32 | } 33 | 34 | public static class Text { 35 | public int ln; 36 | public String text; 37 | 38 | public void accept(NodeVisitor nodeVisitor) { 39 | nodeVisitor.visitContentText(ln, text); 40 | } 41 | } 42 | 43 | public List attrs = new ArrayList(); 44 | public List children = new ArrayList(); 45 | public Integer ln; 46 | public String ns, name; 47 | public Text text; 48 | 49 | public void accept(NodeVisitor nodeVisitor) { 50 | NodeVisitor nodeVisitor2 = nodeVisitor.visitChild(ns, name); 51 | nodeVisitor2.visitBegin(); 52 | acceptB(nodeVisitor2); 53 | nodeVisitor2.visitEnd(); 54 | } 55 | 56 | public void acceptB(NodeVisitor nodeVisitor) { 57 | if (ln != null) { 58 | nodeVisitor.visitLineNumber(ln); 59 | } 60 | for (Attr a : attrs) { 61 | a.accept(nodeVisitor); 62 | } 63 | if (text != null) { 64 | text.accept(nodeVisitor); 65 | } 66 | nodeVisitor.visitContentEnd(); 67 | for (Node c : children) { 68 | c.accept(nodeVisitor); 69 | } 70 | } 71 | 72 | @Override 73 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) { 74 | Attr attr = new Attr(); 75 | attr.name = name; 76 | attr.ns = ns; 77 | attr.resourceId = resourceId; 78 | attr.type = type; 79 | attr.value = obj; 80 | attrs.add(attr); 81 | } 82 | 83 | @Override 84 | public NodeVisitor visitChild(String ns, String name) { 85 | Node node = new Node(); 86 | node.name = name; 87 | node.ns = ns; 88 | children.add(node); 89 | return node; 90 | } 91 | 92 | @Override 93 | public void visitLineNumber(int ln) { 94 | this.ln = ln; 95 | } 96 | 97 | @Override 98 | public void visitContentText(int lineNumber, String value) { 99 | Text text = new Text(); 100 | text.ln = lineNumber; 101 | text.text = value; 102 | this.text = text; 103 | } 104 | } 105 | 106 | public static class Ns { 107 | public int ln; 108 | public String prefix, uri; 109 | 110 | public void accept(AxmlVisitor visitor) { 111 | visitor.visitNamespace(prefix, uri, ln); 112 | } 113 | } 114 | 115 | public List firsts = new ArrayList(); 116 | public List nses = new ArrayList(); 117 | 118 | public void accept(final AxmlVisitor visitor) { 119 | visitor.visitBegin(); 120 | 121 | for (Ns ns : nses) { 122 | ns.accept(visitor); 123 | } 124 | for (Node first : firsts) { 125 | first.accept(new NodeVisitor(null) { 126 | 127 | @Override 128 | public NodeVisitor visitChild(String ns, String name) { 129 | return visitor.visitFirst(ns, name); 130 | } 131 | }); 132 | } 133 | visitor.visitEnd(); 134 | } 135 | 136 | @Override 137 | public NodeVisitor visitFirst(String ns, String name) { 138 | Node node = new Node(); 139 | node.name = name; 140 | node.ns = ns; 141 | firsts.add(node); 142 | return node; 143 | } 144 | 145 | @Override 146 | public void visitNamespace(String prefix, String uri, int ln) { 147 | Ns ns = new Ns(); 148 | ns.prefix = prefix; 149 | ns.uri = uri; 150 | ns.ln = ln; 151 | nses.add(ns); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/AxmlReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | import com.googlecode.dex2jar.reader.io.ArrayDataIn; 19 | import com.googlecode.dex2jar.reader.io.DataIn; 20 | 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Stack; 26 | 27 | import static pxb.android.axml.AxmlVisitor.TYPE_INT_BOOLEAN; 28 | import static pxb.android.axml.AxmlVisitor.TYPE_STRING; 29 | 30 | /** 31 | * a class to read android axml 32 | * 33 | * @author Panxiaobo 34 | * 35 | */ 36 | public class AxmlReader { 37 | static final int CHUNK_AXML_FILE = 0x00080003; 38 | static final int CHUNK_RESOURCEIDS = 0x00080180; 39 | static final int CHUNK_STRINGS = 0x001C0001; 40 | static final int CHUNK_XML_END_NAMESPACE = 0x00100101; 41 | static final int CHUNK_XML_END_TAG = 0x00100103; 42 | static final int CHUNK_XML_START_NAMESPACE = 0x00100100; 43 | static final int CHUNK_XML_START_TAG = 0x00100102; 44 | static final int CHUNK_XML_TEXT = 0x00100104; 45 | public static final NodeVisitor EMPTY_VISITOR = new NodeVisitor() { 46 | 47 | @Override 48 | public NodeVisitor visitChild(String namespace, String name) { 49 | return EMPTY_VISITOR; 50 | } 51 | }; 52 | static final int UTF8_FLAG = 0x00000100; 53 | private DataIn input; 54 | private List resourceIds = new ArrayList(); 55 | private StringItems stringItems = new StringItems(); 56 | 57 | public AxmlReader(byte[] data) { 58 | this(ArrayDataIn.le(data)); 59 | } 60 | 61 | public AxmlReader(DataIn input) { 62 | super(); 63 | this.input = input; 64 | } 65 | 66 | public static AxmlReader create(InputStream is){ 67 | try{ 68 | int available = is.available(); 69 | byte[] buffer = new byte[available]; 70 | 71 | int len = is.read(buffer); 72 | if(len == available){ 73 | return new AxmlReader(buffer); 74 | } 75 | } 76 | catch(Exception e){} 77 | return null; 78 | } 79 | 80 | public void accept(final AxmlVisitor documentVisitor) throws IOException { 81 | DataIn input = this.input; 82 | int fileSize; 83 | { 84 | int type = input.readIntx(); 85 | if (type != CHUNK_AXML_FILE) { 86 | throw new RuntimeException(); 87 | } 88 | fileSize = input.readIntx(); 89 | } 90 | NodeVisitor rootVisitor = documentVisitor == null ? EMPTY_VISITOR : new NodeVisitor() { 91 | @Override 92 | public NodeVisitor visitChild(String namespace, String name) { 93 | return documentVisitor.visitFirst(namespace, name); 94 | } 95 | }; 96 | 97 | NodeVisitor stackTop; 98 | Stack nodeVisitorStack = new Stack(); 99 | Stack nodeVisitorContentsHandled = new Stack(); 100 | 101 | String name, namespace; 102 | int nameIdx, namespaceIdx; 103 | int lineNumber; 104 | 105 | stackTop = rootVisitor; 106 | nodeVisitorStack.push(rootVisitor); 107 | nodeVisitorContentsHandled.push(false); 108 | 109 | documentVisitor.visitBegin(); 110 | 111 | for (int position = input.getCurrentPosition(); position < fileSize; position = input.getCurrentPosition()) { 112 | int type = input.readIntx(); 113 | int size = input.readIntx(); 114 | switch (type) { 115 | case CHUNK_XML_START_TAG: { 116 | { 117 | lineNumber = input.readIntx(); 118 | input.skip(4);/* 0xFFFFFFFF */ 119 | namespaceIdx = input.readIntx(); 120 | nameIdx = input.readIntx(); 121 | int flag = input.readIntx();// 0x00140014 ? 122 | if (flag != 0x00140014) { 123 | throw new RuntimeException(); 124 | } 125 | name = stringItems.get(nameIdx).data; 126 | namespace = namespaceIdx >= 0 ? stringItems.get(namespaceIdx).data : null; 127 | 128 | if(!nodeVisitorContentsHandled.peek()){ 129 | nodeVisitorContentsHandled.pop(); 130 | nodeVisitorContentsHandled.push(true); 131 | stackTop.visitContentEnd(); 132 | } 133 | 134 | stackTop = stackTop.visitChild(namespace, name); 135 | if (stackTop == null) { 136 | stackTop = EMPTY_VISITOR; 137 | } 138 | nodeVisitorStack.push(stackTop); 139 | nodeVisitorContentsHandled.push(false); 140 | stackTop.visitBegin(); 141 | stackTop.visitLineNumber(lineNumber); 142 | } 143 | 144 | int attributeCount = input.readUShortx(); 145 | // int idAttribute = input.readUShortx(); 146 | // int classAttribute = input.readUShortx(); 147 | // int styleAttribute = input.readUShortx(); 148 | input.skip(6); 149 | if (stackTop != EMPTY_VISITOR) { 150 | for (int i = 0; i < attributeCount; i++) { 151 | namespaceIdx = input.readIntx(); 152 | nameIdx = input.readIntx(); 153 | input.skip(4);// skip valueString 154 | int aValueType = input.readIntx() >>> 24; 155 | int aValue = input.readIntx(); 156 | name = stringItems.get(nameIdx).data; 157 | namespace = namespaceIdx >= 0 ? stringItems.get(namespaceIdx).data : null; 158 | Object value = null; 159 | switch (aValueType) { 160 | case TYPE_STRING: 161 | value = stringItems.get(aValue).data; 162 | break; 163 | case TYPE_INT_BOOLEAN: 164 | value = aValue != 0; 165 | break; 166 | default: 167 | value = aValue; 168 | } 169 | int resourceId = nameIdx < resourceIds.size() ? resourceIds.get(nameIdx) : -1; 170 | stackTop.visitContentAttr(namespace, name, resourceId, aValueType, value); 171 | } 172 | } else { 173 | input.skip(5 * 4); 174 | } 175 | } 176 | break; 177 | case CHUNK_XML_END_TAG: { 178 | input.skip(size - 8); 179 | 180 | if(!nodeVisitorContentsHandled.peek()){ 181 | stackTop.visitContentEnd(); 182 | nodeVisitorContentsHandled.pop(); 183 | nodeVisitorContentsHandled.push(true); 184 | } 185 | stackTop.visitEnd(); 186 | 187 | nodeVisitorStack.pop(); 188 | stackTop = nodeVisitorStack.peek(); 189 | } 190 | break; 191 | case CHUNK_XML_START_NAMESPACE: 192 | if (documentVisitor == null) { 193 | input.skip(4 * 4); 194 | } else { 195 | lineNumber = input.readIntx(); 196 | input.skip(4);/* 0xFFFFFFFF */ 197 | int prefixIdx = input.readIntx(); 198 | namespaceIdx = input.readIntx(); 199 | documentVisitor.visitNamespace(stringItems.get(prefixIdx).data, stringItems.get(namespaceIdx).data, lineNumber); 200 | } 201 | break; 202 | case CHUNK_XML_END_NAMESPACE: 203 | input.skip(size - 8); 204 | break; 205 | case CHUNK_STRINGS: 206 | stringItems.read(input, size); 207 | break; 208 | case CHUNK_RESOURCEIDS: 209 | int count = size / 4 - 2; 210 | for (int i = 0; i < count; i++) { 211 | resourceIds.add(input.readIntx()); 212 | } 213 | break; 214 | case CHUNK_XML_TEXT: 215 | if (stackTop == EMPTY_VISITOR) { 216 | input.skip(20); 217 | } else { 218 | lineNumber = input.readIntx(); 219 | input.skip(4);/* 0xFFFFFFFF */ 220 | nameIdx = input.readIntx(); 221 | input.skip(8); /* 00000008 00000000 */ 222 | name = stringItems.get(nameIdx).data; 223 | stackTop.visitContentText(lineNumber, name); 224 | } 225 | break; 226 | default: 227 | throw new RuntimeException(); 228 | } 229 | input.move(position + size); 230 | } 231 | 232 | documentVisitor.visitEnd(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/AxmlVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | /* 19 | AXMLVisitor visiting sequence : visitBegin [visitNamespace]* visitFirstNode visitEnd 20 | NodeVisitor visiting sequence : visitBegin [visitContentAttr | visitContentText]* visitContentEnd visitChildNoe visitEnd 21 | */ 22 | 23 | /** 24 | * visitor to visit an axml 25 | * 26 | * @author Panxiaobo 27 | * 28 | */ 29 | public class AxmlVisitor { 30 | 31 | public static final int TYPE_FIRST_INT = 0x10; 32 | public static final int TYPE_INT_BOOLEAN = 0x12; 33 | public static final int TYPE_INT_HEX = 0x11; 34 | public static final int TYPE_REFERENCE = 0x01; 35 | 36 | public static final int TYPE_STRING = 0x03; 37 | 38 | protected AxmlVisitor av; 39 | 40 | public AxmlVisitor() { 41 | super(); 42 | 43 | } 44 | 45 | public AxmlVisitor(AxmlVisitor av) { 46 | super(); 47 | this.av = av; 48 | }; 49 | 50 | public void visitBegin(){ 51 | if (av != null){ 52 | av.visitBegin(); 53 | } 54 | } 55 | 56 | /** 57 | * end the visit 58 | */ 59 | public void visitEnd() { 60 | if (av != null) { 61 | av.visitEnd(); 62 | } 63 | }; 64 | 65 | /** 66 | * create the first node 67 | * 68 | * @param namespace 69 | * @param name 70 | * @return 71 | */ 72 | public NodeVisitor visitFirst(String namespace, String name) { 73 | if (av != null) { 74 | return av.visitFirst(namespace, name); 75 | } 76 | return null; 77 | } 78 | 79 | /** 80 | * create a namespace 81 | * 82 | * @param prefix 83 | * @param uri 84 | * @param ln 85 | */ 86 | public void visitNamespace(String prefix, String uri, int ln) { 87 | if (av != null) { 88 | av.visitNamespace(prefix, uri, ln); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/AxmlWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | import com.googlecode.dex2jar.reader.io.DataOut; 19 | import com.googlecode.dex2jar.reader.io.LeDataOut; 20 | 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.IOException; 23 | import java.io.OutputStream; 24 | import java.util.*; 25 | 26 | /** 27 | * a class to write android axml 28 | * 29 | * @author Panxiaobo 30 | * 31 | */ 32 | public class AxmlWriter extends AxmlVisitor { 33 | static class Attr { 34 | public StringItem name; 35 | public StringItem ns; 36 | public int resourceId; 37 | public int type; 38 | public Object value; 39 | 40 | public Attr(StringItem ns, StringItem name, int resourceId, int type, Object value) { 41 | super(); 42 | this.ns = ns; 43 | this.name = name; 44 | this.resourceId = resourceId; 45 | this.type = type; 46 | this.value = value; 47 | } 48 | 49 | public void prepare(AxmlWriter axmlWriter) { 50 | ns = axmlWriter.updateNs(ns); 51 | if (this.name != null) { 52 | if (resourceId != -1) { 53 | this.name = axmlWriter.updateWithResourceId(this.name, this.resourceId); 54 | } else { 55 | this.name = axmlWriter.update(this.name); 56 | } 57 | } 58 | if (value instanceof StringItem) { 59 | value = axmlWriter.update((StringItem) value); 60 | } 61 | } 62 | } 63 | 64 | static class NodeImpl extends NodeVisitor { 65 | private Map attrs = new HashMap(); 66 | private List children = new ArrayList(); 67 | private int line; 68 | private StringItem name; 69 | private StringItem ns; 70 | private StringItem text; 71 | private int textLineNumber; 72 | 73 | public NodeImpl(String ns, String name) { 74 | super(null); 75 | this.ns = ns == null ? null : new StringItem(ns); 76 | this.name = name == null ? null : new StringItem(name); 77 | } 78 | 79 | @Override 80 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object value) { 81 | if (name == null) { 82 | throw new RuntimeException("name can't be null"); 83 | } 84 | attrs.put((ns == null ? "zzz" : ns) + "." + name, new Attr(ns == null ? null : new StringItem(ns), 85 | new StringItem(name), resourceId, type, type == TYPE_STRING ? new StringItem((String) value) 86 | : value)); 87 | } 88 | 89 | @Override 90 | public NodeVisitor visitChild(String ns, String name) { 91 | NodeImpl child = new NodeImpl(ns, name); 92 | this.children.add(child); 93 | return child; 94 | } 95 | 96 | @Override 97 | public void visitEnd() { 98 | } 99 | 100 | @Override 101 | public void visitLineNumber(int ln) { 102 | this.line = ln; 103 | } 104 | 105 | public int prepare(AxmlWriter axmlWriter) { 106 | ns = axmlWriter.updateNs(ns); 107 | name = axmlWriter.update(name); 108 | 109 | for (Attr attr : this.sortedAttrs()) { 110 | attr.prepare(axmlWriter); 111 | } 112 | text = axmlWriter.update(text); 113 | int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for 114 | // start tag 115 | for (NodeImpl child : children) { 116 | size += child.prepare(axmlWriter); 117 | } 118 | if (text != null) { 119 | size += 28; 120 | } 121 | return size; 122 | } 123 | 124 | List sortedAttrs() { 125 | List lAttrs = new ArrayList(attrs.values()); 126 | Collections.sort(lAttrs, new Comparator() { 127 | 128 | @Override 129 | public int compare(Attr a, Attr b) { 130 | if (a.ns == null) { 131 | if (b.ns == null) { 132 | return b.name.data.compareTo(a.name.data); 133 | } else { 134 | return 1; 135 | } 136 | } else if (b.ns == null) { 137 | return -1; 138 | } else { 139 | int x = a.ns.data.compareTo(b.ns.data); 140 | if (x == 0) { 141 | x = a.resourceId - b.resourceId; 142 | if (x == 0) { 143 | return a.name.data.compareTo(b.name.data); 144 | } 145 | } 146 | return x; 147 | } 148 | } 149 | }); 150 | return lAttrs; 151 | } 152 | 153 | @Override 154 | public void visitContentText(int ln, String value) { 155 | this.text = new StringItem(value); 156 | this.textLineNumber = ln; 157 | } 158 | 159 | void write(DataOut out) throws IOException { 160 | // start tag 161 | out.writeInt(AxmlReader.CHUNK_XML_START_TAG); 162 | out.writeInt(36 + attrs.size() * 20); 163 | out.writeInt(line); 164 | out.writeInt(0xFFFFFFFF); 165 | out.writeInt(ns != null ? this.ns.index : -1); 166 | out.writeInt(name.index); 167 | out.writeInt(0x00140014);// TODO 168 | out.writeShort(this.attrs.size()); 169 | out.writeShort(0); 170 | out.writeShort(0); 171 | out.writeShort(0); 172 | for (Attr attr : this.sortedAttrs()) { 173 | out.writeInt(attr.ns == null ? -1 : attr.ns.index); 174 | out.writeInt(attr.name.index); 175 | out.writeInt(attr.value instanceof StringItem ? ((StringItem) attr.value).index : -1); 176 | out.writeInt((attr.type << 24) | 0x000008); 177 | Object v = attr.value; 178 | if (v instanceof StringItem) { 179 | out.writeInt(((StringItem) attr.value).index); 180 | } else if (v instanceof Boolean) { 181 | out.writeInt(Boolean.TRUE.equals(v) ? -1 : 0); 182 | } else { 183 | out.writeInt((Integer) attr.value); 184 | } 185 | } 186 | 187 | if (this.text != null) { 188 | out.writeInt(AxmlReader.CHUNK_XML_TEXT); 189 | out.writeInt(28); 190 | out.writeInt(textLineNumber); 191 | out.writeInt(0xFFFFFFFF); 192 | out.writeInt(text.index); 193 | out.writeInt(0x00000008); 194 | out.writeInt(0x00000000); 195 | } 196 | 197 | // children 198 | for (NodeImpl child : children) { 199 | child.write(out); 200 | } 201 | 202 | // end tag 203 | out.writeInt(AxmlReader.CHUNK_XML_END_TAG); 204 | out.writeInt(24); 205 | out.writeInt(-1); 206 | out.writeInt(0xFFFFFFFF); 207 | out.writeInt(ns != null ? this.ns.index : -1); 208 | out.writeInt(name.index); 209 | } 210 | } 211 | 212 | static class Ns { 213 | int ln; 214 | StringItem prefix; 215 | StringItem uri; 216 | 217 | public Ns(StringItem prefix, StringItem uri, int ln) { 218 | super(); 219 | this.prefix = prefix; 220 | this.uri = uri; 221 | this.ln = ln; 222 | } 223 | } 224 | 225 | private List firsts = new ArrayList(3); 226 | 227 | private Map nses = new HashMap(); 228 | 229 | private List otherString = new ArrayList(); 230 | 231 | private Map resourceId2Str = new HashMap(); 232 | 233 | private List resourceIds = new ArrayList(); 234 | 235 | private List resourceString = new ArrayList(); 236 | 237 | private StringItems stringItems = new StringItems(); 238 | 239 | // TODO add style support 240 | // private List styleItems = new ArrayList(); 241 | 242 | @Override 243 | public void visitEnd() { 244 | } 245 | 246 | @Override 247 | public NodeVisitor visitFirst(String ns, String name) { 248 | NodeImpl first = new NodeImpl(ns, name); 249 | this.firsts.add(first); 250 | return first; 251 | } 252 | 253 | @Override 254 | public void visitNamespace(String prefix, String uri, int ln) { 255 | nses.put(uri, new Ns(new StringItem(prefix), new StringItem(uri), ln)); 256 | } 257 | 258 | private int prepare() throws IOException { 259 | 260 | int size = nses.size() * 24 * 2; 261 | for (NodeImpl first : firsts) { 262 | size += first.prepare(this); 263 | } 264 | { 265 | int a = 0; 266 | for (Map.Entry e : nses.entrySet()) { 267 | Ns ns = e.getValue(); 268 | if (ns == null) { 269 | ns = new Ns(new StringItem(String.format("axml_auto_%02d", a++)), new StringItem(e.getKey()), 0); 270 | e.setValue(ns); 271 | } 272 | ns.prefix = update(ns.prefix); 273 | ns.uri = update(ns.uri); 274 | } 275 | } 276 | this.stringItems.addAll(resourceString); 277 | resourceString = null; 278 | this.stringItems.addAll(otherString); 279 | otherString = null; 280 | this.stringItems.prepare(); 281 | int stringSize = this.stringItems.getSize(); 282 | if (stringSize % 4 != 0) { 283 | stringSize += 4 - stringSize % 4; 284 | } 285 | size += 8 + stringSize; 286 | size += 8 + resourceIds.size() * 4; 287 | return size; 288 | } 289 | 290 | public void writeTo(OutputStream os) throws IOException{ 291 | DataOut out = new LeDataOut(os); 292 | int size = prepare(); 293 | out.writeInt(AxmlReader.CHUNK_AXML_FILE); 294 | out.writeInt(size + 8); 295 | 296 | int stringSize = this.stringItems.getSize(); 297 | int padding = 0; 298 | if (stringSize % 4 != 0) { 299 | padding = 4 - stringSize % 4; 300 | } 301 | out.writeInt(AxmlReader.CHUNK_STRINGS); 302 | out.writeInt(stringSize + padding + 8); 303 | this.stringItems.write(out); 304 | out.writeBytes(new byte[padding]); 305 | 306 | out.writeInt(AxmlReader.CHUNK_RESOURCEIDS); 307 | out.writeInt(8 + this.resourceIds.size() * 4); 308 | for (Integer i : resourceIds) { 309 | out.writeInt(i); 310 | } 311 | 312 | Stack stack = new Stack(); 313 | for (Map.Entry e : this.nses.entrySet()) { 314 | Ns ns = e.getValue(); 315 | stack.push(ns); 316 | out.writeInt(AxmlReader.CHUNK_XML_START_NAMESPACE); 317 | out.writeInt(24); 318 | out.writeInt(-1); 319 | out.writeInt(0xFFFFFFFF); 320 | out.writeInt(ns.prefix.index); 321 | out.writeInt(ns.uri.index); 322 | } 323 | 324 | for (NodeImpl first : firsts) { 325 | first.write(out); 326 | } 327 | 328 | while (stack.size() > 0) { 329 | Ns ns = stack.pop(); 330 | out.writeInt(AxmlReader.CHUNK_XML_END_NAMESPACE); 331 | out.writeInt(24); 332 | out.writeInt(ns.ln); 333 | out.writeInt(0xFFFFFFFF); 334 | out.writeInt(ns.prefix.index); 335 | out.writeInt(ns.uri.index); 336 | } 337 | } 338 | 339 | public byte[] toByteArray() throws IOException { 340 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 341 | this.writeTo(os); 342 | return os.toByteArray(); 343 | } 344 | 345 | StringItem update(StringItem item) { 346 | if (item == null) 347 | return null; 348 | int i = this.otherString.indexOf(item); 349 | if (i < 0) { 350 | StringItem copy = new StringItem(item.data); 351 | this.otherString.add(copy); 352 | return copy; 353 | } else { 354 | return this.otherString.get(i); 355 | } 356 | } 357 | 358 | StringItem updateNs(StringItem item) { 359 | if (item == null) { 360 | return null; 361 | } 362 | String ns = item.data; 363 | if (!this.nses.containsKey(ns)) { 364 | this.nses.put(ns, null); 365 | } 366 | return update(item); 367 | } 368 | 369 | StringItem updateWithResourceId(StringItem name, int resourceId) { 370 | StringItem item = this.resourceId2Str.get(resourceId); 371 | if (item != null) { 372 | return item; 373 | } else { 374 | StringItem copy = new StringItem(name.data); 375 | resourceIds.add(resourceId); 376 | resourceString.add(copy); 377 | resourceId2Str.put(resourceId, copy); 378 | return copy; 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/DumpAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | import java.io.PrintWriter; 19 | import java.io.Writer; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * dump axml to stdout 25 | * 26 | * @author Panxiaobo 27 | * 28 | */ 29 | public class DumpAdapter extends AxmlVisitor { 30 | 31 | /** 32 | * dump a node to stdout 33 | * 34 | * @author Panxiaobo 35 | * 36 | */ 37 | public static class DumpNodeAdapter extends NodeVisitor { 38 | protected final int depth; 39 | protected Map nses; 40 | protected String namespace; 41 | protected String name; 42 | protected PrintWriter writer; 43 | 44 | public DumpNodeAdapter(PrintWriter w, NodeVisitor nv, String namespace, String name) { 45 | super(nv); 46 | this.depth = 0; 47 | this.nses = null; 48 | this.namespace = namespace; 49 | this.name = name; 50 | this.writer = w; 51 | } 52 | 53 | public DumpNodeAdapter(PrintWriter w, NodeVisitor nv, int x, Map nses, String namespace, String name) { 54 | super(nv); 55 | this.depth = x; 56 | this.nses = nses; 57 | this.namespace = namespace; 58 | this.name = name; 59 | this.writer = w; 60 | } 61 | 62 | @Override 63 | public void visitBegin(){ 64 | indent(); 65 | writer.print("<"); 66 | if (namespace != null) { 67 | writer.println(getPrefix(namespace) + ":"); 68 | } 69 | writer.print(name); 70 | super.visitBegin(); 71 | } 72 | 73 | 74 | @Override 75 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) { 76 | writer.println(); 77 | 78 | indentContent(); 79 | if (ns != null) { 80 | writer.print(String.format("%s:", getPrefix(ns))); 81 | } 82 | writer.print(name); 83 | if (resourceId != -1) { 84 | writer.print(String.format("(%08x)", resourceId)); 85 | } 86 | if (obj instanceof String) { 87 | writer.print(String.format("=[%08x]\"%s\"", type, obj)); 88 | } else if (obj instanceof Boolean) { 89 | writer.print(String.format("=[%08x]\"%b\"", type, obj)); 90 | } else { 91 | writer.print(String.format("=[%08x]%08x", type, obj)); 92 | } 93 | 94 | //writer.println(); 95 | super.visitContentAttr(ns, name, resourceId, type, obj); 96 | } 97 | 98 | @Override 99 | public void visitContentEnd(){ 100 | writer.println(">"); 101 | super.visitContentEnd(); 102 | } 103 | 104 | @Override 105 | public NodeVisitor visitChild(String namespace, String name) { 106 | NodeVisitor nv = super.visitChild(namespace, name); 107 | return new DumpNodeAdapter(writer, nv, depth + 1, nses, namespace, name); 108 | } 109 | 110 | protected String getPrefix(String uri) { 111 | if (nses != null) { 112 | String prefix = nses.get(uri); 113 | if (prefix != null) { 114 | return prefix; 115 | } 116 | } 117 | return uri; 118 | } 119 | 120 | @Override 121 | public void visitContentText(int ln, String value) { 122 | writer.println(); 123 | 124 | indentContent(); 125 | writer.print(value); 126 | super.visitContentText(ln, value); 127 | } 128 | 129 | @Override 130 | public void visitLineNumber(int n){ 131 | writer.println(); 132 | indentContent(); 133 | writer.print("L"+n); 134 | super.visitLineNumber(n); 135 | } 136 | 137 | @Override 138 | public void visitEnd(){ 139 | indent(); 140 | writer.println(""); 141 | super.visitEnd(); 142 | } 143 | 144 | private void indent(){ 145 | for (int i = 0; i < depth; i++) { 146 | writer.print(" "); 147 | } 148 | } 149 | 150 | private void indentContent(){ 151 | indent(); 152 | writer.print(" "); 153 | } 154 | } 155 | 156 | private Map nses = new HashMap(); 157 | private PrintWriter writer = null; 158 | 159 | public DumpAdapter() { 160 | setupWriter(null); 161 | } 162 | 163 | public DumpAdapter(Writer w) { 164 | setupWriter(w); 165 | } 166 | 167 | public DumpAdapter(Writer w, AxmlVisitor av) { 168 | super(av); 169 | setupWriter(w); 170 | } 171 | 172 | private void setupWriter(Writer w){ 173 | if(w == null){ 174 | writer = new PrintWriter(System.out, true); 175 | } 176 | else{ 177 | writer = new PrintWriter(w); 178 | } 179 | } 180 | 181 | @Override 182 | public void visitBegin(){ 183 | writer.println(""); 184 | super.visitBegin(); 185 | } 186 | 187 | @Override 188 | public NodeVisitor visitFirst(String namespace, String name) { 189 | NodeVisitor nv = super.visitFirst(namespace, name); 190 | //if (nv != null) { 191 | DumpNodeAdapter x = new DumpNodeAdapter(writer, nv, 1, nses, namespace, name); 192 | return x; 193 | //} 194 | //else{ 195 | // return new DumpNodeAdapter(null, 1, nses); 196 | //} 197 | } 198 | 199 | 200 | @Override 201 | public void visitNamespace(String prefix, String uri, int ln) { 202 | writer.println("xmlns:" + prefix + "=" + uri); 203 | this.nses.put(uri, prefix); 204 | super.visitNamespace(prefix, uri, ln); 205 | } 206 | 207 | @Override 208 | public void visitEnd(){ 209 | writer.println(""); 210 | super.visitEnd(); 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/EnableDebugger.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.InputStream; 7 | 8 | 9 | public class EnableDebugger { 10 | // @Test 11 | // public void test() throws Exception { 12 | // a(new File("src/test/resources/a.axml"), new File("target/a-debug.axml")); 13 | // } 14 | 15 | public static void main(String... args) throws Exception { 16 | if (args.length < 2) { 17 | System.err.println("test5 in out"); 18 | return; 19 | } 20 | new EnableDebugger().a(new File(args[0]), new File(args[1])); 21 | } 22 | 23 | void a(File a, File b) throws Exception { 24 | InputStream is = new FileInputStream(a); 25 | byte[] xml = new byte[is.available()]; 26 | is.read(xml); 27 | is.close(); 28 | 29 | AxmlReader rd = new AxmlReader(xml); 30 | AxmlWriter wr = new AxmlWriter(); 31 | rd.accept(new AxmlVisitor(wr) { 32 | 33 | @Override 34 | public NodeVisitor visitFirst(String ns, String name) {// manifest 35 | return new NodeVisitor(super.visitFirst(ns, name)) { 36 | 37 | @Override 38 | public NodeVisitor visitChild(String ns, String name) {// application 39 | return new NodeVisitor(super.visitChild(ns, name)) { 40 | 41 | @Override 42 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) { 43 | if ("http://schemas.android.com/apk/res/android".equals(ns) 44 | && "debuggable".equals(name)) { 45 | return; 46 | } 47 | super.visitContentAttr(ns, name, resourceId, type, obj); 48 | } 49 | 50 | @Override 51 | public void visitEnd() { 52 | // android:debuggable(0x0101000f)=(type 0x12)0xffffffff 53 | super.visitContentAttr("http://schemas.android.com/apk/res/android", "debuggable", 0x0101000f, 54 | 0x12, 0xffffffff); 55 | super.visitEnd(); 56 | } 57 | }; 58 | } 59 | }; 60 | } 61 | 62 | }); 63 | byte[] modified = wr.toByteArray(); 64 | FileOutputStream fos = new FileOutputStream(b); 65 | fos.write(modified); 66 | fos.close(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/NodeVisitor.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml; 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: wtchoi 6 | * Date: 10/20/12 7 | * Time: 1:30 AM 8 | * To change this template use File | Settings | File Templates. 9 | */ 10 | public class NodeVisitor { 11 | protected NodeVisitor nv; 12 | 13 | public NodeVisitor() { 14 | super(); 15 | } 16 | 17 | public NodeVisitor(NodeVisitor nv) { 18 | super(); 19 | this.nv = nv; 20 | } 21 | 22 | public void visitBegin(){ 23 | if( nv != null ){ 24 | nv.visitBegin(); 25 | } 26 | } 27 | 28 | /** 29 | * add attribute to the node 30 | * 31 | * @param ns 32 | * @param name 33 | * @param resourceId 34 | * @param type 35 | * {@link pxb.android.axml.AxmlVisitor#TYPE_STRING} or others 36 | * @param obj 37 | * a string for {@link pxb.android.axml.AxmlVisitor#TYPE_STRING} ,and Integer for others 38 | */ 39 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) { 40 | if (nv != null) { 41 | nv.visitContentAttr(ns, name, resourceId, type, obj); 42 | } 43 | } 44 | 45 | public void visitContentEnd(){ 46 | if(nv != null){ 47 | nv.visitContentEnd(); 48 | } 49 | } 50 | 51 | /** 52 | * create a child node 53 | * 54 | * @param ns 55 | * @param name 56 | * @return 57 | */ 58 | public NodeVisitor visitChild(String ns, String name) { 59 | if (nv != null) { 60 | return nv.visitChild(ns, name); 61 | } 62 | return null; 63 | } 64 | 65 | /** 66 | * end the visit 67 | */ 68 | public void visitEnd() { 69 | if (nv != null) { 70 | nv.visitEnd(); 71 | } 72 | } 73 | 74 | /** 75 | * line number in the .xml 76 | * 77 | * @param ln 78 | */ 79 | public void visitLineNumber(int ln) { 80 | if (nv != null) { 81 | nv.visitLineNumber(ln); 82 | } 83 | } 84 | 85 | /** 86 | * the node text 87 | * 88 | * @param value 89 | */ 90 | public void visitContentText(int lineNumber, String value) { 91 | if (nv != null) { 92 | nv.visitContentText(lineNumber, value); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/StringItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | class StringItem { 19 | public String data; 20 | public int dataOffset; 21 | public int index; 22 | 23 | public StringItem() { 24 | super(); 25 | } 26 | 27 | public StringItem(String data) { 28 | super(); 29 | this.data = data; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object obj) { 34 | StringItem b = (StringItem) obj; 35 | return b.data.equals(data); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return data.hashCode(); 41 | } 42 | 43 | public String toString() { 44 | return String.format("S%04d %s", index, data); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/pxb/android/axml/StringItems.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 Panxiaobo 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 | package pxb.android.axml; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | import java.util.TreeMap; 24 | 25 | import com.googlecode.dex2jar.reader.io.DataIn; 26 | import com.googlecode.dex2jar.reader.io.DataOut; 27 | 28 | @SuppressWarnings("serial") 29 | class StringItems extends ArrayList { 30 | 31 | byte[] stringData; 32 | 33 | public int getSize() { 34 | return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO 35 | } 36 | 37 | public void prepare() throws IOException { 38 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 39 | int i = 0; 40 | int offset = 0; 41 | baos.reset(); 42 | Map map = new HashMap(); 43 | for (StringItem item : this) { 44 | item.index = i++; 45 | String stringData = item.data; 46 | Integer of = map.get(stringData); 47 | if (of != null) { 48 | item.dataOffset = of; 49 | } else { 50 | item.dataOffset = offset; 51 | map.put(stringData, offset); 52 | int length = stringData.length(); 53 | byte[] data = stringData.getBytes("UTF-16LE"); 54 | baos.write(length); 55 | baos.write(length >> 8); 56 | baos.write(data); 57 | baos.write(0); 58 | baos.write(0); 59 | offset += 4 + data.length; 60 | } 61 | } 62 | // TODO 63 | stringData = baos.toByteArray(); 64 | } 65 | 66 | public void read(DataIn in, int size) throws IOException { 67 | int trunkOffset = in.getCurrentPosition() - 4; 68 | int stringCount = in.readIntx(); 69 | int styleOffsetCount = in.readIntx(); 70 | int flags = in.readIntx(); 71 | int stringDataOffset = in.readIntx(); 72 | int stylesOffset = in.readIntx(); 73 | for (int i = 0; i < stringCount; i++) { 74 | StringItem stringItem = new StringItem(); 75 | stringItem.index = i; 76 | stringItem.dataOffset = in.readIntx(); 77 | this.add(stringItem); 78 | } 79 | Map stringMap = new TreeMap(); 80 | if (styleOffsetCount != 0) { 81 | throw new RuntimeException(); 82 | // for (int i = 0; i < styleOffsetCount; i++) { 83 | // StringItem stringItem = new StringItem(); 84 | // stringItem.index = i; 85 | // stringItems.add(stringItem); 86 | // } 87 | } 88 | int endOfStringData = stylesOffset == 0 ? size : stylesOffset; 89 | int base = in.getCurrentPosition(); 90 | if (0 != (flags & AxmlReader.UTF8_FLAG)) { 91 | for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) { 92 | int length = (int) in.readLeb128(); 93 | ByteArrayOutputStream bos = new ByteArrayOutputStream(length + 10); 94 | for (int r = in.readByte(); r != 0; r = in.readByte()) { 95 | bos.write(r); 96 | } 97 | String value = new String(bos.toByteArray(), "UTF-8"); 98 | stringMap.put(p - base, value); 99 | } 100 | } else { 101 | for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) { 102 | int length = in.readShortx(); 103 | byte[] data = in.readBytes(length * 2); 104 | in.skip(2); 105 | String value = new String(data, "UTF-16LE"); 106 | stringMap.put(p - base, value); 107 | // System.out.println(String.format("%08x %s", p - base, value)); 108 | } 109 | } 110 | if (stylesOffset != 0) { 111 | throw new RuntimeException(); 112 | } 113 | for (StringItem item : this) { 114 | item.data = stringMap.get(item.dataOffset); 115 | // System.out.println(item); 116 | } 117 | } 118 | 119 | public void write(DataOut out) throws IOException { 120 | out.writeInt(this.size()); 121 | out.writeInt(0);// TODO 122 | out.writeInt(0); 123 | out.writeInt(7 * 4 + this.size() * 4); 124 | out.writeInt(0); 125 | for (StringItem item : this) { 126 | out.writeInt(item.dataOffset); 127 | } 128 | out.writeBytes(stringData); 129 | // TODO 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/pxb/android/axml/test/Test2.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml.test; 2 | 3 | import com.googlecode.dex2jar.reader.io.ArrayDataIn; 4 | import org.junit.Test; 5 | import pxb.android.axml.Axml; 6 | import pxb.android.axml.AxmlReader; 7 | import pxb.android.axml.AxmlWriter; 8 | import pxb.android.axml.DumpAdapter; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.InputStream; 13 | import java.io.StringWriter; 14 | 15 | public class Test2 { 16 | @Test 17 | public void test() throws Exception { 18 | for (File file : new File("src/test/resources/").listFiles()) { 19 | if (file.getName().endsWith(".axml")) { 20 | System.out.println("======= test0 " + file); 21 | InputStream is = new FileInputStream(file); 22 | byte[] xml = new byte[is.available()]; 23 | is.read(xml); 24 | is.close(); 25 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml)); 26 | AxmlWriter wr = new AxmlWriter(); 27 | 28 | //initial parsed 29 | System.out.println("==== A"); 30 | StringWriter writerA = new StringWriter(); 31 | Axml axml1 = new Axml(); 32 | rd.accept(axml1); 33 | axml1.accept(new DumpAdapter(writerA)); 34 | System.out.println(writerA); 35 | 36 | //write and re-parsed 37 | System.out.println("==== B"); 38 | axml1.accept(wr); 39 | StringWriter writerB = new StringWriter(); 40 | Axml axml2 = new Axml(); 41 | new AxmlReader(ArrayDataIn.le(wr.toByteArray())).accept(axml2); 42 | axml2.accept(new DumpAdapter(writerB)); 43 | System.out.println(writerB); 44 | 45 | // Assert.assertTrue(writerA.toString().compareTo(writerB.toString()) == 0); 46 | 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/pxb/android/axml/test/Test3.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml.test; 2 | 3 | import com.googlecode.dex2jar.reader.io.ArrayDataIn; 4 | import org.junit.Test; 5 | import pxb.android.axml.AxmlReader; 6 | import pxb.android.axml.AxmlVisitor; 7 | import pxb.android.axml.DumpAdapter; 8 | import pxb.android.axml.NodeVisitor; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.InputStream; 13 | 14 | public class Test3 { 15 | @Test 16 | public void test0() throws Exception { 17 | for (File file : new File("src/test/resources/").listFiles()) { 18 | if (file.getName().endsWith(".axml")) { 19 | System.out.println("======= test " + file); 20 | InputStream is = new FileInputStream(file); 21 | byte[] xml = new byte[is.available()]; 22 | is.read(xml); 23 | is.close(); 24 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml)); 25 | rd.accept(null); 26 | } 27 | } 28 | } 29 | 30 | @Test 31 | public void test1() throws Exception { 32 | for (File file : new File("src/test/resources/").listFiles()) { 33 | if (file.getName().endsWith(".axml")) { 34 | System.out.println("======= test " + file); 35 | InputStream is = new FileInputStream(file); 36 | byte[] xml = new byte[is.available()]; 37 | is.read(xml); 38 | is.close(); 39 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml)); 40 | rd.accept(new AxmlVisitor(new DumpAdapter()) { 41 | 42 | @Override 43 | public NodeVisitor visitFirst(String ns, String name) { 44 | return null; 45 | } 46 | }); 47 | } 48 | } 49 | } 50 | 51 | @Test 52 | public void test2() throws Exception { 53 | for (File file : new File("src/test/resources/").listFiles()) { 54 | if (file.getName().endsWith(".axml")) { 55 | System.out.println("======= test " + file); 56 | InputStream is = new FileInputStream(file); 57 | byte[] xml = new byte[is.available()]; 58 | is.read(xml); 59 | is.close(); 60 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml)); 61 | rd.accept(new AxmlVisitor(new DumpAdapter()) { 62 | 63 | @Override 64 | public NodeVisitor visitFirst(String ns, String name) { 65 | return new NodeVisitor(super.visitFirst(ns, name)) { 66 | 67 | @Override 68 | public NodeVisitor visitChild(String ns, String name) { 69 | return null; 70 | } 71 | }; 72 | } 73 | }); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/pxb/android/axml/test/Test4.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml.test; 2 | 3 | import org.junit.Test; 4 | import pxb.android.axml.AxmlReader; 5 | import pxb.android.axml.AxmlWriter; 6 | import pxb.android.axml.DumpAdapter; 7 | import pxb.android.axml.NodeVisitor; 8 | 9 | import java.io.IOException; 10 | 11 | public class Test4 { 12 | @Test 13 | public void test() throws IOException { 14 | AxmlWriter aw = new AxmlWriter(); 15 | NodeVisitor nv = aw.visitFirst("http://abc.com", "abc"); 16 | nv.visitEnd(); 17 | nv = aw.visitFirst("http://efg.com", "efg"); 18 | nv.visitEnd(); 19 | aw.visitEnd(); 20 | AxmlReader ar = new AxmlReader(aw.toByteArray()); 21 | ar.accept(new DumpAdapter()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/pxb/android/axml/test/Test5.java: -------------------------------------------------------------------------------- 1 | package pxb.android.axml.test; 2 | 3 | 4 | import com.googlecode.dex2jar.reader.io.ArrayDataIn; 5 | import org.junit.Test; 6 | import pxb.android.axml.*; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Created with IntelliJ IDEA. 14 | * User: wtchoi 15 | * Date: 10/19/12 16 | * Time: 10:09 PM 17 | * To change this template use File | Settings | File Templates. 18 | */ 19 | public class Test5 { 20 | @Test 21 | public void test() throws Exception{ 22 | System.out.println("TEST5"); 23 | for(File file: new File("src/test/resources/").listFiles()){ 24 | if(file.getName().compareTo("manifest1.axml") == 0){ 25 | InputStream is = new FileInputStream(file); 26 | byte[] xml = new byte[is.available()]; 27 | is.read(xml); 28 | is.close(); 29 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml)); 30 | AxmlWriter wr = new AxmlWriter(); 31 | AxmlDecoder av = new AxmlDecoder(new DumpAdapter(null, wr)); 32 | rd.accept(av); 33 | 34 | System.out.println(av.getMainActivityName()); 35 | } 36 | } 37 | } 38 | } 39 | 40 | class AxmlDecoder extends AxmlVisitor{ 41 | 42 | private static String androidNS = "http://schemas.android.com/apk/res/android"; 43 | 44 | public AxmlDecoder(AxmlVisitor visitor){ 45 | super(visitor); 46 | } 47 | 48 | public static class NodeDecoder extends NodeVisitor{ 49 | private AxmlDecoder root; 50 | private NodeDecoder parent; 51 | private String namespace; 52 | private String name; 53 | private Object aux; 54 | 55 | private static boolean permissionHandled = false; 56 | private static boolean screeDensityHandled = false; 57 | 58 | private boolean hasActionMainAttr = false; 59 | private boolean hasCategoryLauncherAttr = false; 60 | 61 | public NodeDecoder(String namespace, String name, NodeDecoder parent, AxmlDecoder root, NodeVisitor nv){ 62 | super(nv); 63 | this.namespace = namespace; 64 | this.name = name; 65 | this.parent = parent; 66 | this.root = root; 67 | } 68 | 69 | @Override 70 | public NodeVisitor visitChild(String namespace, String name){ 71 | NodeVisitor nv = super.visitChild(namespace, name); 72 | return new NodeDecoder(namespace, name, this, root, nv); 73 | } 74 | 75 | @Override 76 | public void visitContentAttr(String namespace, String name, int resourceId, int type, Object obj){ 77 | if(this.name.compareTo("supports-screens") == 0 78 | && namespace.compareTo(androidNS) == 0 79 | && name.compareTo("anyDensity") == 0){ 80 | super.visitContentAttr(namespace, name, resourceId, type, true); 81 | screeDensityHandled = true; 82 | } 83 | else{ 84 | super.visitContentAttr(namespace, name, resourceId, type, obj); 85 | if(this.name.compareTo("uses-permission") == 0 86 | && namespace.compareTo(androidNS) == 0 87 | && name.compareTo("name") == 0 88 | && ((String)obj).compareTo("android.permission.INTERNET") == 0) { 89 | permissionHandled = true; 90 | } 91 | } 92 | 93 | //check main activity 94 | if(this.parent != null && this.parent.name.compareTo("intent-filter") == 0){ 95 | if(this.name.compareTo("action") == 0 96 | && namespace.compareTo(androidNS) == 0 97 | && name.compareTo("name") == 0 98 | && ((String) obj).compareTo("android.intent.action.MAIN") == 0){ 99 | parent.parent.hasActionMainAttr = true; 100 | } 101 | if(this.name.compareTo("category") == 0 102 | && namespace.compareTo(androidNS) == 0 103 | && name.compareTo("name") == 0 104 | && ((String) obj).compareTo("android.intent.category.LAUNCHER") == 0){ 105 | parent.parent.hasCategoryLauncherAttr = true; 106 | } 107 | } 108 | 109 | if(this.name.compareTo("activity") == 0 110 | && namespace.compareTo(androidNS) == 0 111 | && name.compareTo("name") == 0){ 112 | aux = obj; 113 | } 114 | 115 | if(this.name.compareTo("manifest") == 0 116 | && name.compareTo("package") == 0){ 117 | root.setPackage((String)obj); 118 | } 119 | } 120 | 121 | @Override 122 | public void visitContentEnd(){ 123 | if(this.name.compareTo("supports-screens") == 0 && !screeDensityHandled){ 124 | super.visitContentAttr("android", "anyDensity", 0x0101026c, AxmlVisitor.TYPE_INT_BOOLEAN, true); 125 | screeDensityHandled = true; 126 | } 127 | if(this.name.compareTo("uses-permission")== 0 && !permissionHandled){ 128 | super.visitContentAttr("android", "name", 0x01010003, AxmlVisitor.TYPE_STRING, "android.permission.INTERNET"); 129 | permissionHandled = true; 130 | } 131 | super.visitContentEnd(); 132 | } 133 | 134 | 135 | @Override 136 | public void visitEnd(){ 137 | if(name.compareTo("manifest") == 0 ){ 138 | if(!screeDensityHandled){ 139 | screeDensityHandled = true; 140 | NodeVisitor nv = visitChild(null,"supports-screens"); 141 | if(nv != null){ 142 | nv.visitBegin(); 143 | nv.visitContentAttr("android","anyDensity", 0x0101026c, AxmlVisitor.TYPE_INT_BOOLEAN, true); 144 | nv.visitContentEnd(); 145 | nv.visitEnd(); 146 | } 147 | } 148 | if(!permissionHandled){ 149 | permissionHandled = true; 150 | NodeVisitor nv = visitChild(null, "uses-permission"); 151 | if(nv != null){ 152 | nv.visitBegin(); 153 | nv.visitContentAttr("android","name", 0x01010003, AxmlVisitor.TYPE_STRING, "android.permission.INTERNET"); 154 | nv.visitContentEnd(); 155 | nv.visitEnd(); 156 | } 157 | } 158 | } 159 | super.visitEnd(); 160 | 161 | //check 162 | if(this.name.compareTo("activity") == 0 && hasActionMainAttr && hasCategoryLauncherAttr){ 163 | root.setMainActivity((String)this.aux); 164 | } 165 | } 166 | } 167 | 168 | private String mainActivity; 169 | private String packageName; 170 | 171 | @Override 172 | public NodeVisitor visitFirst(String namespace, String name){ 173 | NodeVisitor nv = super.visitFirst(namespace, name); 174 | return new NodeDecoder(namespace, name, null, this, nv); 175 | } 176 | 177 | public void setMainActivity(String name){ 178 | mainActivity = name; 179 | } 180 | 181 | public void setPackage(String name){ 182 | packageName = name; 183 | } 184 | 185 | public String getMainActivityName(){ 186 | return packageName + mainActivity; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/test/resources/a.axml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/a.axml -------------------------------------------------------------------------------- /src/test/resources/b.axml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/b.axml -------------------------------------------------------------------------------- /src/test/resources/c.axml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/c.axml -------------------------------------------------------------------------------- /src/test/resources/d.axml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/d.axml -------------------------------------------------------------------------------- /src/test/resources/manifest1.axml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/manifest1.axml --------------------------------------------------------------------------------