pkgs = new ArscParser(data).parse();
69 |
70 | dump(pkgs);
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/pxb/android/arsc/ArscParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.io.IOException;
19 | import java.nio.ByteBuffer;
20 | import java.nio.ByteOrder;
21 | import java.util.AbstractMap;
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | import org.slf4j.Logger;
27 | import org.slf4j.LoggerFactory;
28 |
29 | import pxb.android.ResConst;
30 | import pxb.android.StringItems;
31 |
32 | /**
33 | *
34 | * Read the resources.arsc inside an Android apk.
35 | *
36 | * Usage:
37 | *
38 | *
39 | * byte[] oldArscFile= ... ; //
40 | * List<Pkg> pkgs = new ArscParser(oldArscFile).parse(); // read the file
41 | * modify(pkgs); // do what you want here
42 | * byte[] newArscFile = new ArscWriter(pkgs).toByteArray(); // build a new file
43 | *
44 | *
45 | * The format of arsc is described here (gingerbread)
46 | *
47 | * - frameworks/base/libs/utils/ResourceTypes.cpp
48 | * - frameworks/base/include/utils/ResourceTypes.h
49 | *
50 | * and the cmd line aapt d resources abc.apk
is also good for debug
51 | * (available in android sdk)
52 | *
53 | *
54 | * Todos:
55 | *
56 | * TODO add support to read styled strings
57 | *
58 | *
59 | *
60 | * Thanks to the the following projects
61 | *
62 | * - android4me https://code.google.com/p/android4me/
63 | * - Apktool https://code.google.com/p/android-apktool
64 | * - Android http://source.android.com/
65 | *
66 | *
67 | * @author bob
68 | *
69 | */
70 | public class ArscParser implements ResConst {
71 |
72 | private static final Logger logger = LoggerFactory.getLogger(ArscParser.class);
73 |
74 | /* pkg */class Chunk {
75 |
76 | public final int headSize;
77 | public final int location;
78 | public final int size;
79 | public final int type;
80 |
81 | public Chunk() {
82 | location = in.position();
83 | type = in.getShort() & 0xFFFF;
84 | headSize = in.getShort() & 0xFFFF;
85 | size = in.getInt();
86 | D("[%08x]type: %04x, headsize: %04x, size:%08x", location, type, headSize, size);
87 | }
88 | }
89 |
90 | private static void D(String fmt, Object... args) {
91 | if (logger.isTraceEnabled()) {
92 | logger.trace(String.format(fmt, args));
93 | }
94 | }
95 |
96 | /**
97 | * If set, this resource has been declared public, so libraries are allowed
98 | * to reference it.
99 | */
100 | static final int ENGRY_FLAG_PUBLIC = 0x0002;
101 |
102 | /**
103 | * If set, this is a complex entry, holding a set of name/value mappings. It
104 | * is followed by an array of ResTable_map structures.
105 | */
106 | final static short ENTRY_FLAG_COMPLEX = 0x0001;
107 | public static final int TYPE_STRING = 0x03;
108 |
109 | private int fileSize = -1;
110 | private ByteBuffer in;
111 | private String[] keyNamesX;
112 | private Pkg pkg;
113 | private List pkgs = new ArrayList();
114 | private String[] strings;
115 | private String[] typeNamesX;
116 |
117 | public ArscParser(byte[] b) {
118 | this.in = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
119 | }
120 |
121 | public List parse() throws IOException {
122 | if (fileSize < 0) {
123 | Chunk head = new Chunk();
124 | if (head.type != RES_TABLE_TYPE) {
125 | throw new RuntimeException();
126 | }
127 | fileSize = head.size;
128 | in.getInt();// packagecount
129 | }
130 | while (in.hasRemaining()) {
131 | Chunk chunk = new Chunk();
132 | switch (chunk.type) {
133 | case RES_STRING_POOL_TYPE:
134 | strings = StringItems.read(in);
135 | if (logger.isTraceEnabled()) {
136 | for (int i = 0; i < strings.length; i++) {
137 | D("STR [%08x] %s", i, strings[i]);
138 | }
139 | }
140 | break;
141 | case RES_TABLE_PACKAGE_TYPE:
142 | readPackage(in);
143 | }
144 | in.position(chunk.location + chunk.size);
145 | }
146 | return pkgs;
147 | }
148 |
149 | // private void readConfigFlags() {
150 | // int size = in.getInt();
151 | // if (size < 28) {
152 | // throw new RuntimeException();
153 | // }
154 | // short mcc = in.getShort();
155 | // short mnc = in.getShort();
156 | //
157 | // char[] language = new char[] { (char) in.get(), (char) in.get() };
158 | // char[] country = new char[] { (char) in.get(), (char) in.get() };
159 | //
160 | // byte orientation = in.get();
161 | // byte touchscreen = in.get();
162 | // short density = in.getShort();
163 | //
164 | // byte keyboard = in.get();
165 | // byte navigation = in.get();
166 | // byte inputFlags = in.get();
167 | // byte inputPad0 = in.get();
168 | //
169 | // short screenWidth = in.getShort();
170 | // short screenHeight = in.getShort();
171 | //
172 | // short sdkVersion = in.getShort();
173 | // short minorVersion = in.getShort();
174 | //
175 | // byte screenLayout = 0;
176 | // byte uiMode = 0;
177 | // short smallestScreenWidthDp = 0;
178 | // if (size >= 32) {
179 | // screenLayout = in.get();
180 | // uiMode = in.get();
181 | // smallestScreenWidthDp = in.getShort();
182 | // }
183 | //
184 | // short screenWidthDp = 0;
185 | // short screenHeightDp = 0;
186 | //
187 | // if (size >= 36) {
188 | // screenWidthDp = in.getShort();
189 | // screenHeightDp = in.getShort();
190 | // }
191 | //
192 | // short layoutDirection = 0;
193 | // if (size >= 38 && sdkVersion >= 17) {
194 | // layoutDirection = in.getShort();
195 | // }
196 | //
197 | // }
198 |
199 | private void readEntry(Config config, ResSpec spec) {
200 | D("[%08x]read ResTable_entry", in.position());
201 | int size = in.getShort();
202 | D("ResTable_entry %d", size);
203 |
204 | int flags = in.getShort(); // ENTRY_FLAG_PUBLIC
205 | int keyStr = in.getInt();
206 | spec.updateName(keyNamesX[keyStr]);
207 |
208 | ResEntry resEntry = new ResEntry(flags, spec);
209 |
210 | if (0 != (flags & ENTRY_FLAG_COMPLEX)) {
211 |
212 | int parent = in.getInt();
213 | int count = in.getInt();
214 | BagValue bag = new BagValue(parent);
215 | for (int i = 0; i < count; i++) {
216 | Map.Entry entry = new AbstractMap.SimpleEntry(in.getInt(), readValue());
217 | bag.map.add(entry);
218 | }
219 | resEntry.value = bag;
220 | } else {
221 | resEntry.value = readValue();
222 | }
223 | config.resources.put(spec.id, resEntry);
224 | }
225 |
226 | private void readPackage(ByteBuffer in) throws IOException {
227 | int pid = in.getInt() % 0xFF;
228 |
229 | String name;
230 | {
231 | int nextPisition = in.position() + 128 * 2;
232 | StringBuilder sb = new StringBuilder(32);
233 | for (int i = 0; i < 128; i++) {
234 | int s = in.getShort();
235 | if (s == 0) {
236 | break;
237 | } else {
238 | sb.append((char) s);
239 | }
240 | }
241 | name = sb.toString();
242 | in.position(nextPisition);
243 | }
244 |
245 | pkg = new Pkg(pid, name);
246 | pkgs.add(pkg);
247 |
248 | int typeStringOff = in.getInt();
249 | int typeNameCount = in.getInt();
250 | int keyStringOff = in.getInt();
251 | int specNameCount = in.getInt();
252 |
253 | {
254 | Chunk chunk = new Chunk();
255 | if (chunk.type != RES_STRING_POOL_TYPE) {
256 | throw new RuntimeException();
257 | }
258 | typeNamesX = StringItems.read(in);
259 | in.position(chunk.location + chunk.size);
260 | }
261 | {
262 | Chunk chunk = new Chunk();
263 | if (chunk.type != RES_STRING_POOL_TYPE) {
264 | throw new RuntimeException();
265 | }
266 | keyNamesX = StringItems.read(in);
267 | if (logger.isTraceEnabled()) {
268 | for (int i = 0; i < keyNamesX.length; i++) {
269 | D("STR [%08x] %s", i, keyNamesX[i]);
270 | }
271 | }
272 | in.position(chunk.location + chunk.size);
273 | }
274 |
275 | out: while (in.hasRemaining()) {
276 | Chunk chunk = new Chunk();
277 | switch (chunk.type) {
278 | case RES_TABLE_TYPE_SPEC_TYPE: {
279 | D("[%08x]read spec", in.position() - 8);
280 | int tid = in.get() & 0xFF;
281 | in.get(); // res0
282 | in.getShort();// res1
283 | int entryCount = in.getInt();
284 |
285 | Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
286 | for (int i = 0; i < entryCount; i++) {
287 | t.getSpec(i).flags = in.getInt();
288 | }
289 | }
290 | break;
291 | case RES_TABLE_TYPE_TYPE: {
292 | D("[%08x]read config", in.position() - 8);
293 | int tid = in.get() & 0xFF;
294 | in.get(); // res0
295 | in.getShort();// res1
296 | int entryCount = in.getInt();
297 | Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
298 | int entriesStart = in.getInt();
299 |
300 | D("[%08x]read config id", in.position());
301 |
302 | int p = in.position();
303 | int size = in.getInt();
304 | // readConfigFlags();
305 | byte[] data = new byte[size];
306 | in.position(p);
307 | in.get(data);
308 | Config config = new Config(data, entryCount);
309 |
310 | in.position(chunk.location + chunk.headSize);
311 |
312 | D("[%08x]read config entry offset", in.position());
313 |
314 | int[] entrys = new int[entryCount];
315 | for (int i = 0; i < entryCount; i++) {
316 | entrys[i] = in.getInt();
317 | }
318 | D("[%08x]read config entrys", in.position());
319 | for (int i = 0; i < entrys.length; i++) {
320 | if (entrys[i] != -1) {
321 | in.position(chunk.location + entriesStart + entrys[i]);
322 | ResSpec spec = t.getSpec(i);
323 | readEntry(config, spec);
324 | }
325 | }
326 |
327 | t.addConfig(config);
328 | }
329 | break;
330 | default:
331 | break out;
332 | }
333 | in.position(chunk.location + chunk.size);
334 | }
335 | }
336 |
337 | private Object readValue() {
338 | int size1 = in.getShort();// 8
339 | int zero = in.get();// 0
340 | int type = in.get() & 0xFF; // TypedValue.*
341 | int data = in.getInt();
342 | String raw = null;
343 | if (type == TYPE_STRING) {
344 | raw = strings[data];
345 | }
346 | return new Value(type, data, raw);
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/src/pxb/android/arsc/ArscWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.nio.ByteBuffer;
21 | import java.nio.ByteOrder;
22 | import java.util.ArrayList;
23 | import java.util.HashMap;
24 | import java.util.List;
25 | import java.util.Map;
26 | import java.util.TreeMap;
27 |
28 | import pxb.android.ResConst;
29 | import pxb.android.StringItem;
30 | import pxb.android.StringItems;
31 | import pxb.android.axml.Util;
32 |
33 | /**
34 | * Write pkgs to an arsc file
35 | *
36 | * @see ArscParser
37 | * @author bob
38 | *
39 | */
40 | public class ArscWriter implements ResConst {
41 | private static class PkgCtx {
42 | Map keyNames = new HashMap();
43 | StringItems keyNames0 = new StringItems();
44 | public int keyStringOff;
45 | int offset;
46 | Pkg pkg;
47 | int pkgSize;
48 | List typeNames = new ArrayList();
49 |
50 | StringItems typeNames0 = new StringItems();
51 | int typeStringOff;
52 |
53 | public void addKeyName(String name) {
54 | if (keyNames.containsKey(name)) {
55 | return;
56 | }
57 | StringItem stringItem = new StringItem(name);
58 | keyNames.put(name, stringItem);
59 | keyNames0.add(stringItem);
60 | }
61 |
62 | public void addTypeName(int id, String name) {
63 | while (typeNames.size() <= id) {
64 | typeNames.add(null);
65 | }
66 |
67 | StringItem item = typeNames.get(id);
68 | if (item == null) {
69 | typeNames.set(id, new StringItem(name));
70 | } else {
71 | throw new RuntimeException();
72 | }
73 | }
74 | }
75 |
76 | private static void D(String fmt, Object... args) {
77 |
78 | }
79 |
80 | private List ctxs = new ArrayList(5);
81 | private List pkgs;
82 | private Map strTable = new TreeMap();
83 | private StringItems strTable0 = new StringItems();
84 |
85 | public ArscWriter(List pkgs) {
86 | this.pkgs = pkgs;
87 | }
88 |
89 | public static void main(String... args) throws IOException {
90 | if (args.length < 2) {
91 | System.err.println("asrc-write-test in.arsc out.arsc");
92 | return;
93 | }
94 | byte[] data = Util.readFile(new File(args[0]));
95 | List pkgs = new ArscParser(data).parse();
96 | // ArscDumper.dump(pkgs);
97 | byte[] data2 = new ArscWriter(pkgs).toByteArray();
98 | // ArscDumper.dump(new ArscParser(data2).parse());
99 | Util.writeFile(data2, new File(args[1]));
100 | }
101 |
102 | private void addString(String str) {
103 | if (strTable.containsKey(str)) {
104 | return;
105 | }
106 | StringItem stringItem = new StringItem(str);
107 | strTable.put(str, stringItem);
108 | strTable0.add(stringItem);
109 | }
110 |
111 | private int count() {
112 |
113 | int size = 0;
114 |
115 | size += 8 + 4;// chunk, pkgcount
116 | {
117 | int stringSize = strTable0.getSize();
118 | if (stringSize % 4 != 0) {
119 | stringSize += 4 - stringSize % 4;
120 | }
121 | size += 8 + stringSize;// global strings
122 | }
123 | for (PkgCtx ctx : ctxs) {
124 | ctx.offset = size;
125 | int pkgSize = 0;
126 | pkgSize += 8 + 4 + 256;// chunk,pid+name
127 | pkgSize += 4 * 4;
128 |
129 | ctx.typeStringOff = pkgSize;
130 | {
131 | int stringSize = ctx.typeNames0.getSize();
132 | if (stringSize % 4 != 0) {
133 | stringSize += 4 - stringSize % 4;
134 | }
135 | pkgSize += 8 + stringSize;// type names
136 | }
137 |
138 | ctx.keyStringOff = pkgSize;
139 |
140 | {
141 | int stringSize = ctx.keyNames0.getSize();
142 | if (stringSize % 4 != 0) {
143 | stringSize += 4 - stringSize % 4;
144 | }
145 | pkgSize += 8 + stringSize;// key names
146 | }
147 |
148 | for (Type type : ctx.pkg.types.values()) {
149 | type.wPosition = size + pkgSize;
150 | pkgSize += 8 + 4 + 4 + 4 * type.specs.length; // trunk,id,entryCount,
151 | // configs
152 |
153 | for (Config config : type.configs) {
154 | config.wPosition = pkgSize + size;
155 | int configBasePostion = pkgSize;
156 | pkgSize += 8 + 4 + 4 + 4; // trunk,id,entryCount,entriesStart
157 | int size0 = config.id.length;
158 | if (size0 % 4 != 0) {
159 | size0 += 4 - size0 % 4;
160 | }
161 | pkgSize += size0;// config
162 |
163 | if (pkgSize - configBasePostion > 0x0038) {
164 | throw new RuntimeException("config id too big");
165 | } else {
166 | pkgSize = configBasePostion + 0x0038;
167 | }
168 |
169 | pkgSize += 4 * config.entryCount;// offset
170 | config.wEntryStart = pkgSize - configBasePostion;
171 | int entryBase = pkgSize;
172 | for (ResEntry e : config.resources.values()) {
173 | e.wOffset = pkgSize - entryBase;
174 | pkgSize += 8;// size,flag,keyString
175 | if (e.value instanceof BagValue) {
176 | BagValue big = (BagValue) e.value;
177 | pkgSize += 8 + big.map.size() * 12;
178 | } else {
179 | pkgSize += 8;
180 | }
181 | }
182 | config.wChunkSize = pkgSize - configBasePostion;
183 | }
184 | }
185 | ctx.pkgSize = pkgSize;
186 | size += pkgSize;
187 | }
188 |
189 | return size;
190 | }
191 |
192 | private List prepare() throws IOException {
193 | for (Pkg pkg : pkgs) {
194 | PkgCtx ctx = new PkgCtx();
195 | ctx.pkg = pkg;
196 | ctxs.add(ctx);
197 |
198 | for (Type type : pkg.types.values()) {
199 | ctx.addTypeName(type.id - 1, type.name);
200 | for (ResSpec spec : type.specs) {
201 | ctx.addKeyName(spec.name);
202 | }
203 | for (Config config : type.configs) {
204 | for (ResEntry e : config.resources.values()) {
205 | Object object = e.value;
206 | if (object instanceof BagValue) {
207 | travelBagValue((BagValue) object);
208 | } else {
209 | travelValue((Value) object);
210 | }
211 | }
212 | }
213 | }
214 | ctx.keyNames0.prepare();
215 | ctx.typeNames0.addAll(ctx.typeNames);
216 | ctx.typeNames0.prepare();
217 | }
218 | strTable0.prepare();
219 | return ctxs;
220 | }
221 |
222 | public byte[] toByteArray() throws IOException {
223 | prepare();
224 | int size = count();
225 | ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
226 | write(out, size);
227 | return out.array();
228 | }
229 |
230 | private void travelBagValue(BagValue bag) {
231 | for (Map.Entry e : bag.map) {
232 | travelValue(e.getValue());
233 | }
234 | }
235 |
236 | private void travelValue(Value v) {
237 | if (v.raw != null) {
238 | addString(v.raw);
239 | }
240 | }
241 |
242 | private void write(ByteBuffer out, int size) throws IOException {
243 | out.putInt(RES_TABLE_TYPE | (0x000c << 16));
244 | out.putInt(size);
245 | out.putInt(ctxs.size());
246 |
247 | {
248 | int stringSize = strTable0.getSize();
249 | int padding = 0;
250 | if (stringSize % 4 != 0) {
251 | padding = 4 - stringSize % 4;
252 | }
253 | out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
254 | out.putInt(stringSize + padding + 8);
255 | strTable0.write(out);
256 | out.put(new byte[padding]);
257 | }
258 |
259 | for (PkgCtx pctx : ctxs) {
260 | if (out.position() != pctx.offset) {
261 | throw new RuntimeException();
262 | }
263 | final int basePosition = out.position();
264 | out.putInt(RES_TABLE_PACKAGE_TYPE | (0x011c << 16));
265 | out.putInt(pctx.pkgSize);
266 | out.putInt(pctx.pkg.id);
267 | int p = out.position();
268 | out.put(pctx.pkg.name.getBytes("UTF-16LE"));
269 | out.position(p + 256);
270 |
271 | out.putInt(pctx.typeStringOff);
272 | out.putInt(pctx.typeNames0.size());
273 |
274 | out.putInt(pctx.keyStringOff);
275 | out.putInt(pctx.keyNames0.size());
276 |
277 | {
278 | if (out.position() - basePosition != pctx.typeStringOff) {
279 | throw new RuntimeException();
280 | }
281 | int stringSize = pctx.typeNames0.getSize();
282 | int padding = 0;
283 | if (stringSize % 4 != 0) {
284 | padding = 4 - stringSize % 4;
285 | }
286 | out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
287 | out.putInt(stringSize + padding + 8);
288 | pctx.typeNames0.write(out);
289 | out.put(new byte[padding]);
290 | }
291 |
292 | {
293 | if (out.position() - basePosition != pctx.keyStringOff) {
294 | throw new RuntimeException();
295 | }
296 | int stringSize = pctx.keyNames0.getSize();
297 | int padding = 0;
298 | if (stringSize % 4 != 0) {
299 | padding = 4 - stringSize % 4;
300 | }
301 | out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
302 | out.putInt(stringSize + padding + 8);
303 | pctx.keyNames0.write(out);
304 | out.put(new byte[padding]);
305 | }
306 |
307 | for (Type t : pctx.pkg.types.values()) {
308 | D("[%08x]write spec", out.position(), t.name);
309 | if (t.wPosition != out.position()) {
310 | throw new RuntimeException();
311 | }
312 | out.putInt(RES_TABLE_TYPE_SPEC_TYPE | (0x0010 << 16));
313 | out.putInt(4 * 4 + 4 * t.specs.length);// size
314 |
315 | out.putInt(t.id);
316 | out.putInt(t.specs.length);
317 | for (ResSpec spec : t.specs) {
318 | out.putInt(spec.flags);
319 | }
320 |
321 | for (Config config : t.configs) {
322 | D("[%08x]write config", out.position());
323 | int typeConfigPosition = out.position();
324 | if (config.wPosition != typeConfigPosition) {
325 | throw new RuntimeException();
326 | }
327 | out.putInt(RES_TABLE_TYPE_TYPE | (0x0038 << 16));
328 | out.putInt(config.wChunkSize);// size
329 |
330 | out.putInt(t.id);
331 | out.putInt(t.specs.length);
332 | out.putInt(config.wEntryStart);
333 |
334 | D("[%08x]write config ids", out.position());
335 | out.put(config.id);
336 |
337 | int size0 = config.id.length;
338 | int padding = 0;
339 | if (size0 % 4 != 0) {
340 | padding = 4 - size0 % 4;
341 | }
342 | out.put(new byte[padding]);
343 |
344 | out.position(typeConfigPosition + 0x0038);
345 |
346 | D("[%08x]write config entry offsets", out.position());
347 | for (int i = 0; i < config.entryCount; i++) {
348 | ResEntry entry = config.resources.get(i);
349 | if (entry == null) {
350 | out.putInt(-1);
351 | } else {
352 | out.putInt(entry.wOffset);
353 | }
354 | }
355 |
356 | if (out.position() - typeConfigPosition != config.wEntryStart) {
357 | throw new RuntimeException();
358 | }
359 | D("[%08x]write config entrys", out.position());
360 | for (ResEntry e : config.resources.values()) {
361 | D("[%08x]ResTable_entry", out.position());
362 | boolean isBag = e.value instanceof BagValue;
363 | out.putShort((short) (isBag ? 16 : 8));
364 | int flag = e.flag;
365 | if (isBag) { // add complex flag
366 | flag |= ArscParser.ENTRY_FLAG_COMPLEX;
367 | } else { // remove
368 | flag &= ~ArscParser.ENTRY_FLAG_COMPLEX;
369 | }
370 | out.putShort((short) flag);
371 | out.putInt(pctx.keyNames.get(e.spec.name).index);
372 | if (isBag) {
373 | BagValue bag = (BagValue) e.value;
374 | out.putInt(bag.parent);
375 | out.putInt(bag.map.size());
376 | for (Map.Entry entry : bag.map) {
377 | out.putInt(entry.getKey());
378 | writeValue(entry.getValue(), out);
379 | }
380 | } else {
381 | writeValue((Value) e.value, out);
382 | }
383 | }
384 | }
385 | }
386 | }
387 | }
388 |
389 | private void writeValue(Value value, ByteBuffer out) {
390 | out.putShort((short) 8);
391 | out.put((byte) 0);
392 | out.put((byte) value.type);
393 | if (value.type == ArscParser.TYPE_STRING) {
394 | out.putInt(strTable.get(value.raw).index);
395 | } else {
396 | out.putInt(value.data);
397 | }
398 | }
399 |
400 | }
401 |
--------------------------------------------------------------------------------
/src/pxb/android/arsc/BagValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 | import java.util.Map;
21 | import java.util.Map.Entry;
22 |
23 | public class BagValue {
24 | public List> map = new ArrayList>();
25 | public final int parent;
26 |
27 | public BagValue(int parent) {
28 | this.parent = parent;
29 | }
30 |
31 | @Override
32 | public boolean equals(Object obj) {
33 | if (this == obj)
34 | return true;
35 | if (obj == null)
36 | return false;
37 | if (!(obj instanceof BagValue))
38 | return false;
39 | BagValue other = (BagValue) obj;
40 | if (map == null) {
41 | if (other.map != null)
42 | return false;
43 | } else if (!map.equals(other.map))
44 | return false;
45 | if (parent != other.parent)
46 | return false;
47 | return true;
48 | }
49 |
50 | @Override
51 | public int hashCode() {
52 | final int prime = 31;
53 | int result = 1;
54 | result = prime * result + ((map == null) ? 0 : map.hashCode());
55 | result = prime * result + parent;
56 | return result;
57 | }
58 |
59 | public String toString() {
60 | StringBuilder sb = new StringBuilder();
61 | sb.append(String.format("{bag%08x", parent));
62 | for (Map.Entry e : map) {
63 | sb.append(",").append(String.format("0x%08x", e.getKey()));
64 | sb.append("=");
65 | sb.append(e.getValue());
66 | }
67 |
68 | return sb.append("}").toString();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pxb/android/arsc/Config.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.util.Map;
19 | import java.util.TreeMap;
20 |
21 | public class Config {
22 | public final int entryCount;
23 | public final byte[] id;
24 | public Map resources = new TreeMap();
25 | /* package */int wChunkSize;
26 | /* package */int wEntryStart;
27 | /* package */int wPosition;
28 |
29 | public Config(byte[] id, int entryCount) {
30 | super();
31 | this.id = id;
32 | this.entryCount = entryCount;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/pxb/android/arsc/Pkg.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.util.TreeMap;
19 |
20 | public class Pkg {
21 | public final int id;
22 | public String name;
23 | public TreeMap types = new TreeMap();
24 |
25 | public Pkg(int id, String name) {
26 | super();
27 | this.id = id;
28 | this.name = name;
29 | }
30 |
31 | public Type getType(int tid, String name, int entrySize) {
32 | Type type = types.get(tid);
33 | if (type != null) {
34 | if (name != null) {
35 | if (type.name == null) {
36 | type.name = name;
37 | } else if (!name.endsWith(type.name)) {
38 | throw new RuntimeException();
39 | }
40 | if (type.specs.length != entrySize) {
41 | throw new RuntimeException();
42 | }
43 | }
44 | } else {
45 | type = new Type();
46 | type.id = tid;
47 | type.name = name;
48 | type.specs = new ResSpec[entrySize];
49 | types.put(tid, type);
50 | }
51 | return type;
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/pxb/android/arsc/ResEntry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | public class ResEntry {
19 | public final int flag;
20 |
21 | public final ResSpec spec;
22 | /**
23 | * {@link BagValue} or {@link Value}
24 | */
25 | public Object value;
26 |
27 | /* package */int wOffset;
28 |
29 | public ResEntry(int flag, ResSpec spec) {
30 | super();
31 | this.flag = flag;
32 | this.spec = spec;
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/src/pxb/android/arsc/ResSpec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | public class ResSpec {
19 | public int flags;
20 | public final int id;
21 | public String name;
22 |
23 | public ResSpec(int id) {
24 | super();
25 | this.id = id;
26 | }
27 |
28 | public void updateName(String s) {
29 | String name = this.name;
30 | if (name == null) {
31 | this.name = s;
32 | } else if (!s.equals(name)) {
33 | throw new RuntimeException();
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/pxb/android/arsc/Type.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | public class Type {
22 | public List configs = new ArrayList();
23 | public int id;
24 | public String name;
25 | public ResSpec[] specs;
26 | /* package */int wPosition;
27 |
28 | public void addConfig(Config config) {
29 | if (config.entryCount != specs.length) {
30 | throw new RuntimeException();
31 | }
32 | configs.add(config);
33 | }
34 |
35 | public ResSpec getSpec(int resId) {
36 | ResSpec res = specs[resId];
37 | if (res == null) {
38 | res = new ResSpec(resId);
39 | specs[resId] = res;
40 | }
41 | return res;
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/pxb/android/arsc/Value.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.arsc;
17 |
18 | public class Value {
19 | public final int data;
20 | public String raw;
21 | public final int type;
22 |
23 | public Value(int type, int data, String raw) {
24 | super();
25 | this.type = type;
26 | this.data = data;
27 | this.raw = raw;
28 | }
29 |
30 | public String toString() {
31 | if (type == 0x03) {
32 | return raw;
33 | }
34 | return String.format("{t=0x%02x d=0x%08x}", type, data);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/Axml.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.attr(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.text(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.child(ns, name);
51 | acceptB(nodeVisitor2);
52 | nodeVisitor2.end();
53 | }
54 |
55 | public void acceptB(NodeVisitor nodeVisitor) {
56 | if (text != null) {
57 | text.accept(nodeVisitor);
58 | }
59 | for (Attr a : attrs) {
60 | a.accept(nodeVisitor);
61 | }
62 | if (ln != null) {
63 | nodeVisitor.line(ln);
64 | }
65 | for (Node c : children) {
66 | c.accept(nodeVisitor);
67 | }
68 | }
69 |
70 | @Override
71 | public void attr(String ns, String name, int resourceId, int type, Object obj) {
72 | Attr attr = new Attr();
73 | attr.name = name;
74 | attr.ns = ns;
75 | attr.resourceId = resourceId;
76 | attr.type = type;
77 | attr.value = obj;
78 | attrs.add(attr);
79 | }
80 |
81 | @Override
82 | public NodeVisitor child(String ns, String name) {
83 | Node node = new Node();
84 | node.name = name;
85 | node.ns = ns;
86 | children.add(node);
87 | return node;
88 | }
89 |
90 | @Override
91 | public void line(int ln) {
92 | this.ln = ln;
93 | }
94 |
95 | @Override
96 | public void text(int lineNumber, String value) {
97 | Text text = new Text();
98 | text.ln = lineNumber;
99 | text.text = value;
100 | this.text = text;
101 | }
102 | }
103 |
104 | public static class Ns {
105 | public int ln;
106 | public String prefix, uri;
107 |
108 | public void accept(AxmlVisitor visitor) {
109 | visitor.ns(prefix, uri, ln);
110 | }
111 | }
112 |
113 | public List firsts = new ArrayList();
114 | public List nses = new ArrayList();
115 |
116 | public void accept(final AxmlVisitor visitor) {
117 | for (Ns ns : nses) {
118 | ns.accept(visitor);
119 | }
120 | for (Node first : firsts) {
121 | first.accept(visitor);
122 | }
123 | }
124 |
125 | @Override
126 | public NodeVisitor child(String ns, String name) {
127 | Node node = new Node();
128 | node.name = name;
129 | node.ns = ns;
130 | firsts.add(node);
131 | return node;
132 | }
133 |
134 | @Override
135 | public void ns(String prefix, String uri, int ln) {
136 | Ns ns = new Ns();
137 | ns.prefix = prefix;
138 | ns.uri = uri;
139 | ns.ln = ln;
140 | nses.add(ns);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/AxmlParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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 static pxb.android.axml.NodeVisitor.TYPE_INT_BOOLEAN;
19 | import static pxb.android.axml.NodeVisitor.TYPE_STRING;
20 |
21 | import java.io.IOException;
22 | import java.nio.ByteBuffer;
23 | import java.nio.ByteOrder;
24 | import java.nio.IntBuffer;
25 |
26 | import pxb.android.ResConst;
27 | import pxb.android.StringItems;
28 |
29 | /**
30 | * a class to read android axml
31 | *
32 | * @author Panxiaobo
33 | */
34 | public class AxmlParser implements ResConst {
35 |
36 | public static final int END_FILE = 7;
37 | public static final int END_NS = 5;
38 | public static final int END_TAG = 3;
39 | public static final int START_FILE = 1;
40 | public static final int START_NS = 4;
41 | public static final int START_TAG = 2;
42 | public static final int TEXT = 6;
43 | // private int attrName[];
44 | // private int attrNs[];
45 | // private int attrResId[];
46 | // private int attrType[];
47 | // private Object attrValue[];
48 |
49 | private int attributeCount;
50 |
51 | private IntBuffer attrs;
52 |
53 | private int classAttribute;
54 | private int fileSize = -1;
55 | private int idAttribute;
56 | private ByteBuffer in;
57 | private int lineNumber;
58 | private int nameIdx;
59 | private int nsIdx;
60 |
61 | private int prefixIdx;
62 |
63 | private int[] resourceIds;
64 |
65 | private String[] strings;
66 |
67 | private int styleAttribute;
68 |
69 | private int textIdx;
70 |
71 | public AxmlParser(byte[] data) {
72 | this(ByteBuffer.wrap(data));
73 | }
74 |
75 | public AxmlParser(ByteBuffer in) {
76 | super();
77 | this.in = in.order(ByteOrder.LITTLE_ENDIAN);
78 | }
79 |
80 | public int getAttrCount() {
81 | return attributeCount;
82 | }
83 |
84 | public int getAttributeCount() {
85 | return attributeCount;
86 | }
87 |
88 | public String getAttrName(int i) {
89 | int idx = attrs.get(i * 5 + 1);
90 | if (idx >= 0 && idx < strings.length) {
91 | return strings[idx];
92 | }
93 | return null;
94 | }
95 |
96 | public String getAttrNs(int i) {
97 | int idx = attrs.get(i * 5 + 0);
98 | if (idx >= 0 && idx < strings.length) {
99 | return strings[idx];
100 | }
101 | return null;
102 | }
103 |
104 | String getAttrRawString(int i) {
105 | int idx = attrs.get(i * 5 + 2);
106 | if (idx >= 0 && idx < strings.length) {
107 | return strings[idx];
108 | }
109 | return null;
110 | }
111 |
112 | public int getAttrResId(int i) {
113 | if (resourceIds != null) {
114 | int idx = attrs.get(i * 5 + 1);
115 | if (idx >= 0 && idx < resourceIds.length) {
116 | return resourceIds[idx];
117 | }
118 | }
119 | return -1;
120 | }
121 |
122 | public int getAttrType(int i) {
123 | return attrs.get(i * 5 + 3) >> 24;
124 | }
125 |
126 | public Object getAttrValue(int i) {
127 | int v = attrs.get(i * 5 + 4);
128 |
129 | if (i == idAttribute) {
130 | return ValueWrapper.wrapId(v, getAttrRawString(i));
131 | } else if (i == styleAttribute) {
132 | return ValueWrapper.wrapStyle(v, getAttrRawString(i));
133 | } else if (i == classAttribute) {
134 | return ValueWrapper.wrapClass(v, getAttrRawString(i));
135 | }
136 |
137 | switch (getAttrType(i)) {
138 | case TYPE_STRING:
139 | return strings[v];
140 | case TYPE_INT_BOOLEAN:
141 | return v != 0;
142 | default:
143 | return v;
144 | }
145 | }
146 |
147 | public int getLineNumber() {
148 | return lineNumber;
149 | }
150 |
151 | public String getName() {
152 | if (nameIdx >=0 && nameIdx < strings.length) {
153 | return strings[nameIdx];
154 | }
155 | return null;
156 | }
157 |
158 | public String getNamespacePrefix() {
159 | if (prefixIdx >= 0 && prefixIdx < strings.length) {
160 | return strings[prefixIdx];
161 | }
162 | return null;
163 | }
164 |
165 | public String getNamespaceUri() {
166 | if (nsIdx >= 0 && nsIdx < strings.length) {
167 | return strings[nsIdx];
168 | }
169 | return null;
170 | }
171 |
172 | public String getText() {
173 | if (textIdx >= 0 && textIdx < strings.length) {
174 | return strings[textIdx];
175 | }
176 | return null;
177 | }
178 |
179 | public int next() throws IOException {
180 | if (fileSize < 0) {
181 | int type = in.getInt() & 0xFFFF;
182 | if (type != RES_XML_TYPE) {
183 | throw new RuntimeException(String.format("Resource type was 0x%04X instead of XML_TYPE (0x0003)", type));
184 | }
185 | fileSize = in.getInt();
186 | return START_FILE;
187 | }
188 | int event = -1;
189 | for (int p = in.position(); p < fileSize; p = in.position()) {
190 | int type = in.getInt() & 0xFFFF;
191 | int size = in.getInt();
192 | switch (type) {
193 | case RES_XML_START_ELEMENT_TYPE: {
194 | {
195 | lineNumber = in.getInt();
196 | in.getInt();/* skip, 0xFFFFFFFF */
197 | nsIdx = in.getInt();
198 | nameIdx = in.getInt();
199 | int flag = in.getInt();// 0x00140014 ?
200 | if (flag != 0x00140014) {
201 | throw new RuntimeException();
202 | }
203 | }
204 |
205 | attributeCount = in.getShort() & 0xFFFF;
206 | idAttribute = (in.getShort() & 0xFFFF) - 1;
207 | classAttribute = (in.getShort() & 0xFFFF) - 1;
208 | styleAttribute = (in.getShort() & 0xFFFF) - 1;
209 |
210 | attrs = in.asIntBuffer();
211 |
212 | // attrResId = new int[attributeCount];
213 | // attrName = new int[attributeCount];
214 | // attrNs = new int[attributeCount];
215 | // attrType = new int[attributeCount];
216 | // attrValue = new Object[attributeCount];
217 | // for (int i = 0; i < attributeCount; i++) {
218 | // int attrNsIdx = in.getInt();
219 | // int attrNameIdx = in.getInt();
220 | // int raw = in.getInt();
221 | // int aValueType = in.getInt() >>> 24;
222 | // int aValue = in.getInt();
223 | // Object value = null;
224 | // switch (aValueType) {
225 | // case TYPE_STRING:
226 | // value = strings[aValue];
227 | // break;
228 | // case TYPE_INT_BOOLEAN:
229 | // value = aValue != 0;
230 | // break;
231 | // default:
232 | // value = aValue;
233 | // }
234 | // int resourceId = attrNameIdx < this.resourceIds.length ?
235 | // resourceIds[attrNameIdx] : -1;
236 | // attrNs[i] = attrNsIdx;
237 | // attrName[i] = attrNameIdx;
238 | // attrType[i] = aValueType;
239 | // attrResId[i] = resourceId;
240 | // attrValue[i] = value;
241 | // }
242 | event = START_TAG;
243 | }
244 | break;
245 | case RES_XML_END_ELEMENT_TYPE: {
246 | in.position(p + size);
247 | event = END_TAG;
248 | }
249 | break;
250 | case RES_XML_START_NAMESPACE_TYPE:
251 | lineNumber = in.getInt();
252 | in.getInt();/* 0xFFFFFFFF */
253 | prefixIdx = in.getInt();
254 | nsIdx = in.getInt();
255 | event = START_NS;
256 | break;
257 | case RES_XML_END_NAMESPACE_TYPE:
258 | in.position(p + size);
259 | event = END_NS;
260 | break;
261 | case RES_STRING_POOL_TYPE:
262 | strings = StringItems.read(in);
263 | in.position(p + size);
264 | continue;
265 | case RES_XML_RESOURCE_MAP_TYPE:
266 | int count = size / 4 - 2;
267 | resourceIds = new int[count];
268 | for (int i = 0; i < count; i++) {
269 | resourceIds[i] = in.getInt();
270 | }
271 | in.position(p + size);
272 | continue;
273 | case RES_XML_CDATA_TYPE:
274 | lineNumber = in.getInt();
275 | in.getInt();/* 0xFFFFFFFF */
276 | textIdx = in.getInt();
277 |
278 | in.getInt();/* 00000008 00000000 */
279 | in.getInt();
280 |
281 | event = TEXT;
282 | break;
283 | default:
284 | throw new RuntimeException("Unsupported type: " + type);
285 | }
286 | in.position(p + size);
287 | return event;
288 | }
289 | return END_FILE;
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/AxmlReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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 static pxb.android.axml.AxmlParser.END_FILE;
19 | import static pxb.android.axml.AxmlParser.END_NS;
20 | import static pxb.android.axml.AxmlParser.END_TAG;
21 | import static pxb.android.axml.AxmlParser.START_FILE;
22 | import static pxb.android.axml.AxmlParser.START_NS;
23 | import static pxb.android.axml.AxmlParser.START_TAG;
24 | import static pxb.android.axml.AxmlParser.TEXT;
25 |
26 | import java.io.IOException;
27 | import java.util.Stack;
28 |
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | /**
33 | * a class to read android axml
34 | *
35 | * @author Panxiaobo
36 | */
37 | public class AxmlReader {
38 |
39 | private static final Logger logger = LoggerFactory.getLogger(AxmlReader.class);
40 |
41 | public static final NodeVisitor EMPTY_VISITOR = new NodeVisitor() {
42 |
43 | @Override
44 | public NodeVisitor child(String ns, String name) {
45 | return this;
46 | }
47 |
48 | };
49 | final AxmlParser parser;
50 |
51 | public AxmlReader(byte[] data) {
52 | super();
53 | this.parser = new AxmlParser(data);
54 | }
55 |
56 | public void accept(final AxmlVisitor av) throws IOException {
57 | Stack nvs = new Stack();
58 | NodeVisitor tos = av;
59 | while (true) {
60 | int type = parser.next();
61 | switch (type) {
62 | case START_FILE:
63 | break;
64 | case START_TAG:
65 | nvs.push(tos);
66 | tos = tos.child(parser.getNamespaceUri(), parser.getName());
67 | if (tos != null) {
68 | if (tos != EMPTY_VISITOR) {
69 | tos.line(parser.getLineNumber());
70 | for (int i = 0; i < parser.getAttrCount(); i++) {
71 | tos.attr(parser.getAttrNs(i), parser.getAttrName(i), parser.getAttrResId(i),
72 | parser.getAttrType(i), parser.getAttrValue(i));
73 | }
74 | }
75 | } else {
76 | tos = EMPTY_VISITOR;
77 | }
78 | break;
79 | case END_TAG:
80 | tos.end();
81 | tos = nvs.pop();
82 | break;
83 | case START_NS:
84 | av.ns(parser.getNamespacePrefix(), parser.getNamespaceUri(), parser.getLineNumber());
85 | break;
86 | case END_NS:
87 | break;
88 | case TEXT:
89 | tos.text(parser.getLineNumber(), parser.getText());
90 | break;
91 | case END_FILE:
92 | return;
93 | default:
94 | logger.warn("Unsupported tag: {}", type);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/AxmlVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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 | * visitor to visit an axml
20 | *
21 | * @author Panxiaobo
22 | */
23 | public class AxmlVisitor extends NodeVisitor {
24 |
25 | public AxmlVisitor() {
26 | super();
27 |
28 | }
29 |
30 | public AxmlVisitor(NodeVisitor av) {
31 | super(av);
32 | }
33 |
34 | /**
35 | * create a ns
36 | *
37 | * @param prefix
38 | * @param uri
39 | * @param ln
40 | */
41 | public void ns(String prefix, String uri, int ln) {
42 | if (nv != null && nv instanceof AxmlVisitor) {
43 | ((AxmlVisitor) nv).ns(prefix, uri, ln);
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/AxmlWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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 static pxb.android.ResConst.RES_STRING_POOL_TYPE;
19 | import static pxb.android.ResConst.RES_XML_CDATA_TYPE;
20 | import static pxb.android.ResConst.RES_XML_END_ELEMENT_TYPE;
21 | import static pxb.android.ResConst.RES_XML_END_NAMESPACE_TYPE;
22 | import static pxb.android.ResConst.RES_XML_RESOURCE_MAP_TYPE;
23 | import static pxb.android.ResConst.RES_XML_START_ELEMENT_TYPE;
24 | import static pxb.android.ResConst.RES_XML_START_NAMESPACE_TYPE;
25 | import static pxb.android.ResConst.RES_XML_TYPE;
26 |
27 | import java.io.IOException;
28 | import java.nio.ByteBuffer;
29 | import java.nio.ByteOrder;
30 | import java.util.ArrayList;
31 | import java.util.Comparator;
32 | import java.util.HashMap;
33 | import java.util.List;
34 | import java.util.Map;
35 | import java.util.Set;
36 | import java.util.Stack;
37 | import java.util.TreeSet;
38 |
39 | import pxb.android.Item;
40 | import pxb.android.StringItem;
41 | import pxb.android.StringItems;
42 |
43 | /**
44 | * a class to write android axml
45 | *
46 | * @author Panxiaobo
47 | */
48 | public class AxmlWriter extends AxmlVisitor {
49 | static final Comparator ATTR_CMP = new Comparator() {
50 |
51 | @Override
52 | public int compare(Attr a, Attr b) {
53 | int x = a.resourceId - b.resourceId;
54 | if (x == 0) {
55 | x = a.name.data.compareTo(b.name.data);
56 | if (x == 0) {
57 | boolean aNsIsnull = a.ns == null;
58 | boolean bNsIsnull = b.ns == null;
59 | if (aNsIsnull) {
60 | if (bNsIsnull) {
61 | x = 0;
62 | } else {
63 | x = -1;
64 | }
65 | } else {
66 | if (bNsIsnull) {
67 | x = 1;
68 | } else {
69 | x = a.ns.data.compareTo(b.ns.data);
70 | }
71 | }
72 |
73 | }
74 | }
75 | return x;
76 | }
77 | };
78 |
79 | static class Attr {
80 |
81 | public int index;
82 | public StringItem name;
83 | public StringItem ns;
84 | public int resourceId;
85 | public int type;
86 | public Object value;
87 | public StringItem raw;
88 |
89 | public Attr(StringItem ns, StringItem name, int resourceId) {
90 | super();
91 | this.ns = ns;
92 | this.name = name;
93 | this.resourceId = resourceId;
94 | }
95 |
96 | public void prepare(AxmlWriter axmlWriter) {
97 | ns = axmlWriter.updateNs(ns);
98 | if (this.name != null) {
99 | if (resourceId != -1) {
100 | this.name = axmlWriter.updateWithResourceId(this.name, this.resourceId);
101 | } else {
102 | this.name = axmlWriter.update(this.name);
103 | }
104 | }
105 | if (value instanceof StringItem) {
106 | value = axmlWriter.update((StringItem) value);
107 | }
108 | if (raw != null) {
109 | raw = axmlWriter.update(raw);
110 | }
111 | }
112 |
113 | }
114 |
115 | static class NodeImpl extends NodeVisitor {
116 | private Set attrs = new TreeSet(ATTR_CMP);
117 | private List children = new ArrayList();
118 | private int line;
119 | private StringItem name;
120 | private StringItem ns;
121 | private StringItem text;
122 | private int textLineNumber;
123 | Attr id;
124 | Attr style;
125 | Attr clz;
126 |
127 | public NodeImpl(String ns, String name) {
128 | super(null);
129 | this.ns = ns == null ? null : new StringItem(ns);
130 | this.name = name == null ? null : new StringItem(name);
131 | }
132 |
133 | @Override
134 | public void attr(String ns, String name, int resourceId, int type, Object value) {
135 | if (name == null) {
136 | throw new RuntimeException("name can't be null");
137 | }
138 | Attr a = new Attr(ns == null ? null : new StringItem(ns), new StringItem(name), resourceId);
139 | a.type = type;
140 |
141 | if (value instanceof ValueWrapper) {
142 | ValueWrapper valueWrapper = (ValueWrapper) value;
143 | if (valueWrapper.raw != null) {
144 | a.raw = new StringItem(valueWrapper.raw);
145 | }
146 | a.value = valueWrapper.ref;
147 | switch (valueWrapper.type) {
148 | case ValueWrapper.CLASS:
149 | clz = a;
150 | break;
151 | case ValueWrapper.ID:
152 | id = a;
153 | break;
154 | case ValueWrapper.STYLE:
155 | style = a;
156 | break;
157 | }
158 | } else if (type == TYPE_STRING) {
159 | StringItem raw = new StringItem((String) value);
160 | a.raw = raw;
161 | a.value = raw;
162 |
163 | } else {
164 | a.raw = null;
165 | a.value = value;
166 | }
167 |
168 | attrs.add(a);
169 | }
170 |
171 | @Override
172 | public NodeVisitor child(String ns, String name) {
173 | NodeImpl child = new NodeImpl(ns, name);
174 | this.children.add(child);
175 | return child;
176 | }
177 |
178 | @Override
179 | public void end() {
180 | }
181 |
182 | @Override
183 | public void line(int ln) {
184 | this.line = ln;
185 | }
186 |
187 | public int prepare(AxmlWriter axmlWriter) {
188 | ns = axmlWriter.updateNs(ns);
189 | name = axmlWriter.update(name);
190 |
191 | int attrIndex = 0;
192 | for (Attr attr : attrs) {
193 | attr.index = attrIndex++;
194 | attr.prepare(axmlWriter);
195 | }
196 |
197 | text = axmlWriter.update(text);
198 | int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for
199 | // start tag
200 | for (NodeImpl child : children) {
201 | size += child.prepare(axmlWriter);
202 | }
203 | if (text != null) {
204 | size += 28;
205 | }
206 | return size;
207 | }
208 |
209 | @Override
210 | public void text(int ln, String value) {
211 | this.text = new StringItem(value);
212 | this.textLineNumber = ln;
213 | }
214 |
215 | void write(ByteBuffer out) throws IOException {
216 | // start tag
217 | out.putInt(RES_XML_START_ELEMENT_TYPE | (0x0010 << 16));
218 | out.putInt(36 + attrs.size() * 20);
219 | out.putInt(line);
220 | out.putInt(0xFFFFFFFF);
221 | out.putInt(ns != null ? this.ns.index : -1);
222 | out.putInt(name.index);
223 | out.putInt(0x00140014);// TODO
224 | out.putShort((short) this.attrs.size());
225 | out.putShort((short) (id == null ? 0 : id.index + 1));
226 | out.putShort((short) (clz == null ? 0 : clz.index + 1));
227 | out.putShort((short) (style == null ? 0 : style.index + 1));
228 | for (Attr attr : attrs) {
229 | out.putInt(attr.ns == null ? -1 : attr.ns.index);
230 | out.putInt(attr.name.index);
231 | out.putInt(attr.raw != null ? attr.raw.index : -1);
232 | out.putInt((attr.type << 24) | 0x000008);
233 | Object v = attr.value;
234 | if (v instanceof Item) {
235 | ((Item) attr.value).writeout(out);
236 | } else if (v instanceof Boolean) {
237 | out.putInt(Boolean.TRUE.equals(v) ? -1 : 0);
238 | } else if (v instanceof Float) {
239 | out.putInt(Float.floatToIntBits((float) v));
240 | } else {
241 | out.putInt((Integer) attr.value);
242 | }
243 | }
244 |
245 | if (this.text != null) {
246 | out.putInt(RES_XML_CDATA_TYPE | (0x0010 << 16));
247 | out.putInt(28);
248 | out.putInt(textLineNumber);
249 | out.putInt(0xFFFFFFFF);
250 | out.putInt(text.index);
251 | out.putInt(0x00000008);
252 | out.putInt(0x00000000);
253 | }
254 |
255 | // children
256 | for (NodeImpl child : children) {
257 | child.write(out);
258 | }
259 |
260 | // end tag
261 | out.putInt(RES_XML_END_ELEMENT_TYPE | (0x0010 << 16));
262 | out.putInt(24);
263 | out.putInt(-1);
264 | out.putInt(0xFFFFFFFF);
265 | out.putInt(ns != null ? this.ns.index : -1);
266 | out.putInt(name.index);
267 | }
268 | }
269 |
270 | static class Ns {
271 | int ln;
272 | StringItem prefix;
273 | StringItem uri;
274 |
275 | public Ns(StringItem prefix, StringItem uri, int ln) {
276 | super();
277 | this.prefix = prefix;
278 | this.uri = uri;
279 | this.ln = ln;
280 | }
281 | }
282 |
283 | private List firsts = new ArrayList(3);
284 |
285 | private Map nses = new HashMap();
286 |
287 | private List otherString = new ArrayList();
288 |
289 | private Map resourceId2Str = new HashMap();
290 |
291 | private List resourceIds = new ArrayList();
292 |
293 | private List resourceString = new ArrayList();
294 |
295 | private StringItems stringItems = new StringItems();
296 |
297 | // TODO add style support
298 | // private List styleItems = new ArrayList();
299 |
300 | @Override
301 | public NodeVisitor child(String ns, String name) {
302 | NodeImpl first = new NodeImpl(ns, name);
303 | this.firsts.add(first);
304 | return first;
305 | }
306 |
307 | @Override
308 | public void end() {
309 | }
310 |
311 | @Override
312 | public void ns(String prefix, String uri, int ln) {
313 | nses.put(uri, new Ns(prefix == null ? null : new StringItem(prefix), new StringItem(uri), ln));
314 | }
315 |
316 | private int prepare() throws IOException {
317 | int size = 0;
318 |
319 | for (NodeImpl first : firsts) {
320 | size += first.prepare(this);
321 | }
322 | {
323 | int a = 0;
324 | for (Map.Entry e : nses.entrySet()) {
325 | Ns ns = e.getValue();
326 | if (ns == null) {
327 | ns = new Ns(null, new StringItem(e.getKey()), 0);
328 | e.setValue(ns);
329 | }
330 | if (ns.prefix == null) {
331 | ns.prefix = new StringItem(String.format("axml_auto_%02d", a++));
332 | }
333 | ns.prefix = update(ns.prefix);
334 | ns.uri = update(ns.uri);
335 | }
336 | }
337 |
338 | size += nses.size() * 24 * 2;
339 |
340 | this.stringItems.addAll(resourceString);
341 | resourceString = null;
342 | this.stringItems.addAll(otherString);
343 | otherString = null;
344 | this.stringItems.prepare();
345 | int stringSize = this.stringItems.getSize();
346 | if (stringSize % 4 != 0) {
347 | stringSize += 4 - stringSize % 4;
348 | }
349 | size += 8 + stringSize;
350 | size += 8 + resourceIds.size() * 4;
351 | return size;
352 | }
353 |
354 | public byte[] toByteArray() throws IOException {
355 |
356 | int size = 8 + prepare();
357 | ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
358 |
359 | out.putInt(RES_XML_TYPE | (0x0008 << 16));
360 | out.putInt(size);
361 |
362 | int stringSize = this.stringItems.getSize();
363 | int padding = 0;
364 | if (stringSize % 4 != 0) {
365 | padding = 4 - stringSize % 4;
366 | }
367 | out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
368 | out.putInt(stringSize + padding + 8);
369 | this.stringItems.write(out);
370 | out.put(new byte[padding]);
371 |
372 | out.putInt(RES_XML_RESOURCE_MAP_TYPE | (0x0008 << 16));
373 | out.putInt(8 + this.resourceIds.size() * 4);
374 | for (Integer i : resourceIds) {
375 | out.putInt(i);
376 | }
377 |
378 | Stack stack = new Stack();
379 | for (Map.Entry e : this.nses.entrySet()) {
380 | Ns ns = e.getValue();
381 | stack.push(ns);
382 | out.putInt(RES_XML_START_NAMESPACE_TYPE | (0x0010 << 16));
383 | out.putInt(24);
384 | out.putInt(-1);
385 | out.putInt(0xFFFFFFFF);
386 | out.putInt(ns.prefix.index);
387 | out.putInt(ns.uri.index);
388 | }
389 |
390 | for (NodeImpl first : firsts) {
391 | first.write(out);
392 | }
393 |
394 | while (stack.size() > 0) {
395 | Ns ns = stack.pop();
396 | out.putInt(RES_XML_END_NAMESPACE_TYPE | (0x0010 << 16));
397 | out.putInt(24);
398 | out.putInt(ns.ln);
399 | out.putInt(0xFFFFFFFF);
400 | out.putInt(ns.prefix.index);
401 | out.putInt(ns.uri.index);
402 | }
403 | return out.array();
404 | }
405 |
406 | StringItem update(StringItem item) {
407 | if (item == null)
408 | return null;
409 | int i = this.otherString.indexOf(item);
410 | if (i < 0) {
411 | StringItem copy = new StringItem(item.data);
412 | this.otherString.add(copy);
413 | return copy;
414 | } else {
415 | return this.otherString.get(i);
416 | }
417 | }
418 |
419 | StringItem updateNs(StringItem item) {
420 | if (item == null) {
421 | return null;
422 | }
423 | String ns = item.data;
424 | if (!this.nses.containsKey(ns)) {
425 | this.nses.put(ns, null);
426 | }
427 | return update(item);
428 | }
429 |
430 | StringItem updateWithResourceId(StringItem name, int resourceId) {
431 | String key = name.data + resourceId;
432 | StringItem item = this.resourceId2Str.get(key);
433 | if (item != null) {
434 | return item;
435 | } else {
436 | StringItem copy = new StringItem(name.data);
437 | resourceIds.add(resourceId);
438 | resourceString.add(copy);
439 | resourceId2Str.put(key, copy);
440 | return copy;
441 | }
442 | }
443 | }
444 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/DumpAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.HashMap;
19 | import java.util.Map;
20 |
21 | /**
22 | * dump axml to stdout
23 | *
24 | * @author Panxiaobo
25 | */
26 | public class DumpAdapter extends AxmlVisitor {
27 | protected int deep;
28 | protected Map nses;
29 |
30 | public DumpAdapter() {
31 | this(null);
32 | }
33 |
34 | public DumpAdapter(NodeVisitor nv) {
35 | this(nv, 0, new HashMap());
36 | }
37 |
38 | public DumpAdapter(NodeVisitor nv, int x, Map nses) {
39 | super(nv);
40 | this.deep = x;
41 | this.nses = nses;
42 | }
43 |
44 | @Override
45 | public void attr(String ns, String name, int resourceId, int type, Object obj) {
46 | for (int i = 0; i < deep; i++) {
47 | System.out.print(" ");
48 | }
49 | if (ns != null) {
50 | System.out.print(String.format("%s:", getPrefix(ns)));
51 | }
52 | System.out.print(name);
53 | if (resourceId != -1) {
54 | System.out.print(String.format("(%08x)", resourceId));
55 | }
56 | if (obj instanceof String) {
57 | System.out.print(String.format("=[%08x]\"%s\"", type, obj));
58 | } else if (obj instanceof Boolean) {
59 | System.out.print(String.format("=[%08x]\"%b\"", type, obj));
60 | } else if (obj instanceof ValueWrapper) {
61 | ValueWrapper w = (ValueWrapper) obj;
62 | System.out.print(String.format("=[%08x]@%08x, raw: \"%s\"", type, w.ref, w.raw));
63 | } else if (type == TYPE_REFERENCE) {
64 | System.out.print(String.format("=[%08x]@%08x", type, obj));
65 | } else {
66 | System.out.print(String.format("=[%08x]%08x", type, obj));
67 | }
68 | System.out.println();
69 | super.attr(ns, name, resourceId, type, obj);
70 | }
71 |
72 | @Override
73 | public NodeVisitor child(String ns, String name) {
74 | for (int i = 0; i < deep; i++) {
75 | System.out.print(" ");
76 | }
77 | System.out.print("<");
78 | if (ns != null) {
79 | System.out.print(getPrefix(ns) + ":");
80 | }
81 | System.out.println(name);
82 | NodeVisitor nv = super.child(ns, name);
83 | if (nv != null) {
84 | return new DumpAdapter(nv, deep + 1, nses);
85 | }
86 | return null;
87 | }
88 |
89 | protected String getPrefix(String uri) {
90 | if (nses != null) {
91 | String prefix = nses.get(uri);
92 | if (prefix != null) {
93 | return prefix;
94 | }
95 | }
96 | return uri;
97 | }
98 |
99 | @Override
100 | public void ns(String prefix, String uri, int ln) {
101 | System.out.println(prefix + "=" + uri);
102 | this.nses.put(uri, prefix);
103 | super.ns(prefix, uri, ln);
104 | }
105 |
106 | @Override
107 | public void text(int ln, String value) {
108 | for (int i = 0; i < deep + 1; i++) {
109 | System.out.print(" ");
110 | }
111 | System.out.print("T: ");
112 | System.out.println(value);
113 | super.text(ln, value);
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/NodeVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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 | public abstract class NodeVisitor {
19 |
20 | public static final int TYPE_FIRST_INT = 0x10;
21 | public static final int TYPE_INT_BOOLEAN = 0x12;
22 | public static final int TYPE_INT_HEX = 0x11;
23 | public static final int TYPE_REFERENCE = 0x01;
24 | public static final int TYPE_STRING = 0x03;
25 | protected NodeVisitor nv;
26 |
27 | public NodeVisitor() {
28 | super();
29 | }
30 |
31 | public NodeVisitor(NodeVisitor nv) {
32 | super();
33 | this.nv = nv;
34 | }
35 |
36 | /**
37 | * add attribute to the node
38 | *
39 | * @param ns
40 | * @param name
41 | * @param resourceId
42 | * @param type
43 | * {@link #TYPE_STRING} or others
44 | * @param obj
45 | * a string for {@link #TYPE_STRING} ,and Integer for others
46 | */
47 | public void attr(String ns, String name, int resourceId, int type, Object obj) {
48 | if (nv != null) {
49 | nv.attr(ns, name, resourceId, type, obj);
50 | }
51 | }
52 |
53 | /**
54 | * create a child node
55 | *
56 | * @param ns
57 | * @param name
58 | * @return
59 | */
60 | public NodeVisitor child(String ns, String name) {
61 | if (nv != null) {
62 | return nv.child(ns, name);
63 | }
64 | return null;
65 | }
66 |
67 | /**
68 | * end the visit
69 | */
70 | public void end() {
71 | if (nv != null) {
72 | nv.end();
73 | }
74 | }
75 |
76 | /**
77 | * line number in the .xml
78 | *
79 | * @param ln
80 | */
81 | public void line(int ln) {
82 | if (nv != null) {
83 | nv.line(ln);
84 | }
85 | }
86 |
87 | /**
88 | * the node text
89 | *
90 | * @param value
91 | */
92 | public void text(int lineNumber, String value) {
93 | if (nv != null) {
94 | nv.text(lineNumber, value);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/Util.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2013 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.BufferedReader;
19 | import java.io.ByteArrayOutputStream;
20 | import java.io.File;
21 | import java.io.FileInputStream;
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import java.io.InputStreamReader;
26 | import java.io.OutputStream;
27 | import java.util.HashMap;
28 | import java.util.Map;
29 |
30 | public class Util {
31 | public static byte[] readFile(File in) throws IOException {
32 | InputStream is = new FileInputStream(in);
33 | byte[] xml = new byte[is.available()];
34 | is.read(xml);
35 | is.close();
36 | return xml;
37 | }
38 |
39 | public static byte[] readIs(InputStream is) throws IOException {
40 | ByteArrayOutputStream os = new ByteArrayOutputStream();
41 | copy(is, os);
42 | return os.toByteArray();
43 | }
44 |
45 | public static void writeFile(byte[] data, File out) throws IOException {
46 | FileOutputStream fos = new FileOutputStream(out);
47 | fos.write(data);
48 | fos.close();
49 | }
50 |
51 | public static Map readProguardConfig(File config) throws IOException {
52 | Map clzMap = new HashMap();
53 | BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(config), "utf8"));
54 | try {
55 | for (String ln = r.readLine(); ln != null; ln = r.readLine()) {
56 | if (ln.startsWith("#") || ln.startsWith(" ")) {
57 | continue;
58 | }
59 | // format a.pt.Main -> a.a.a:
60 | int i = ln.indexOf("->");
61 | if (i > 0) {
62 | clzMap.put(ln.substring(0, i).trim(), ln.substring(i + 2, ln.length() - 1).trim());
63 | }
64 | }
65 | } finally {
66 | r.close();
67 | }
68 | return clzMap;
69 | }
70 |
71 | public static void copy(InputStream is, OutputStream os) throws IOException {
72 | byte[] xml = new byte[10 * 1024];
73 | for (int c = is.read(xml); c > 0; c = is.read(xml)) {
74 | os.write(xml, 0, c);
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/pxb/android/axml/ValueWrapper.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml;
2 |
3 | public class ValueWrapper {
4 |
5 | public static final int ID = 1;
6 | public static final int STYLE = 2;
7 | public static final int CLASS = 3;
8 | public final int type;
9 | public final String raw;
10 | public final int ref;
11 |
12 | private ValueWrapper(int type, int ref, String raw) {
13 | super();
14 | this.type = type;
15 | this.raw = raw;
16 | this.ref = ref;
17 | }
18 |
19 | public ValueWrapper replaceRaw(String raw) {
20 | return new ValueWrapper(type, ref, raw);
21 | }
22 |
23 | public static ValueWrapper wrapId(int ref, String raw) {
24 | return new ValueWrapper(ID, ref, raw);
25 | }
26 |
27 | public static ValueWrapper wrapStyle(int ref, String raw) {
28 | return new ValueWrapper(STYLE, ref, raw);
29 | }
30 |
31 | public static ValueWrapper wrapClass(int ref, String raw) {
32 | return new ValueWrapper(CLASS, ref, raw);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pxb/android/package.html:
--------------------------------------------------------------------------------
1 | Source code of axml project.
2 | Revision 2394d02d86bb - Apr 22, 2014
--------------------------------------------------------------------------------