commit 48e1afd6b14d6da07b8f0b4544e78533ac454866 Author: Ryszard Wiśniewski Date: Fri Mar 12 12:47:56 2010 +0100 Added current version. diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5c33f50f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/nbproject +/build +/dist +/build.xml + diff --git a/src/android/content/res/StringBlock.java b/src/android/content/res/StringBlock.java new file mode 100644 index 00000000..2a92db75 --- /dev/null +++ b/src/android/content/res/StringBlock.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import brut.util.Jar; + + +/** + * Conveniences for retrieving data out of a compiled string resource. + * + * {@hide} + */ +final class StringBlock { + static { + Jar.load("/libAndroid.so"); + } + + private final int mNative; + private final boolean mOwnsNative; + private CharSequence[] mStrings; + + public CharSequence get(int idx) { + synchronized (this) { + if (mStrings != null) { + CharSequence res = mStrings[idx]; + if (res != null) { + return res; + } + } else { + final int num = nativeGetSize(mNative); + mStrings = new CharSequence[num]; + } + String str = nativeGetString(mNative, idx); + CharSequence res = str; + mStrings[idx] = res; + return res; + } + } + + protected void finalize() throws Throwable { + if (mOwnsNative) { + nativeDestroy(mNative); + } + } + + /** + * Create from an existing string block native object. This is + * -extremely- dangerous -- only use it if you absolutely know what you + * are doing! The given native object must exist for the entire lifetime + * of this newly creating StringBlock. + */ + StringBlock(int obj, boolean useSparse) { + mNative = obj; + mOwnsNative = false; + } + + private static final native int nativeGetSize(int obj); + private static final native String nativeGetString(int obj, int idx); + private static final native void nativeDestroy(int obj); +} diff --git a/src/android/content/res/XmlBlock.java b/src/android/content/res/XmlBlock.java new file mode 100644 index 00000000..9172dfc2 --- /dev/null +++ b/src/android/content/res/XmlBlock.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import android.util.TypedValue; + +import brut.util.Jar; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * Wrapper around a compiled XML file. + * + * {@hide} + */ +final public class XmlBlock { + static { + Jar.load("/libAndroid.so"); + } + + private static final boolean DEBUG=false; + + public XmlBlock(byte[] data) { + mNative = nativeCreate(data, 0, data.length); + mStrings = new StringBlock(nativeGetStringBlock(mNative), false); + } + + public XmlBlock(byte[] data, int offset, int size) { + mNative = nativeCreate(data, offset, size); + mStrings = new StringBlock(nativeGetStringBlock(mNative), false); + } + + public void close() { + synchronized (this) { + if (mOpen) { + mOpen = false; + decOpenCountLocked(); + } + } + } + + private void decOpenCountLocked() { + mOpenCount--; + if (mOpenCount == 0) { + nativeDestroy(mNative); + } + } + + public XmlPullParser newParser() { + synchronized (this) { + if (mNative != 0) { + return new Parser(nativeCreateParseState(mNative), this); + } + return null; + } + } + + /*package*/ final class Parser implements XmlPullParser { + Parser(int parseState, XmlBlock block) { + mParseState = parseState; + mBlock = block; + block.mOpenCount++; + } + + public void setFeature(String name, boolean state) throws XmlPullParserException { + if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { + return; + } + if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { + return; + } + throw new XmlPullParserException("Unsupported feature: " + name); + } + public boolean getFeature(String name) { + if (FEATURE_PROCESS_NAMESPACES.equals(name)) { + return true; + } + if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) { + return true; + } + return false; + } + public void setProperty(String name, Object value) throws XmlPullParserException { + throw new XmlPullParserException("setProperty() not supported"); + } + public Object getProperty(String name) { + return null; + } + public void setInput(Reader in) throws XmlPullParserException { + throw new XmlPullParserException("setInput() not supported"); + } + public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException { + throw new XmlPullParserException("setInput() not supported"); + } + public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { + throw new XmlPullParserException("defineEntityReplacementText() not supported"); + } + public String getNamespacePrefix(int pos) throws XmlPullParserException { + throw new XmlPullParserException("getNamespacePrefix() not supported"); + } + public String getInputEncoding() { + return null; + } + public String getNamespace(String prefix) { + throw new RuntimeException("getNamespace() not supported"); + } + public int getNamespaceCount(int depth) throws XmlPullParserException { + throw new XmlPullParserException("getNamespaceCount() not supported"); + } + public String getPositionDescription() { + return "Binary XML file line #" + getLineNumber(); + } + public String getNamespaceUri(int pos) throws XmlPullParserException { + throw new XmlPullParserException("getNamespaceUri() not supported"); + } + public int getColumnNumber() { + return -1; + } + public int getDepth() { + return mDepth; + } + public String getText() { + int id = nativeGetText(mParseState); + return id >= 0 ? mStrings.get(id).toString() : null; + } + public int getLineNumber() { + return nativeGetLineNumber(mParseState); + } + public int getEventType() throws XmlPullParserException { + return mEventType; + } + public boolean isWhitespace() throws XmlPullParserException { + // whitespace was stripped by aapt. + return false; + } + public String getPrefix() { + throw new RuntimeException("getPrefix not supported"); + } + public char[] getTextCharacters(int[] holderForStartAndLength) { + String txt = getText(); + char[] chars = null; + if (txt != null) { + holderForStartAndLength[0] = 0; + holderForStartAndLength[1] = txt.length(); + chars = new char[txt.length()]; + txt.getChars(0, txt.length(), chars, 0); + } + return chars; + } + public String getNamespace() { + int id = nativeGetNamespace(mParseState); + return id >= 0 ? mStrings.get(id).toString() : ""; + } + public String getName() { + int id = nativeGetName(mParseState); + return id >= 0 ? mStrings.get(id).toString() : null; + } + public String getAttributeNamespace(int index) { + int id = nativeGetAttributeNamespace(mParseState, index); + if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id); + if (id >= 0) return mStrings.get(id).toString(); + else if (id == -1) return ""; + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + public String getAttributeName(int index) { + int id = nativeGetAttributeName(mParseState, index); + if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id); + if (id >= 0) return mStrings.get(id).toString(); + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + public String getAttributePrefix(int index) { + throw new RuntimeException("getAttributePrefix not supported"); + } + public boolean isEmptyElementTag() throws XmlPullParserException { + // XXX Need to detect this. + return false; + } + public int getAttributeCount() { + return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1; + } + public String getAttributeValue(int index) { + int id = nativeGetAttributeStringValue(mParseState, index); + if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id); + if (id >= 0) return mStrings.get(id).toString(); + + // May be some other type... check and try to convert if so. + int t = nativeGetAttributeDataType(mParseState, index); + if (t == TypedValue.TYPE_NULL) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + + int v = nativeGetAttributeData(mParseState, index); + return TypedValue.coerceToString(t, v); + } + public String getAttributeType(int index) { + return "CDATA"; + } + public boolean isAttributeDefault(int index) { + return false; + } + public int nextToken() throws XmlPullParserException,IOException { + return next(); + } + public String getAttributeValue(String namespace, String name) { + int idx = nativeGetAttributeIndex(mParseState, namespace, name); + if (idx >= 0) { + if (DEBUG) System.out.println("getAttributeName of " + + namespace + ":" + name + " index = " + idx); + if (DEBUG) System.out.println( + "Namespace=" + getAttributeNamespace(idx) + + "Name=" + getAttributeName(idx) + + ", Value=" + getAttributeValue(idx)); + return getAttributeValue(idx); + } + return null; + } + public int next() throws XmlPullParserException,IOException { + if (!mStarted) { + mStarted = true; + return START_DOCUMENT; + } + if (mParseState == 0) { + return END_DOCUMENT; + } + int ev = nativeNext(mParseState); + if (mDecNextDepth) { + mDepth--; + mDecNextDepth = false; + } + switch (ev) { + case START_TAG: + mDepth++; + break; + case END_TAG: + mDecNextDepth = true; + break; + } + mEventType = ev; + if (ev == END_DOCUMENT) { + // Automatically close the parse when we reach the end of + // a document, since the standard XmlPullParser interface + // doesn't have such an API so most clients will leave us + // dangling. + close(); + } + return ev; + } + public void require(int type, String namespace, String name) throws XmlPullParserException,IOException { + if (type != getEventType() + || (namespace != null && !namespace.equals( getNamespace () ) ) + || (name != null && !name.equals( getName() ) ) ) + throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription()); + } + public String nextText() throws XmlPullParserException,IOException { + if(getEventType() != START_TAG) { + throw new XmlPullParserException( + getPositionDescription() + + ": parser must be on START_TAG to read next text", this, null); + } + int eventType = next(); + if(eventType == TEXT) { + String result = getText(); + eventType = next(); + if(eventType != END_TAG) { + throw new XmlPullParserException( + getPositionDescription() + + ": event TEXT it must be immediately followed by END_TAG", this, null); + } + return result; + } else if(eventType == END_TAG) { + return ""; + } else { + throw new XmlPullParserException( + getPositionDescription() + + ": parser must be on START_TAG or TEXT to read text", this, null); + } + } + public int nextTag() throws XmlPullParserException,IOException { + int eventType = next(); + if(eventType == TEXT && isWhitespace()) { // skip whitespace + eventType = next(); + } + if (eventType != START_TAG && eventType != END_TAG) { + throw new XmlPullParserException( + getPositionDescription() + + ": expected start or end tag", this, null); + } + return eventType; + } + public void close() { + synchronized (mBlock) { + if (mParseState != 0) { + nativeDestroyParseState(mParseState); + mParseState = 0; + mBlock.decOpenCountLocked(); + } + } + } + + protected void finalize() throws Throwable { + close(); + } + + /*package*/ final CharSequence getPooledString(int id) { + return mStrings.get(id); + } + + /*package*/ int mParseState; + private final XmlBlock mBlock; + private boolean mStarted = false; + private boolean mDecNextDepth = false; + private int mDepth = 0; + private int mEventType = START_DOCUMENT; + } + + protected void finalize() throws Throwable { + close(); + } + + private final int mNative; + private final StringBlock mStrings; + private boolean mOpen = true; + private int mOpenCount = 1; + + private static final native int nativeCreate(byte[] data, + int offset, + int size); + private static final native int nativeGetStringBlock(int obj); + + private static final native int nativeCreateParseState(int obj); + private static final native int nativeNext(int state); + private static final native int nativeGetNamespace(int state); + private static final native int nativeGetName(int state); + private static final native int nativeGetText(int state); + private static final native int nativeGetLineNumber(int state); + private static final native int nativeGetAttributeCount(int state); + private static final native int nativeGetAttributeNamespace(int state, int idx); + private static final native int nativeGetAttributeName(int state, int idx); + private static final native int nativeGetAttributeDataType(int state, int idx); + private static final native int nativeGetAttributeData(int state, int idx); + private static final native int nativeGetAttributeStringValue(int state, int idx); + private static final native int nativeGetAttributeIndex(int state, String namespace, String name); + private static final native void nativeDestroyParseState(int state); + + private static final native void nativeDestroy(int obj); +} diff --git a/src/android/util/TypedValue.java b/src/android/util/TypedValue.java new file mode 100644 index 00000000..4179f7e5 --- /dev/null +++ b/src/android/util/TypedValue.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * Container for a dynamically typed data value. Primarily used with + * {@link android.content.res.Resources} for holding resource values. + */ +public class TypedValue { + /** The value contains no data. */ + public static final int TYPE_NULL = 0x00; + + /** The data field holds a resource identifier. */ + public static final int TYPE_REFERENCE = 0x01; + /** The data field holds an attribute resource + * identifier (referencing an attribute in the current theme + * style, not a resource entry). */ + public static final int TYPE_ATTRIBUTE = 0x02; + /** The string field holds string data. In addition, if + * data is non-zero then it is the string block + * index of the string and assetCookie is the set of + * assets the string came from. */ + public static final int TYPE_STRING = 0x03; + /** The data field holds an IEEE 754 floating point number. */ + public static final int TYPE_FLOAT = 0x04; + /** The data field holds a complex number encoding a + * dimension value. */ + public static final int TYPE_DIMENSION = 0x05; + /** The data field holds a complex number encoding a fraction + * of a container. */ + public static final int TYPE_FRACTION = 0x06; + + /** Identifies the start of plain integer values. Any type value + * from this to {@link #TYPE_LAST_INT} means the + * data field holds a generic integer value. */ + public static final int TYPE_FIRST_INT = 0x10; + + /** The data field holds a number that was + * originally specified in decimal. */ + public static final int TYPE_INT_DEC = 0x10; + /** The data field holds a number that was + * originally specified in hexadecimal (0xn). */ + public static final int TYPE_INT_HEX = 0x11; + /** The data field holds 0 or 1 that was originally + * specified as "false" or "true". */ + public static final int TYPE_INT_BOOLEAN = 0x12; + + /** Identifies the start of integer values that were specified as + * color constants (starting with '#'). */ + public static final int TYPE_FIRST_COLOR_INT = 0x1c; + + /** The data field holds a color that was originally + * specified as #aarrggbb. */ + public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; + /** The data field holds a color that was originally + * specified as #rrggbb. */ + public static final int TYPE_INT_COLOR_RGB8 = 0x1d; + /** The data field holds a color that was originally + * specified as #argb. */ + public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; + /** The data field holds a color that was originally + * specified as #rgb. */ + public static final int TYPE_INT_COLOR_RGB4 = 0x1f; + + /** Identifies the end of integer values that were specified as color + * constants. */ + public static final int TYPE_LAST_COLOR_INT = 0x1f; + + /** Identifies the end of plain integer values. */ + public static final int TYPE_LAST_INT = 0x1f; + + /* ------------------------------------------------------------ */ + + /** Complex data: bit location of unit information. */ + public static final int COMPLEX_UNIT_SHIFT = 0; + /** Complex data: mask to extract unit information (after shifting by + * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as + * defined below. */ + public static final int COMPLEX_UNIT_MASK = 0xf; + + /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ + public static final int COMPLEX_UNIT_PX = 0; + /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent + * Pixels. */ + public static final int COMPLEX_UNIT_DIP = 1; + /** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */ + public static final int COMPLEX_UNIT_SP = 2; + /** {@link #TYPE_DIMENSION} complex unit: Value is in points. */ + public static final int COMPLEX_UNIT_PT = 3; + /** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */ + public static final int COMPLEX_UNIT_IN = 4; + /** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */ + public static final int COMPLEX_UNIT_MM = 5; + + /** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall + * size. */ + public static final int COMPLEX_UNIT_FRACTION = 0; + /** {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. */ + public static final int COMPLEX_UNIT_FRACTION_PARENT = 1; + + /** Complex data: where the radix information is, telling where the decimal + * place appears in the mantissa. */ + public static final int COMPLEX_RADIX_SHIFT = 4; + /** Complex data: mask to extract radix information (after shifting by + * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point + * representations as defined below. */ + public static final int COMPLEX_RADIX_MASK = 0x3; + + /** Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 */ + public static final int COMPLEX_RADIX_23p0 = 0; + /** Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */ + public static final int COMPLEX_RADIX_16p7 = 1; + /** Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */ + public static final int COMPLEX_RADIX_8p15 = 2; + /** Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */ + public static final int COMPLEX_RADIX_0p23 = 3; + + /** Complex data: bit location of mantissa information. */ + public static final int COMPLEX_MANTISSA_SHIFT = 8; + /** Complex data: mask to extract mantissa information (after shifting by + * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision; + * the top bit is the sign. */ + public static final int COMPLEX_MANTISSA_MASK = 0xffffff; + + /* ------------------------------------------------------------ */ + + /** + * If {@link #density} is equal to this value, then the density should be + * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. + */ + public static final int DENSITY_DEFAULT = 0; + + /** + * If {@link #density} is equal to this value, then there is no density + * associated with the resource and it should not be scaled. + */ + public static final int DENSITY_NONE = 0xffff; + + /* ------------------------------------------------------------ */ + + /** The type held by this value, as defined by the constants here. + * This tells you how to interpret the other fields in the object. */ + public int type; + + private static final float MANTISSA_MULT = + 1.0f / (1<>TypedValue.COMPLEX_RADIX_SHIFT) + & TypedValue.COMPLEX_RADIX_MASK]; + } + + private static final String[] DIMENSION_UNIT_STRS = new String[] { + "px", "dip", "sp", "pt", "in", "mm" + }; + private static final String[] FRACTION_UNIT_STRS = new String[] { + "%", "%p" + }; + + /** + * Perform type conversion as per {@link #coerceToString()} on an + * explicitly supplied type and data. + * + * @param type The data type identifier. + * @param data The data value. + * + * @return String The coerced string value. If the value is + * null or the type is not known, null is returned. + */ + public static final String coerceToString(int type, int data) + { + switch (type) { + case TYPE_NULL: + return null; + case TYPE_REFERENCE: + return "@" + data; + case TYPE_ATTRIBUTE: + return "?" + data; + case TYPE_FLOAT: + return Float.toString(Float.intBitsToFloat(data)); + case TYPE_DIMENSION: + return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[ + (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK]; + case TYPE_FRACTION: + return Float.toString(complexToFloat(data)*100) + FRACTION_UNIT_STRS[ + (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK]; + case TYPE_INT_HEX: + return "0x" + Integer.toHexString(data); + case TYPE_INT_BOOLEAN: + return data != 0 ? "true" : "false"; + } + + if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) { + return "#" + Integer.toHexString(data); + } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) { + return Integer.toString(data); + } + + return null; + } + +}; + diff --git a/src/brut/androlib/Androlib.java b/src/brut/androlib/Androlib.java new file mode 100644 index 00000000..191c1779 --- /dev/null +++ b/src/brut/androlib/Androlib.java @@ -0,0 +1,152 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib; + +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResTable; +import brut.common.BrutException; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import brut.directory.Util; +import brut.directory.ZipRODirectory; +import brut.util.OS; +import java.io.File; +import java.io.IOException; + +/** + * @author Ryszard Wiśniewski + */ +public class Androlib { + private final String mAndroidJar; + private final AndrolibResources mAndRes; + private final AndrolibSmali mSmali; + + public Androlib(String androidJar) { + mAndroidJar = androidJar; + mAndRes = new AndrolibResources(mAndroidJar); + mSmali = new AndrolibSmali(); + } + + public void decode(String apkFileName, String outDirName) + throws AndrolibException { + decode(new File(apkFileName), new File(outDirName)); + } + + public void decode(File apkFile, File outDir) + throws AndrolibException { + try { + OS.rmdir(outDir); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + outDir.mkdirs(); + + ResTable resTable = mAndRes.getResTable(apkFile); + + mAndRes.decode(resTable, apkFile, outDir); + + File smaliDir = new File(outDir.getPath() + "/smali"); + mSmali.baksmali(apkFile, smaliDir); + mAndRes.tagSmaliResIDs(resTable, smaliDir); + + try { + Directory in = new ZipRODirectory(apkFile); + Directory out = new FileDirectory(outDir); + if (in.containsDir("assets")) { + Util.copyFiles(in.getDir("assets"), out.createDir("assets")); + } + if (in.containsDir("lib")) { + Util.copyFiles(in.getDir("lib"), out.createDir("lib")); + } + } catch (DirectoryException ex) { + throw new AndrolibException("Could not decode apk", ex); + } + } + + public void buildAll() throws AndrolibException { + clean(); + new File("build/apk").mkdirs(); + buildCopyRawFiles(); + buildResources(); + buildClasses(); + buildPackage(); + } + + public void clean() throws AndrolibException { + try { + OS.rmdir("build"); + OS.rmdir("dist"); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public void buildCopyRawFiles() throws AndrolibException { + try { + if (new File("assets").exists()) { + OS.cpdir("assets", "build/apk/assets"); + } + if (new File("lib").exists()) { + OS.cpdir("lib", "build/apk/lib"); + } + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public void buildResources() throws AndrolibException { + File apkFile; + try { + apkFile = File.createTempFile("APKTOOL", null); + } catch (IOException ex) { + throw new AndrolibException(ex); + } + apkFile.delete(); + + mAndRes.aaptPackage( + apkFile, + new File("AndroidManifest.xml"), + new File("res") + ); + mAndRes.updateSmaliResIDs( + mAndRes.getResTable(apkFile), new File("smali")); + + try { + Util.copyFiles(new ZipRODirectory(apkFile), + new FileDirectory("build/apk")); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + + apkFile.delete(); + } + + public void buildClasses() throws AndrolibException { + new AndrolibSmali().smali("smali", "build/apk/classes.dex"); + } + + public void buildPackage() throws AndrolibException { + File distDir = new File("dist"); + if (! distDir.exists()) { + distDir.mkdirs(); + } + mAndRes.aaptPackage(new File("dist/out.apk"), null, null, + new File("build/apk"), false); + } +} diff --git a/src/brut/androlib/AndrolibException.java b/src/brut/androlib/AndrolibException.java new file mode 100644 index 00000000..846457ca --- /dev/null +++ b/src/brut/androlib/AndrolibException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib; + +import brut.common.BrutException; + +/** + * @author Ryszard Wiśniewski + */ +public class AndrolibException extends BrutException { + public AndrolibException() { + } + + public AndrolibException(String message) { + super(message); + } + + public AndrolibException(String message, Throwable cause) { + super(message, cause); + } + + public AndrolibException(Throwable cause) { + super(cause); + } +} diff --git a/src/brut/androlib/AndrolibSmali.java b/src/brut/androlib/AndrolibSmali.java new file mode 100644 index 00000000..736cdf9d --- /dev/null +++ b/src/brut/androlib/AndrolibSmali.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib; + +import java.io.File; +import java.io.IOException; +import org.jf.baksmali.baksmali; +import org.jf.dexlib.DexFile; +import org.jf.smali.main; + +/** + * @author Ryszard Wiśniewski + */ +class AndrolibSmali { + public void baksmali(File apkFile, File dir) throws AndrolibException { + baksmali(apkFile.getAbsolutePath(), dir.getAbsolutePath()); + } + + public void baksmali(String apkFile, String dir) throws AndrolibException { + try { + DexFile dexFile = new DexFile(apkFile); + baksmali.disassembleDexFile(dexFile, false, dir, new String[]{}, "", false, true, true, true, false, 0, false); + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + public void smali(File dir, File dexFile) throws AndrolibException { + smali(dir.getAbsolutePath(), dexFile.getAbsolutePath()); + } + + public void smali(String dir, String dexFile) throws AndrolibException { + main.main(new String[]{"smali", dir, "-o", dexFile}); + } +} diff --git a/src/brut/androlib/ApkFile.java b/src/brut/androlib/ApkFile.java new file mode 100644 index 00000000..c49bf198 --- /dev/null +++ b/src/brut/androlib/ApkFile.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib; + +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResTable; +import brut.common.BrutException; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import brut.directory.ZipRODirectory; +import brut.util.OS; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ApkFile { + private final AndrolibResources mAndRes; + private final File mApkFile; + private final AndrolibSmali mSmali; + + private ResTable mResTable; + + public ApkFile(AndrolibResources andRes, String apkFileName) { + this(andRes, new File(apkFileName)); + } + + public ApkFile(AndrolibResources andRes, File apkFile) { + mAndRes = andRes; + mApkFile = apkFile; + mSmali = new AndrolibSmali(); + } + + public void decode(String outDirName) throws AndrolibException { + decode(new File(outDirName)); + } + + public void decode(File outDir) throws AndrolibException { + if (outDir.exists()) { + throw new AndrolibException("Output directory already exists: " + + outDir.getAbsolutePath()); + } + outDir.mkdirs(); + + File smaliDir = new File(outDir.getPath() + "/smali"); + + mAndRes.decode(getResTable(), mApkFile, outDir); + mSmali.baksmali(mApkFile, smaliDir); + mAndRes.tagSmaliResIDs(getResTable(), smaliDir); + + try { + Directory in = new ZipRODirectory(mApkFile); + Directory out = new FileDirectory(outDir); + if (in.containsDir("assets")) { + Directory in2 = in.getDir("assets"); + Directory out2 = out.createDir("assets"); + for (String fileName : in2.getFiles(true)) { + IOUtils.copy(in2.getFileInput(fileName), + out2.getFileOutput(fileName)); + } + } + if (in.containsDir("lib")) { + Directory in2 = in.getDir("lib"); + Directory out2 = out.createDir("lib"); + for (String fileName : in2.getFiles(true)) { + IOUtils.copy(in2.getFileInput(fileName), + out2.getFileOutput(fileName)); + } + } + } catch (DirectoryException ex) { + throw new AndrolibException("Could not decode apk", ex); + } catch (IOException ex) { + throw new AndrolibException("Could not decode apk", ex); + } + } + + public void build(String inDir) throws AndrolibException { + build(new File(inDir)); + } + + public void build(File inDir) throws AndrolibException { + try { + File smaliDir = new File(inDir.getPath() + "/smali"); + + mAndRes.aaptPackage( + mApkFile, + new File(inDir.getPath() + "/AndroidManifest.xml"), + new File(inDir.getPath() + "/res") + ); + mAndRes.updateSmaliResIDs(getResTable(), smaliDir); + + File tmpDir = OS.createTempDirectory(); + try { + mSmali.smali(smaliDir, + new File(tmpDir.getPath() + "/classes.dex")); + if (new File(inDir.getPath() + "/assets").exists()) { + OS.cpdir(inDir.getPath() + "/assets", + tmpDir.getPath() + "/assets"); + } + if (new File(inDir.getPath() + "/lib").exists()) { + OS.cpdir(inDir.getPath() + "/lib", + tmpDir.getPath() + "/lib"); + } + + mAndRes.aaptPackage( + mApkFile, + null, + null, + tmpDir, + true + ); + + } finally { + OS.rmdir(tmpDir); + } + } catch (BrutException ex) { + throw new AndrolibException( + "Could not build apk for dir: " + inDir.getAbsolutePath(), ex); + } + } + + public ResTable getResTable() throws AndrolibException { + if (mResTable == null) { + mResTable = mAndRes.getResTable(mApkFile); + } + return mResTable; + } +} diff --git a/src/brut/androlib/res/AndrolibResources.java b/src/brut/androlib/res/AndrolibResources.java new file mode 100644 index 00000000..39dfd3b2 --- /dev/null +++ b/src/brut/androlib/res/AndrolibResources.java @@ -0,0 +1,218 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res; + +import brut.androlib.*; +import brut.androlib.res.data.*; +import brut.androlib.res.data.value.ResFileValue; +import brut.androlib.res.data.value.ResXmlSerializable; +import brut.androlib.res.decoder.*; +import brut.androlib.res.jni.JniPackage; +import brut.androlib.res.jni.JniPackageGroup; +import brut.common.BrutException; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import brut.directory.ZipRODirectory; +import brut.util.Jar; +import brut.util.OS; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * @author Ryszard Wiśniewski + */ +final public class AndrolibResources { + static { + Jar.load("/libAndroid.so"); + } + + private final File mAndroidJar; + + public AndrolibResources(String androidJar) { + this(new File(androidJar)); + } + + public AndrolibResources(File androidJar) { + this.mAndroidJar = androidJar; + } + + public ResTable getResTable(File apkFile) throws AndrolibException { + ResTable resTable = new ResTable(); + loadApk(resTable, new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()), false); + loadApk(resTable, apkFile, true); + return resTable; + } + + public void decode(ResTable resTable, File apkFile, File outDir) + throws AndrolibException { + ResXmlSerializer serial = getResXmlSerializer(resTable); + ResFileDecoder fileDecoder = getResFileDecoder(serial); + serial.setCurrentPackage( + resTable.listMainPackages().iterator().next()); + + Directory in, out; + try { + in = new ZipRODirectory(apkFile); + out = new FileDirectory(outDir); + + fileDecoder.decode( + in, "AndroidManifest.xml", out, "AndroidManifest.xml", "xml"); + + in = in.getDir("res"); + out = out.createDir("res"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + + for (ResPackage pkg : resTable.listMainPackages()) { + serial.setCurrentPackage(pkg); + for (ResResource res : pkg.listFiles()) { + ResFileValue fileValue = (ResFileValue) res.getValue(); + fileDecoder.decode(in, fileValue.getStrippedPath(), + out, res.getFilePath()); + } + for (ResValuesFile valuesFile : pkg.listValuesFiles()) { + generateValuesFile(valuesFile, out, serial); + } + } + } + + public void aaptPackage(File apkFile, File manifest, File resDir) + throws AndrolibException { + aaptPackage(apkFile, manifest, resDir, null, false); + } + + public void aaptPackage(File apkFile, File manifest, File resDir, + File rawDir, boolean update) throws AndrolibException { + String[] cmd = new String[12]; + int i = 0; + cmd[i++] = "aapt"; + cmd[i++] = "p"; + if (update) { + cmd[i++] = "-u"; + } + cmd[i++] = "-F"; + cmd[i++] = apkFile.getAbsolutePath(); + cmd[i++] = "-I"; + cmd[i++] = mAndroidJar.getAbsolutePath(); + if (manifest != null) { + cmd[i++] = "-M"; + cmd[i++] = manifest.getAbsolutePath(); + } + if (resDir != null) { + cmd[i++] = "-S"; + cmd[i++] = resDir.getAbsolutePath(); + } + if (rawDir != null) { + cmd[i++] = rawDir.getAbsolutePath(); + } + + try { + OS.exec(Arrays.copyOf(cmd, i)); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public void tagSmaliResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + new ResSmaliUpdater().tagResIDs(resTable, smaliDir); + } + + public void updateSmaliResIDs(ResTable resTable, File smaliDir) throws AndrolibException { + new ResSmaliUpdater().updateResIDs(resTable, smaliDir); + } + + public ResFileDecoder getResFileDecoder(ResXmlSerializer serializer) { + ResStreamDecoderContainer decoders = + new ResStreamDecoderContainer(); + decoders.setDecoder("raw", new ResRawStreamDecoder()); + decoders.setDecoder("xml", + new ResXmlStreamDecoder(serializer)); + return new ResFileDecoder(decoders); + } + + public ResXmlSerializer getResXmlSerializer(ResTable resTable) { + ResXmlSerializer serial = new ResXmlSerializer(); + serial.setProperty(serial.PROPERTY_SERIALIZER_INDENTATION, " "); + return serial; + } + + private void generateValuesFile(ResValuesFile valuesFile, Directory out, + ResXmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput(valuesFile.getPath()); + serial.setOutput((outStream), null); + serial.setDecodingEnabled(false); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResource res : valuesFile.listResources()) { + + ((ResXmlSerializable) res.getValue()) + .serializeToXml(serial, res); + } + + serial.endTag(null, "resources"); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException ex) { + throw new AndrolibException( + "Could not generate: " + valuesFile.getPath(), ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not generate: " + valuesFile.getPath(), ex); + } + } + + private void loadApk(ResTable resTable, File apkFile, boolean main) + throws AndrolibException { + JniPackageGroup[] groups = + nativeGetPackageGroups(apkFile.getAbsolutePath()); + if (groups.length != 1) { + throw new AndrolibException( + "Apk's with multiple or zero package groups not supported"); + } + for (int i = 0; i < groups.length; i++) { +// if (groups.length != 1 && i == 0) { +// continue; +// } + for (JniPackage jniPkg : groups[i].packages) { + ResPackage pkg = new JniPackageDecoder().decode(jniPkg, resTable); + resTable.addPackage(pkg, main); + } + } + } + + public static String escapeForResXml(String value) { + value = value.replace("'", "\\'"); + value = value.replace("\n", "\\n\n"); + char c = value.charAt(0); + if (c == '@' || c == '#' || c == '?') { + return '\\' + value; + } + return value; + } + + private static final native JniPackageGroup[] nativeGetPackageGroups( + String apkFileName); +} diff --git a/src/brut/androlib/res/ResSmaliUpdater.java b/src/brut/androlib/res/ResSmaliUpdater.java new file mode 100644 index 00000000..2fe2ba09 --- /dev/null +++ b/src/brut/androlib/res/ResSmaliUpdater.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResSpec; +import brut.androlib.res.data.ResTable; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResSmaliUpdater { + public void tagResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + Directory dir = null; + try { + dir = new FileDirectory(smaliDir); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag res IDs", ex); + } + for (String fileName : dir.getFiles(true)) { + try { + Iterator it = + IOUtils.readLines(dir.getFileInput(fileName)).iterator(); + PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); + while (it.hasNext()) { + String line = it.next(); + if (RES_NAME_PATTERN.matcher(line).matches()) { + out.println(line); + out.println(it.next()); + continue; + } + Matcher m = RES_ID_PATTERN.matcher(line); + if (m.matches()) { + int resID = parseResID(m.group(3)); + if (resID != -1) { + ResResSpec spec = resTable.getResSpec(resID); + out.println(String.format( + RES_NAME_FORMAT, spec.getFullName())); + } + } + out.println(line); + } + out.close(); + } catch (IOException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } catch (AndrolibException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } + } + } + + public void updateResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + try { + Directory dir = new FileDirectory(smaliDir); + for (String fileName : dir.getFiles(true)) { + Iterator it = + IOUtils.readLines(dir.getFileInput(fileName)).iterator(); + PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); + while (it.hasNext()) { + String line = it.next(); + out.println(line); + Matcher m1 = RES_NAME_PATTERN.matcher(line); + if (! m1.matches()) { + continue; + } + Matcher m2 = RES_ID_PATTERN.matcher(it.next()); + if (! m2.matches()) { + throw new AndrolibException(); + } + int resID = resTable.getPackage(m1.group(1)) + .getType(m1.group(2)).getResSpec(m1.group(3)) + .getId().id; + if (m2.group(1) != null) { + out.println(String.format( + RES_ID_FORMAT_FIELD, m2.group(1), resID)); + } else { + out.println(String.format( + RES_ID_FORMAT_CONST, m2.group(2), resID)); + } + } + out.close(); + } + } catch (IOException ex) { + throw new AndrolibException( + "Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex); + } + } + + private int parseResID(String resIDHex) { + if (resIDHex.endsWith("ff")) { + return -1; + } + int resID = Integer.valueOf(resIDHex, 16); + if (resIDHex.length() == 4) { + resID = resID << 16; + } + return resID; + } + + private final static String RES_ID_FORMAT_FIELD = + ".field %s:I = 0x%08x"; + private final static String RES_ID_FORMAT_CONST = + " const %s, 0x%08x"; + private final static Pattern RES_ID_PATTERN = Pattern.compile( + "^(?:\\.field (.+?):I =| const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$"); + private final static String RES_NAME_FORMAT = + "# APKTOOL/RES_NAME: %s"; + private final static Pattern RES_NAME_PATTERN = Pattern.compile( + "^# APKTOOL/RES_NAME: ([a-zA-Z0-9.]+):([a-z]+)/([a-zA-Z0-9._]+)$"); +} diff --git a/src/brut/androlib/res/data/ResConfig.java b/src/brut/androlib/res/data/ResConfig.java new file mode 100644 index 00000000..e0d9d5a7 --- /dev/null +++ b/src/brut/androlib/res/data/ResConfig.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResConfig { + private final ResConfigFlags mFlags; + private final Map mResources = + new LinkedHashMap(); + + public ResConfig(ResConfigFlags flags) { + this.mFlags = flags; + } + + public Set listResources() { + return new LinkedHashSet(mResources.values()); + } + + public ResResource getResource(ResResSpec spec) throws AndrolibException { + ResResource res = mResources.get(spec); + if (res == null) { + throw new AndrolibException(String.format( + "Undefined resource: spec=%s, config=%s", spec, this)); + } + return res; + } + + public Set listResSpecs() { + return mResources.keySet(); + } + + public ResConfigFlags getFlags() { + return mFlags; + } + + public void addResource(ResResource res) + throws AndrolibException { + ResResSpec spec = res.getResSpec(); + if (mResources.put(spec, res) != null) { + throw new AndrolibException(String.format( + "Multiple resources: spec=%s, config=%s", spec, this)); + } + } + + @Override + public String toString() { + return mFlags.toString(); + } +} diff --git a/src/brut/androlib/res/data/ResConfigFlags.java b/src/brut/androlib/res/data/ResConfigFlags.java new file mode 100644 index 00000000..caab76b8 --- /dev/null +++ b/src/brut/androlib/res/data/ResConfigFlags.java @@ -0,0 +1,342 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import brut.androlib.res.jni.JniConfig; + +/** + * @author Ryszard Wiśniewski + */ +public class ResConfigFlags { + public final int mcc; + public final int mnc; + + public final char[] language; + public final char[] country; + +// public final Orientation orientation; +// public final Touchscreen touchscreen; + public final int orientation; + public final int touchscreen; + public final int density; + +// public final Keyboard keyboard; +// public final Navigation navigation; +// public final Keys keys; +// public final Nav nav; + public final int keyboard; + public final int navigation; + public final int inputFlags; + + public final int screenWidth; + public final int screenHeight; +// public final ScreenSize screenSize; +// public final ScreenLong screenLong; + public final int screenLayout; + + public final int sdkVersion; + + private final String mQualifiers; + + public ResConfigFlags() { + mcc = 0; + mnc = 0; + language = new char[]{'\00', '\00'}; + country = new char[]{'\00', '\00'}; + orientation = ORIENTATION_ANY; + touchscreen = TOUCHSCREEN_ANY; + density = DENSITY_DEFAULT; + keyboard = KEYBOARD_ANY; + navigation = NAVIGATION_ANY; + inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY; + screenWidth = 0; + screenHeight = 0; + screenLayout = SCREENLONG_ANY | SCREENSIZE_ANY; + sdkVersion = 0; + mQualifiers = ""; + } + + public ResConfigFlags(JniConfig cfg) throws AndrolibException { +// if (cfg.mcc == 0 && mnc != 0) { +// throw new AndrolibException(String.format( +// "Invalid IMSI: mcc=%3d, mnc=%3d", mcc, mnc)); +// } + mcc = cfg.mcc; + mnc = cfg.mnc; + +// if (cfg.language.length == 0 && cfg.country.length != 0) { +// throw new AndrolibException(String.format( +// "Invalid local: language=%s, country=%s", +// cfg.language, cfg.country)); +// } + language = cfg.language; + country = cfg.country; +// +// switch (cfg.orientation) { +// case ORIENTATION_ANY: +// orientation = Orientation.ANY; +// break; +// case ORIENTATION_LAND: +// orientation = Orientation.LAND; +// break; +// case ORIENTATION_PORT: +// orientation = Orientation.PORT; +// break; +// case ORIENTATION_SQUARE: +// orientation = Orientation.SQUARE; +// break; +// default: +// throw new AndrolibException(String.format( +// "Invalid orientation: %d", cfg.orientation)); +// } + + orientation = cfg.orientation; + touchscreen = cfg.touchscreen; + density = cfg.density; + keyboard = cfg.keyboard; + navigation = cfg.navigation; + inputFlags = cfg.inputFlags; + screenWidth = cfg.screenWidth; + screenHeight = cfg.screenHeight; + screenLayout = cfg.screenLayout; + sdkVersion = cfg.sdkVersion; + + mQualifiers = generateQualifiers(); + } + + public String getQualifiers() { + return mQualifiers; + } + + private String generateQualifiers() { + StringBuilder ret = new StringBuilder(); + if (mcc != 0) { + ret.append("-mcc").append(mcc); + if (mnc != 0) { + ret.append("-mnc").append(mnc); + } + } + if (language[0] != '\00') { + ret.append('-').append(language); + if (country[0] != '\00') { + ret.append("-r").append(country); + } + } + switch (screenLayout & MASK_SCREENSIZE) { + case SCREENSIZE_SMALL: + ret.append("-small"); + break; + case SCREENSIZE_NORMAL: + ret.append("-normal"); + break; + case SCREENSIZE_LARGE: + ret.append("-large"); + break; + } + switch (screenLayout & MASK_SCREENLONG) { + case SCREENLONG_YES: + ret.append("-long"); + break; + case SCREENLONG_NO: + ret.append("-notlong"); + break; + } + switch (orientation) { + case ORIENTATION_PORT: + ret.append("-port"); + break; + case ORIENTATION_LAND: + ret.append("-land"); + break; + case ORIENTATION_SQUARE: + ret.append("-square"); + break; + } + switch (density) { + case DENSITY_DEFAULT: + break; + case DENSITY_LOW: + ret.append("-ldpi"); + break; + case DENSITY_MEDIUM: + ret.append("-mdpi"); + break; + case DENSITY_HIGH: + ret.append("-hdpi"); + break; + case DENSITY_NONE: + ret.append("-nodpi"); + break; + default: + ret.append('-').append(density).append("dpi"); + } + switch (touchscreen) { + case TOUCHSCREEN_NOTOUCH: + ret.append("-notouch"); + break; + case TOUCHSCREEN_STYLUS: + ret.append("-stylus"); + break; + case TOUCHSCREEN_FINGER: + ret.append("-finger"); + break; + } + switch (inputFlags & MASK_KEYSHIDDEN) { + case KEYSHIDDEN_NO: + ret.append("-keysexposed"); + break; + case KEYSHIDDEN_YES: + ret.append("-keyshidden"); + break; + case KEYSHIDDEN_SOFT: + ret.append("-keyssoft"); + break; + } + switch (keyboard) { + case KEYBOARD_NOKEYS: + ret.append("-nokeys"); + break; + case KEYBOARD_QWERTY: + ret.append("-qwerty"); + break; + case KEYBOARD_12KEY: + ret.append("-12key"); + break; + } + switch (inputFlags & MASK_NAVHIDDEN) { + case NAVHIDDEN_NO: + ret.append("-navexposed"); + break; + case NAVHIDDEN_YES: + ret.append("-navhidden"); + break; + } + switch (navigation) { + case NAVIGATION_NONAV: + ret.append("-nonav"); + break; + case NAVIGATION_DPAD: + ret.append("-dpad"); + break; + case NAVIGATION_TRACKBALL: + ret.append("-trackball"); + break; + case NAVIGATION_WHEEL: + ret.append("-wheel"); + break; + } + if (screenWidth != 0 && screenHeight != 0) { + if (screenWidth > screenHeight) { + ret.append(String.format("-%dx%d", screenWidth, screenHeight)); + } else { + ret.append(String.format("-%dx%d", screenHeight, screenWidth)); + } + } + if (sdkVersion != 0) { + ret.append("-v").append(sdkVersion); + } + + return ret.toString(); + } + + @Override + public String toString() { + return ! getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResConfigFlags other = (ResConfigFlags) obj; + if ((this.mQualifiers == null) ? (other.mQualifiers != null) : !this.mQualifiers.equals(other.mQualifiers)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.mQualifiers != null ? this.mQualifiers.hashCode() : 0); + return hash; + } + +// public enum Orientation {ANY, PORT, LAND, SQUARE} +// public enum Touchscreen {ANY, NOTOUCH, STYLUS, FINGER} +// public enum Keyboard {ANY, NOKEYS, QWERTY, KEY12} +// public enum Navigation {ANY, NONAV, DPAD, TRACKBALL, WHEEL} +// public enum Keys {ANY, EXPOSED, HIDDEN, SOFT} +// public enum Nav {ANY, EXPOSED, HIDDEN} +// public enum ScreenSize {ANY, SMALL, NORMAL, LARGE} +// public enum ScreenLong {ANY, LONG, NOTLONG} + + public final static int ORIENTATION_ANY = 0x0000; + public final static int ORIENTATION_PORT = 0x0001; + public final static int ORIENTATION_LAND = 0x0002; + public final static int ORIENTATION_SQUARE = 0x0003; + + public final static int TOUCHSCREEN_ANY = 0x0000; + public final static int TOUCHSCREEN_NOTOUCH = 0x0001; + public final static int TOUCHSCREEN_STYLUS = 0x0002; + public final static int TOUCHSCREEN_FINGER = 0x0003; + + public final static int DENSITY_DEFAULT = 0; + public final static int DENSITY_LOW = 120; + public final static int DENSITY_MEDIUM = 160; + public final static int DENSITY_HIGH = 240; + public final static int DENSITY_NONE = 0xffff; + + public final static int KEYBOARD_ANY = 0x0000; + public final static int KEYBOARD_NOKEYS = 0x0001; + public final static int KEYBOARD_QWERTY = 0x0002; + public final static int KEYBOARD_12KEY = 0x0003; + + public final static int NAVIGATION_ANY = 0x0000; + public final static int NAVIGATION_NONAV = 0x0001; + public final static int NAVIGATION_DPAD = 0x0002; + public final static int NAVIGATION_TRACKBALL = 0x0003; + public final static int NAVIGATION_WHEEL = 0x0004; + + public final static int MASK_KEYSHIDDEN = 0x0003; + public final static int KEYSHIDDEN_ANY = 0x0000; + public final static int KEYSHIDDEN_NO = 0x0001; + public final static int KEYSHIDDEN_YES = 0x0002; + public final static int KEYSHIDDEN_SOFT = 0x0003; + + public final static int MASK_NAVHIDDEN = 0x000c; + public final static int NAVHIDDEN_ANY = 0x0000; + public final static int NAVHIDDEN_NO = 0x0004; + public final static int NAVHIDDEN_YES = 0x0008; + + public final static int MASK_SCREENSIZE = 0x0f; + public final static int SCREENSIZE_ANY = 0x00; + public final static int SCREENSIZE_SMALL = 0x01; + public final static int SCREENSIZE_NORMAL = 0x02; + public final static int SCREENSIZE_LARGE = 0x03; + + public final static int MASK_SCREENLONG = 0x30; + public final static int SCREENLONG_ANY = 0x00; + public final static int SCREENLONG_NO = 0x10; + public final static int SCREENLONG_YES = 0x20; +} diff --git a/src/brut/androlib/res/data/ResID.java b/src/brut/androlib/res/data/ResID.java new file mode 100644 index 00000000..abb3d465 --- /dev/null +++ b/src/brut/androlib/res/data/ResID.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +/** + * @author Ryszard Wiśniewski + */ +public class ResID { + public final int package_; + public final int type; + public final int entry; + + public final int id; + + public ResID(int package_, int type, int entry) { + this(package_, type, entry, (package_ << 24) + (type << 16) + entry); + } + + public ResID(int id) { + this(id >> 24, (id >> 16) & 0x000000ff, id & 0x0000ffff, id); + } + + public ResID(int package_, int type, int entry, int id) { + this.package_ = package_; + this.type = type; + this.entry = entry; + this.id = id; + } + + @Override + public String toString() { + return String.format("0x%08x", id); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + this.id; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResID other = (ResID) obj; + if (this.id != other.id) { + return false; + } + return true; + } +} diff --git a/src/brut/androlib/res/data/ResPackage.java b/src/brut/androlib/res/data/ResPackage.java new file mode 100644 index 00000000..a555c97a --- /dev/null +++ b/src/brut/androlib/res/data/ResPackage.java @@ -0,0 +1,195 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.value.ResFileValue; +import brut.androlib.res.data.value.ResValue; +import brut.androlib.res.data.value.ResValueFactory; +import brut.androlib.res.data.value.ResXmlSerializable; +import brut.util.Duo; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResPackage { + private final ResTable mResTable; + private final int mId; + private final String mName; + private final Map mResSpecs = + new LinkedHashMap(); + private final Map mConfigs = + new LinkedHashMap(); + private final Map mTypes = + new LinkedHashMap(); + private final Set mFiles = new LinkedHashSet(); + private final Map, ResValuesFile> mValuesFiles = + new LinkedHashMap, ResValuesFile>(); + + private ResValueFactory mValueFactory; + + public ResPackage(ResTable resTable, int id, String name) { + this.mResTable = resTable; + this.mId = id; + this.mName = name; + } + + public List listResSpecs() { + return new ArrayList(mResSpecs.values()); + } + + public boolean hasResSpec(ResID resID) { + return mResSpecs.containsKey(resID); + } + + public ResResSpec getResSpec(ResID resID) throws AndrolibException { + ResResSpec spec = mResSpecs.get(resID); + if (spec == null) { + throw new AndrolibException("Undefined resource spec: " + resID); + } + return spec; + } + + public List getConfigs() { + return new ArrayList(mConfigs.values()); + } + + public boolean hasConfig(ResConfigFlags flags) { + return mConfigs.containsKey(flags); + } + + public ResConfig getConfig(ResConfigFlags flags) throws AndrolibException { + ResConfig config = mConfigs.get(flags); + if (config == null) { + throw new AndrolibException("Undefined config: " + flags); + } + return config; + } + + public List listTypes() { + return new ArrayList(mTypes.values()); + } + + public boolean hasType(String typeName) { + return mTypes.containsKey(typeName); + } + + public ResType getType(String typeName) throws AndrolibException { + ResType type = mTypes.get(typeName); + if (type == null) { + throw new AndrolibException("Undefined type: " + typeName); + } + return type; + } + + public Set listFiles() { + return mFiles; + } + + public Collection listValuesFiles() { + return mValuesFiles.values(); + } + + public ResTable getResTable() { + return mResTable; + } + + public int getId() { + return mId; + } + + public String getName() { + return mName; + } + + public void addResSpec(ResResSpec spec) throws AndrolibException { + if (mResSpecs.put(spec.getId(), spec) != null) { + throw new AndrolibException("Multiple resource specs: " + spec); + } + } + + public void addConfig(ResConfig config) throws AndrolibException { + if (mConfigs.put(config.getFlags(), config) != null) { + throw new AndrolibException("Multiple configs: " + config); + } + } + + public void addType(ResType type) throws AndrolibException { + if (mTypes.put(type.getName(), type) != null) { + throw new AndrolibException("Multiple types: " + type); + } + } + + public void addResource(ResResource res) { + ResValue value = res.getValue(); + if (value instanceof ResFileValue) { + mFiles.add(res); + } + if (value instanceof ResXmlSerializable) { + ResType type = res.getResSpec().getType(); + ResConfig config = res.getConfig(); + Duo key = + new Duo(type, config); + ResValuesFile values = mValuesFiles.get(key); + if (values == null) { + values = new ResValuesFile(type, config); + mValuesFiles.put(key, values); + } + values.addResource(res); + } + } + + @Override + public String toString() { + return mName; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResPackage other = (ResPackage) obj; + if (this.mResTable != other.mResTable && (this.mResTable == null || !this.mResTable.equals(other.mResTable))) { + return false; + } + if (this.mId != other.mId) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 37 * hash + (this.mResTable != null ? this.mResTable.hashCode() : 0); + hash = 37 * hash + this.mId; + return hash; + } + + public ResValueFactory getValueFactory() { + if (mValueFactory == null) { + mValueFactory = new ResValueFactory(this); + } + return mValueFactory; + } +} diff --git a/src/brut/androlib/res/data/ResResSpec.java b/src/brut/androlib/res/data/ResResSpec.java new file mode 100644 index 00000000..ade038d9 --- /dev/null +++ b/src/brut/androlib/res/data/ResResSpec.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResResSpec { + private final ResID mId; + private final String mName; + private final ResPackage mPackage; + private final ResType mType; + private final Map mResources = + new LinkedHashMap(); + + public ResResSpec(ResID id, String name, ResPackage pkg, ResType type) { + this.mId = id; + this.mName = name; + this.mPackage = pkg; + this.mType = type; + } + + public Set listResources() { + return new LinkedHashSet(mResources.values()); + } + + public ResResource getResource(ResConfig config) throws AndrolibException { + return getResource(config.getFlags()); + } + + public ResResource getResource(ResConfigFlags config) + throws AndrolibException { + ResResource res = mResources.get(config); + if (res == null) { + throw new AndrolibException(String.format( + "Undefined resource: spec=%s, config=%s", this, config)); + } + return res; + } + + public ResResource getDefaultResource() throws AndrolibException { + return getResource(new ResConfigFlags()); + } + + public String getFullName() { + return getFullName(false, false); + } + + public String getFullName(ResPackage relativeToPackage, + boolean excludeType) { + return getFullName( + getPackage().equals(relativeToPackage), excludeType); + } + + public String getFullName(boolean excludePackage, boolean excludeType) { + return + (excludePackage ? "" : getPackage().getName() + ":") + + (excludeType ? "" : getType().getName() + "/") + getName(); + } + + public ResID getId() { + return mId; + } + + public String getName() { + return mName; + } + + public ResPackage getPackage() { + return mPackage; + } + + public ResType getType() { + return mType; + } + + public void addResource(ResResource res) + throws AndrolibException { + ResConfigFlags flags = res.getConfig().getFlags(); + if (mResources.put(flags, res) != null) { + throw new AndrolibException(String.format( + "Multiple resources: spec=%s, config=%s", this, flags)); + } + } + + @Override + public String toString() { + return mId.toString() + " " + mType.toString() + "/" + mName; + } +} diff --git a/src/brut/androlib/res/data/ResResource.java b/src/brut/androlib/res/data/ResResource.java new file mode 100644 index 00000000..d7a155f4 --- /dev/null +++ b/src/brut/androlib/res/data/ResResource.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.res.data.value.ResValue; + +/** + * @author Ryszard Wiśniewski + */ +public class ResResource { + private final ResConfig mConfig; + private final ResResSpec mResSpec; + private final ResValue mValue; + + public ResResource(ResConfig config, ResResSpec spec, + ResValue value) { + this.mConfig = config; + this.mResSpec = spec; + this.mValue = value; + } + + public String getFilePath() { + return mResSpec.getType().getName() + + mConfig.getFlags().getQualifiers() + "/" + mResSpec.getName(); + } + + public ResConfig getConfig() { + return mConfig; + } + + public ResResSpec getResSpec() { + return mResSpec; + } + + public ResValue getValue() { + return mValue; + } + + @Override + public String toString() { + return getFilePath(); + } +} diff --git a/src/brut/androlib/res/data/ResTable.java b/src/brut/androlib/res/data/ResTable.java new file mode 100644 index 00000000..68ab4b0d --- /dev/null +++ b/src/brut/androlib/res/data/ResTable.java @@ -0,0 +1,97 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.value.ResAttr; +import brut.androlib.res.data.value.ResValue; +import brut.androlib.res.data.value.ResValueFactory; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Ryszard Wiśniewski + */ +public class ResTable { + private final Map mPackagesById = + new HashMap(); + private final Map mPackagesByName = + new HashMap(); + private final Set mMainPackages = + new LinkedHashSet(); + + public ResTable() { + } + + public ResResSpec getResSpec(int resID) throws AndrolibException { + return getResSpec(new ResID(resID)); + } + + public ResResSpec getResSpec(ResID resID) throws AndrolibException { + return getPackage(resID.package_).getResSpec(resID); + } + + public Set listMainPackages() { + return mMainPackages; + } + + public ResPackage getPackage(int id) throws AndrolibException { + ResPackage pkg = mPackagesById.get(id); + if (pkg == null) { + throw new AndrolibException(String.format( + "Undefined package: id=%d", id)); + } + return pkg; + } + + public ResPackage getPackage(String name) throws AndrolibException { + ResPackage pkg = mPackagesByName.get(name); + if (pkg == null) { + throw new AndrolibException("Undefined package: name=" + name); + } + return pkg; + } + + public ResValue getValue(String package_, String type, String name) + throws AndrolibException { + return getPackage(package_).getType(type).getResSpec(name) + .getDefaultResource().getValue(); + } + + public void addPackage(ResPackage pkg, boolean main) + throws AndrolibException { + Integer id = pkg.getId(); + if (mPackagesById.containsKey(id)) { + throw new AndrolibException( + "Multiple packages: id=" + id.toString()); + } + String name = pkg.getName(); + if (mPackagesByName.containsKey(name)) { + throw new AndrolibException("Multiple packages: name=" + name); + } + + mPackagesById.put(id, pkg); + mPackagesByName.put(name, pkg); + if (main) { + mMainPackages.add(pkg); + } + } +} diff --git a/src/brut/androlib/res/data/ResType.java b/src/brut/androlib/res/data/ResType.java new file mode 100644 index 00000000..b71f5281 --- /dev/null +++ b/src/brut/androlib/res/data/ResType.java @@ -0,0 +1,294 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import brut.androlib.AndrolibException; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.decoder.ResFileDecoder; +import brut.androlib.res.decoder.ResXmlSerializer; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; + +/** + * @author Ryszard Wiśniewski + */ +public final class ResType { + private final String mName; +// private final TypeConfig mConfig; + private final Map mResSpecs = + new LinkedHashMap(); + + private final ResTable mResTable; + private final ResPackage mPackage; + + public ResType(String name, ResTable resTable, + ResPackage package_) { + this.mName = name; + this.mResTable = resTable; + this.mPackage = package_; + } + + public String getName() { + return mName; + } + + public Set listResSpecs() { + return new LinkedHashSet(mResSpecs.values()); + } + + public ResResSpec getResSpec(String name) throws AndrolibException { + ResResSpec spec = mResSpecs.get(name); + if (spec == null) { + throw new AndrolibException(String.format( + "Undefined resource spec: %s/%s", getName(), name)); + } + return spec; + } +// +// public void decode(AndrolibResources andRes, ResXmlSerializer serial, +// Directory in, Directory out) throws AndrolibException { +// decodeFiles(andRes, serial, in, out); +// decodeValues(andRes, serial, out); +// } +// +// public void decodeFiles(AndrolibResources andRes, ResXmlSerializer serial, +// Directory in, Directory out) throws AndrolibException { +// if (! mConfig.isFile) { +// return; +// } +// +// ResFileDecoder decoder = andRes.getResFileDecoder(serial); +// +// for (ResResSpec res : listResources()) { +// for (ResResource value : res.listResources()) { +// if (! value.isStrVal()) { +// continue; +// } +// String fileName = value.getStrVal(); +// if (! fileName.startsWith("res/")) { +// throw new AndrolibException( +// "Invalid res file location: " + fileName); +// } +// decoder.decode(in, fileName.substring(4), out, +// value.getFilePath()); +// } +// } +// } +// +// public void decodeValues(AndrolibResources andRes, ResXmlSerializer serial, +// Directory out) throws AndrolibException { +// if (! mConfig.isValues) { +// return; +// } +// +// boolean oldEscapeRefs = serial.setEscapeRefs(false); +// +// for (Entry> entry : +// groupValuesByConfig().entrySet()) { +// ResConfigFlags config = entry.getKey(); +// String filePath = "values" + config.getQualifiers() + "/" +// + mConfig.valuesFileName + ".xml"; +// +// try { +// serial.setOutput(out.getFileOutput(filePath), null); +// serial.startDocument(null, null); +// serial.startTag(null, "resources"); +// +// for (ResResource value : entry.getValue()) { +// if (mName.equals("array")) { +// decodeArrayValue(serial, value); +// } else { +// serial.startTag(null, mConfig.valuesTagName); +// serial.attribute( +// null, "name", value.getResSpec().getName()); +// +// if (mName.equals("style")) { +// decodeStyleValue(serial, value); +// } else if (mName.equals("attr")) { +// decodeAttrValue(serial, value); +// } else { +// decodeSimpleValue(serial, value); +// } +// +// serial.endTag(null, mConfig.valuesTagName); +// } +// } +// +// serial.endTag(null, "resources"); +// serial.endDocument(); +// } catch (IOException ex) { +// throw new AndrolibException( +// "Could not decode values for:" + filePath, ex); +// } catch (DirectoryException ex) { +// throw new AndrolibException( +// "Could not decode values for:" + filePath, ex); +// } +// } +// +// serial.setEscapeRefs(oldEscapeRefs); +// } +// +// private void decodeSimpleValue(ResXmlSerializer serial, ResResource value) +// throws IOException, AndrolibException { +// serial.text(value.toResXMlText()); +// } +// +// private void decodeArrayValue(ResXmlSerializer serial, ResResource value) +// throws IOException, AndrolibException { +// String arrType = value.getBag().getType(); +// String tagName = (arrType != null ? arrType + "-" : "") + "array"; +// serial.startTag(null, tagName); +// serial.attribute( +// null, "name", value.getResSpec().getName()); +// for (ResResource item : value.getBag().getItems().values()) { +// serial.startTag(null, "item"); +// serial.text(item.getStrVal()); +// serial.endTag(null, "item"); +// } +// serial.endTag(null, tagName); +// } +// +// private void decodeAttrValue(ResXmlSerializer serial, ResResource value) +// throws IOException, AndrolibException { +// +// } +// +// private void decodeStyleValue(ResXmlSerializer serial, ResResource value) +// throws IOException, AndrolibException { +// ResBag bag = value.getBag(); +// if (bag.getParent().id != 0) { +// serial.attribute(null, "parent", bag.getParentRes() +// .getResReference(false, mPackage.getName())); +// +// for (Entry entry : bag.getItems().entrySet()) { +// serial.startTag(null, "item"); +// +// ResResSpec attr = mResTable.getResSpec(entry.getKey()); +// serial.attribute(null, "name", attr.getResReference(false, mPackage.getName(), true).substring(1)); +// serial.text(attr.getDefaultResource().decodeAttrValue(entry.getValue().getAsString(), false, mPackage.getName())); +// serial.endTag(null, "item"); +// } +// } +// } + + public void addResSpec(ResResSpec spec) + throws AndrolibException { + if (mResSpecs.put(spec.getName(), spec) != null) { + throw new AndrolibException(String.format( + "Multiple res specs: %s/%s", getName(), spec.getName())); + } + } + + @Override + public String toString() { + return mName; + } +// +// private Map> groupValuesByConfig() { +// Map> grouped = +// new LinkedHashMap>(); +// +// for (ResResSpec res : listResSpecs()) { +// for (ResResource value : res.listResources()) { +// ResConfigFlags config = value.getConfig().getFlags(); +// Set values = grouped.get(config); +// if (values == null) { +// values = new LinkedHashSet(); +// grouped.put(config, values); +// } +// values.add(value); +// } +// } +// +// return grouped; +// } +// +// +// public static ResType factory(String name, ResTable resTable, +// ResPackage package_) throws AndrolibException { +// if (typesConfig == null) { +// loadTypesConfig(); +// } +// TypeConfig config = typesConfig.get(name); +// if (config == null) { +// throw new AndrolibException("Invalid type name: " + name); +// } +// return new ResType(name, config, resTable, package_); +// } +// +// private static void loadTypesConfig() { +// typesConfig = new HashMap(); +// typesConfig.put("anim", new FileTypeConfig()); +// typesConfig.put("drawable", new FileTypeConfig()); +// typesConfig.put("layout", new FileTypeConfig()); +// typesConfig.put("menu", new FileTypeConfig()); +// typesConfig.put("raw", new FileTypeConfig()); +// typesConfig.put("xml", new FileTypeConfig()); +// +// typesConfig.put("attr", new ValuesTypeConfig("attrs", "attr")); +// typesConfig.put("dimen", new ValuesTypeConfig("dimens", "dimen")); +// typesConfig.put("string", new ValuesTypeConfig("strings", "string")); +// typesConfig.put("integer", new ValuesTypeConfig("integers", "integer")); +// typesConfig.put("array", new ValuesTypeConfig("arrays", "array")); +// typesConfig.put("style", new ValuesTypeConfig("styles", "style")); +// +// typesConfig.put("color", new TypeConfig(true, true, "colors", "color")); +// +// typesConfig.put("bool", new TypeConfig()); +// typesConfig.put("id", new TypeConfig()); +//// typesConfig.put("integer", new TypeConfig()); +// typesConfig.put("plurals", new TypeConfig()); +// } +// +// private static Map typesConfig; +// +// +// public static class TypeConfig { +// public final boolean isFile; +// public final boolean isValues; +// public final String valuesFileName; +// public final String valuesTagName; +// +// public TypeConfig() { +// this(false, false, null, null); +// } +// +// public TypeConfig(boolean isFile, boolean isValues, +// String valuesFileName, String valuesTagName) { +// this.isFile = isFile; +// this.isValues = isValues; +// this.valuesFileName = valuesFileName; +// this.valuesTagName = valuesTagName; +// } +// } +// +// public static class FileTypeConfig extends TypeConfig { +// public FileTypeConfig() { +// super(true, false, null, null); +// } +// } +// +// public static class ValuesTypeConfig extends TypeConfig { +// public ValuesTypeConfig(String fileName, String tagName) { +// super(false, true, fileName, tagName); +// } +// } +} diff --git a/src/brut/androlib/res/data/ResValuesFile.java b/src/brut/androlib/res/data/ResValuesFile.java new file mode 100644 index 00000000..85c5699c --- /dev/null +++ b/src/brut/androlib/res/data/ResValuesFile.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValuesFile { + private final ResType mType; + private final ResConfig mConfig; + private final Set mResources = + new LinkedHashSet(); + + public ResValuesFile(ResType type, ResConfig config) { + this.mType = type; + this.mConfig = config; + } + + public String getPath() { + return "values" + mConfig.getFlags().getQualifiers() + + "/" + mType.getName() + + (mType.getName().endsWith("s") ? "" : "s") + + ".xml"; + } + + public Set listResources() { + return mResources; + } + + public ResType getType() { + return mType; + } + + public ResConfig getConfig() { + return mConfig; + } + + public void addResource(ResResource res) { + mResources.add(res); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResValuesFile other = (ResValuesFile) obj; + if (this.mType != other.mType && (this.mType == null || !this.mType.equals(other.mType))) { + return false; + } + if (this.mConfig != other.mConfig && (this.mConfig == null || !this.mConfig.equals(other.mConfig))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 37 * hash + (this.mType != null ? this.mType.hashCode() : 0); + hash = 37 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0); + return hash; + } +} diff --git a/src/brut/androlib/res/data/value/ResArrayValue.java b/src/brut/androlib/res/data/value/ResArrayValue.java new file mode 100644 index 00000000..4fbf2aa7 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResArrayValue.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResArrayValue extends ResBagValue implements ResXmlSerializable { + public ResArrayValue(ResReferenceValue parent, + Map items) { + super(parent, items); + } + + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String type = getType(); + type = (type == null ? "" : type + "-") + "array"; + + serializer.startTag(null, type); + serializer.attribute(null, "name", res.getResSpec().getName()); + for (ResScalarValue item : mItems.values()) { + serializer.startTag(null, "item"); + serializer.text(item.toResXmlFormat()); + serializer.endTag(null, "item"); + } + serializer.endTag(null, type); + } + + public String getType() { + if (mItems.size() == 0) { + return null; + } + Iterator it = mItems.values().iterator(); + String type = it.next().getType(); + while (it.hasNext()) { + String itemType = it.next().getType(); + if (! type.equals(itemType)) { + return null; + } + } + return type; + } +} diff --git a/src/brut/androlib/res/data/value/ResAttr.java b/src/brut/androlib/res/data/value/ResAttr.java new file mode 100644 index 00000000..de97134c --- /dev/null +++ b/src/brut/androlib/res/data/value/ResAttr.java @@ -0,0 +1,100 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.Map; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResAttr extends ResBagValue implements ResXmlSerializable { + private final int mType; + + public ResAttr(ResReferenceValue parent, + Map items, int type) { + super(parent, items); + mType = type; + } + + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + return value.toResXmlFormat(); + } + + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String type = getTypeAsString(); + + serializer.startTag(null, "attr"); + serializer.attribute(null, "name", res.getResSpec().getName()); + if (type != null) { + serializer.attribute(null, "format", type); + } + serializeBody(serializer, res); + serializer.endTag(null, "attr"); + } + + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException {} + + protected String getTypeAsString() { + String s = ""; + if ((mType & TYPE_REFERENCE) != 0) { + s += "|reference"; + } + if ((mType & TYPE_STRING) != 0) { + s += "|string"; + } + if ((mType & TYPE_INT) != 0) { + s += "|integer"; + } + if ((mType & TYPE_BOOL) != 0) { + s += "|boolean"; + } + if ((mType & TYPE_COLOR) != 0) { + s += "|color"; + } + if ((mType & TYPE_FLOAT) != 0) { + s += "|float"; + } + if ((mType & TYPE_DIMEN) != 0) { + s += "|dimension"; + } + if ((mType & TYPE_FRACTION) != 0) { + s += "|fraction"; + } + if (s.isEmpty()) { + return null; + } + return s.substring(1); + } + + private final static int TYPE_REFERENCE = 0x01; + private final static int TYPE_STRING = 0x02; + private final static int TYPE_INT = 0x04; + private final static int TYPE_BOOL = 0x08; + private final static int TYPE_COLOR = 0x10; + private final static int TYPE_FLOAT = 0x20; + private final static int TYPE_DIMEN = 0x40; + private final static int TYPE_FRACTION = 0x80; + private final static int TYPE_ANY_STRING = 0xee; +} diff --git a/src/brut/androlib/res/data/value/ResAttrFactory.java b/src/brut/androlib/res/data/value/ResAttrFactory.java new file mode 100644 index 00000000..7373c68d --- /dev/null +++ b/src/brut/androlib/res/data/value/ResAttrFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import java.util.Map; + +/** + * @author Ryszard Wiśniewski + */ +public class ResAttrFactory { + static ResValue factory(ResReferenceValue parent, + Map items) { + int type = ((ResIntValue) items.values().iterator().next()).getValue(); + + int attrType = type & 0x0000ffff; + switch (type & 0x00ff0000) { + case TYPE_ENUM: + return new ResEnumAttr(parent, items, attrType); + case TYPE_SET: + return new ResSetAttr(parent, items, attrType); + } + return new ResAttr(parent, items, attrType); + } + + private final static int TYPE_ENUM = 0x00010000; + private final static int TYPE_SET = 0x00020000; +} diff --git a/src/brut/androlib/res/data/value/ResBagValue.java b/src/brut/androlib/res/data/value/ResBagValue.java new file mode 100644 index 00000000..e468969c --- /dev/null +++ b/src/brut/androlib/res/data/value/ResBagValue.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import java.util.Map; + +/** + * @author Ryszard Wiśniewski + */ +public class ResBagValue extends ResValue { + protected final ResReferenceValue mParent; + protected final Map mItems; + + public ResBagValue(ResReferenceValue parent, + Map items) { + this.mParent = parent; + this.mItems = items; + } + + public ResReferenceValue getParent() { + return mParent; + } + + public Map getItems() { + return mItems; + } +} diff --git a/src/brut/androlib/res/data/value/ResBoolValue.java b/src/brut/androlib/res/data/value/ResBoolValue.java new file mode 100644 index 00000000..6a84a713 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResBoolValue.java @@ -0,0 +1,39 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResBoolValue extends ResScalarValue implements ResXmlSerializable { + private final boolean mValue; + + public ResBoolValue(boolean value) { + super("bool"); + this.mValue = value; + } + + public boolean getValue() { + return mValue; + } + + @Override + public String toResXmlFormat() { + return mValue ? "true" : "false"; + } +} diff --git a/src/brut/androlib/res/data/value/ResColorValue.java b/src/brut/androlib/res/data/value/ResColorValue.java new file mode 100644 index 00000000..91046b9e --- /dev/null +++ b/src/brut/androlib/res/data/value/ResColorValue.java @@ -0,0 +1,32 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResColorValue extends ResIntValue { + public ResColorValue(int value) { + super(value, "color"); + } + + @Override + public String toResXmlFormat() { + return String.format("#%08x", mValue); + } +} diff --git a/src/brut/androlib/res/data/value/ResEnumAttr.java b/src/brut/androlib/res/data/value/ResEnumAttr.java new file mode 100644 index 00000000..64af2e86 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResEnumAttr.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResEnumAttr extends ResMapAttr { + public ResEnumAttr(ResReferenceValue parent, + Map items, int type) { + super(parent, items, type); + } + + @Override + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + if (value instanceof ResIntValue) { + String ret = getItemsMap().get(((ResIntValue) value).getValue()); + if (ret != null) { + return ret; + } + } + return super.convertToResXmlFormat(value); + } + + @Override + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException { + for (Entry entry : getItemsMap().entrySet()) { + serializer.startTag(null, "enum"); + serializer.attribute(null, "name", entry.getValue()); + serializer.attribute(null, "value", String.valueOf(entry.getKey())); + serializer.endTag(null, "enum"); + } + } +} diff --git a/src/brut/androlib/res/data/value/ResFileValue.java b/src/brut/androlib/res/data/value/ResFileValue.java new file mode 100644 index 00000000..c322611e --- /dev/null +++ b/src/brut/androlib/res/data/value/ResFileValue.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFileValue extends ResValue { + private final String mPath; + + public ResFileValue(String path) { + this.mPath = path; + } + + public String getPath() { + return mPath; + } + + public String getStrippedPath() throws AndrolibException { + if (! mPath.startsWith("res/")) { + throw new AndrolibException( + "File path does not start with \"res/\": " + mPath); + } + return mPath.substring(4); + } +} diff --git a/src/brut/androlib/res/data/value/ResFloatValue.java b/src/brut/androlib/res/data/value/ResFloatValue.java new file mode 100644 index 00000000..4538f208 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResFloatValue.java @@ -0,0 +1,39 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFloatValue extends ResScalarValue { + private final float mValue; + + public ResFloatValue(float value) { + super("float"); + this.mValue = value; + } + + public float getValue() { + return mValue; + } + + @Override + public String toResXmlFormat() { + return String.valueOf(mValue); + } +} diff --git a/src/brut/androlib/res/data/value/ResIdValue.java b/src/brut/androlib/res/data/value/ResIdValue.java new file mode 100644 index 00000000..d3d177a7 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResIdValue.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResIdValue extends ResValue { + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + serializer.startTag(null, "item"); + serializer.attribute(null, "type", "id"); + serializer.attribute(null, "name", res.getResSpec().getName()); + serializer.endTag(null, "item"); + } +} diff --git a/src/brut/androlib/res/data/value/ResIntValue.java b/src/brut/androlib/res/data/value/ResIntValue.java new file mode 100644 index 00000000..2ecc40e3 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResIntValue.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResIntValue extends ResScalarValue implements ResXmlSerializable { + protected final int mValue; + + public ResIntValue(int value) { + this(value, "integer"); + } + + public ResIntValue(int value, String type) { + super(type); + this.mValue = value; + } + + public int getValue() { + return mValue; + } + + @Override + public String toResXmlFormat() throws AndrolibException { + return String.valueOf(mValue); + } +} diff --git a/src/brut/androlib/res/data/value/ResMapAttr.java b/src/brut/androlib/res/data/value/ResMapAttr.java new file mode 100644 index 00000000..cc962eb9 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResMapAttr.java @@ -0,0 +1,61 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * @author Ryszard Wiśniewski + */ +public abstract class ResMapAttr extends ResAttr { + private Map mMap; + + public ResMapAttr(ResReferenceValue parent, + Map items, int type) { + super(parent, items, type); + } + + protected Map getItemsMap() throws AndrolibException { + if (mMap == null) { + loadItemsMap(); + } + return mMap; + } + + private void loadItemsMap() throws AndrolibException { + mMap = new LinkedHashMap(); + Iterator> it = + mItems.entrySet().iterator(); + it.next(); + + while (it.hasNext()) { + Entry entry = it.next(); + // TODO + if (entry.getKey().getValue() < 0x01010000) { + continue; + } + mMap.put( + ((ResIntValue) entry.getValue()).getValue(), + entry.getKey().getReferent().getName()); + } + } +} diff --git a/src/brut/androlib/res/data/value/ResPluralsValue.java b/src/brut/androlib/res/data/value/ResPluralsValue.java new file mode 100644 index 00000000..04e7a2cb --- /dev/null +++ b/src/brut/androlib/res/data/value/ResPluralsValue.java @@ -0,0 +1,75 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResPluralsValue extends ResBagValue implements ResXmlSerializable { + public ResPluralsValue(ResReferenceValue parent, + Map items) { + super(parent, items); + } + + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + serializer.startTag(null, "plurals"); + serializer.attribute(null, "name", res.getResSpec().getName()); + for (Entry entry : getPluralsMap().entrySet()) { + serializer.startTag(null, "item"); + serializer.attribute(null, "quantity", entry.getKey()); + serializer.text(entry.getValue()); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "plurals"); + } + + private Map getPluralsMap() { + Map plurals = new LinkedHashMap(); + for (Entry entry + : mItems.entrySet()) { + String quantity = getQuantityMap()[ + (entry.getKey().getValue() & 0xffff) - 4]; + if (quantity != null) { + String value = ((ResStringValue) entry.getValue()).getValue(); + plurals.put(quantity, AndrolibResources.escapeForResXml(value)); + + } + } + return plurals; + } + + private static String[] getQuantityMap() { + if (quantityMap == null) { + quantityMap = new String[] + {"other", "zero", "one", "two", "few", "many"}; + } + return quantityMap; + } + + private static String[] quantityMap; +} diff --git a/src/brut/androlib/res/data/value/ResReferenceValue.java b/src/brut/androlib/res/data/value/ResReferenceValue.java new file mode 100644 index 00000000..0fc34338 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResReferenceValue.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResResSpec; + +/** + * @author Ryszard Wiśniewski + */ +public class ResReferenceValue extends ResIntValue { + private final ResPackage mPackage; + private final boolean mTheme; + + public ResReferenceValue(ResPackage package_, int value) { + this(package_, value, false); + } + + public ResReferenceValue(ResPackage package_, int value, boolean theme) { + super(value); + mPackage = package_; + mTheme = theme; + } + + @Override + public String toResXmlFormat() throws AndrolibException { + if (isNull()) { + return "@null"; + } +// try { + return + (mTheme ? '?' : '@') + + getReferent().getFullName(mPackage, mTheme); +// } catch (AndrolibException ex) { +// return "@" + String.valueOf(mValue); +// } + } + + public ResResSpec getReferent() throws AndrolibException { + return mPackage.getResTable().getResSpec(getValue()); + } + + public boolean isNull() { + return mValue == 0; + } +} diff --git a/src/brut/androlib/res/data/value/ResScalarValue.java b/src/brut/androlib/res/data/value/ResScalarValue.java new file mode 100644 index 00000000..00d61cb4 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResScalarValue.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public abstract class ResScalarValue extends ResValue + implements ResXmlPrintable, ResXmlSerializable { + protected final String mType; + + protected ResScalarValue(String type) { + mType = type; + } + + public abstract String toResXmlFormat() throws AndrolibException; + + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String tagName = res.getResSpec().getType().getName(); + serializer.startTag(null, tagName); + serializer.attribute(null, "name", res.getResSpec().getName()); + serializer.text(toResXmlFormat()); + serializer.endTag(null, tagName); + } + + public String getType() { + return mType; + } +} diff --git a/src/brut/androlib/res/data/value/ResSetAttr.java b/src/brut/androlib/res/data/value/ResSetAttr.java new file mode 100644 index 00000000..dcf0bec6 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResSetAttr.java @@ -0,0 +1,67 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResSetAttr extends ResMapAttr { + public ResSetAttr(ResReferenceValue parent, + Map items, int type) { + super(parent, items, type); + } + + @Override + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + if (! (value instanceof ResIntValue)) { + return super.convertToResXmlFormat(value); + } + int intVal = ((ResIntValue) value).getValue(); + String strVal = ""; + for (Entry entry : getItemsMap().entrySet()) { + int flag = entry.getKey(); + if ((intVal & flag) == flag) { + strVal += "|" + entry.getValue(); + } + } + if (strVal.isEmpty()) { + return ""; + } + return strVal.substring(1); + } + + @Override + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException { + for (Entry entry : getItemsMap().entrySet()) { + serializer.startTag(null, "flag"); + serializer.attribute(null, "name", entry.getValue()); + serializer.attribute(null, "value", + String.format("0x%08x", entry.getKey())); + serializer.endTag(null, "flag"); + } + } +} diff --git a/src/brut/androlib/res/data/value/ResStringValue.java b/src/brut/androlib/res/data/value/ResStringValue.java new file mode 100644 index 00000000..068c7f17 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResStringValue.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.res.AndrolibResources; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStringValue extends ResScalarValue + implements ResXmlSerializable { + private final String mValue; + + public ResStringValue(String value) { + super("string"); + this.mValue = value; + } + + public String getValue() { + return mValue; + } + + @Override + public String toResXmlFormat() { + if (mValue.isEmpty()) { + return ""; + } + return AndrolibResources.escapeForResXml(mValue); + } +} diff --git a/src/brut/androlib/res/data/value/ResStyleValue.java b/src/brut/androlib/res/data/value/ResStyleValue.java new file mode 100644 index 00000000..da1da193 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResStyleValue.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResSpec; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStyleValue extends ResBagValue implements ResXmlSerializable { + public ResStyleValue(ResReferenceValue parent, + Map items) { + super(parent, items); + } + + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + serializer.startTag(null, "style"); + serializer.attribute(null, "name", res.getResSpec().getName()); + if (! mParent.isNull()) { + serializer.attribute(null, "parent", mParent.toResXmlFormat()); + } + for (Entry entry + : mItems.entrySet()) { + ResResSpec spec = entry.getKey().getReferent(); + ResAttr attr = (ResAttr) spec.getDefaultResource().getValue(); + String value = attr.convertToResXmlFormat(entry.getValue()); + + if (value == null) { + continue; + } + + serializer.startTag(null, "item"); + serializer.attribute(null, "name", + spec.getFullName(res.getResSpec().getPackage(), true)); + serializer.text(value); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "style"); + } +} diff --git a/src/brut/androlib/res/data/value/ResValue.java b/src/brut/androlib/res/data/value/ResValue.java new file mode 100644 index 00000000..589c3f14 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResValue.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValue { + +} diff --git a/src/brut/androlib/res/data/value/ResValueFactory.java b/src/brut/androlib/res/data/value/ResValueFactory.java new file mode 100644 index 00000000..5b0ffcdd --- /dev/null +++ b/src/brut/androlib/res/data/value/ResValueFactory.java @@ -0,0 +1,157 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResTable; +import brut.androlib.res.data.ResType; +import brut.androlib.res.jni.JniBagItem; +import brut.androlib.res.jni.JniEntry; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValueFactory { + private final ResPackage mPackage; + + public ResValueFactory(ResPackage pakage_) { + this.mPackage = pakage_; + } + + public ResScalarValue factory(String string) { + if (string.isEmpty()) { + return new ResStringValue(string); + } + char c = string.charAt(0); + if (c == '@' || c == '?') { + return newReference( + Integer.parseInt(string.substring(1)), c == '?'); + } + if (c == '#') { + return new ResColorValue( + (int) Long.parseLong(string.substring(1), 16)); + } + try { + if (string.startsWith("0x")) { + return new ResIntValue( + (int) Long.parseLong(string.substring(2), 16)); + } + return new ResIntValue(Integer.parseInt(string)); + } catch (NumberFormatException ex) {} + return new ResStringValue(string); + } + + public ResValue factory(JniEntry entry) + throws AndrolibException { + if ("id".equals(entry.type)) { + return new ResIdValue(); + } + switch (entry.valueType) { + case TYPE_BAG: + return bagFactory(entry); + case TYPE_REFERENCE: + return newReference(entry.intVal); + case TYPE_ATTRIBUTE: + return newReference(entry.intVal, true); + case TYPE_INT_BOOLEAN: + return new ResBoolValue(entry.boolVal); + case TYPE_INT_DEC: + case TYPE_INT_HEX: + return new ResIntValue(entry.intVal); + case TYPE_FLOAT: + return new ResFloatValue(entry.floatVal); + case TYPE_INT_COLOR_ARGB4: + case TYPE_INT_COLOR_ARGB8: + case TYPE_INT_COLOR_RGB4: + case TYPE_INT_COLOR_RGB8: + return new ResColorValue(entry.intVal); + case TYPE_STRING: + if (entry.strVal.startsWith("res/")) { + return new ResFileValue(entry.strVal); + } + case TYPE_DIMENSION: + case TYPE_FRACTION: + return new ResStringValue(entry.strVal); + } + throw new AndrolibException(String.format( + "Unknown value type for %s/%s: ", + entry.type, entry.name, String.valueOf(entry.valueType))); + } + + private ResValue bagFactory(JniEntry entry) + throws AndrolibException { + ResReferenceValue parent = newReference(entry.bagParent); + Map items = + convertItems(entry.bagItems); + String type = entry.type; + + if ("array".equals(type)) { + return new ResArrayValue(parent, items); + } + if ("style".equals(type)) { + return new ResStyleValue(parent, items); + } + if ("plurals".equals(type)) { + return new ResPluralsValue(parent, items); + } + if ("attr".equals(type)) { + return ResAttrFactory.factory(parent, items); + } + return new ResBagValue(parent, items); + } + + private ResReferenceValue newReference(int resID) { + return newReference(resID, false); + } + + private ResReferenceValue newReference(int resID, boolean theme) { + return new ResReferenceValue(mPackage, resID, theme); + } + + private Map convertItems( + JniBagItem[] jniItems) throws AndrolibException { + Map items = + new LinkedHashMap(); + for (int i = 0; i < jniItems.length; i++) { + JniBagItem jniItem = jniItems[i]; + items.put(newReference(jniItem.resID), + (ResScalarValue) factory(jniItem.entry)); + } + return items; + } + + private final static int TYPE_NULL = 0x00; + private final static int TYPE_REFERENCE = 0x01; + private final static int TYPE_ATTRIBUTE = 0x02; + private final static int TYPE_STRING = 0x03; + private final static int TYPE_FLOAT = 0x04; + private final static int TYPE_DIMENSION = 0x05; + private final static int TYPE_FRACTION = 0x06; + private final static int TYPE_INT_DEC = 0x10; + private final static int TYPE_INT_HEX = 0x11; + private final static int TYPE_INT_BOOLEAN = 0x12; + private final static int TYPE_INT_COLOR_ARGB8 = 0x1c; + private final static int TYPE_INT_COLOR_RGB8 = 0x1d; + private final static int TYPE_INT_COLOR_ARGB4 = 0x1e; + private final static int TYPE_INT_COLOR_RGB4 = 0x1f; + + private final static int TYPE_BAG = -0x01; +} diff --git a/src/brut/androlib/res/data/value/ResXmlPrintable.java b/src/brut/androlib/res/data/value/ResXmlPrintable.java new file mode 100644 index 00000000..e784f524 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResXmlPrintable.java @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResXmlPrintable { + public String toResXmlFormat() throws AndrolibException; +} diff --git a/src/brut/androlib/res/data/value/ResXmlSerializable.java b/src/brut/androlib/res/data/value/ResXmlSerializable.java new file mode 100644 index 00000000..e5100315 --- /dev/null +++ b/src/brut/androlib/res/data/value/ResXmlSerializable.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResXmlSerializable { + public void serializeToXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException; +} diff --git a/src/brut/androlib/res/decoder/JniPackageDecoder.java b/src/brut/androlib/res/decoder/JniPackageDecoder.java new file mode 100644 index 00000000..8bdef5b5 --- /dev/null +++ b/src/brut/androlib/res/decoder/JniPackageDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.*; +import brut.androlib.res.data.value.ResValue; +import brut.androlib.res.data.value.ResValueFactory; +import brut.androlib.res.jni.*; + +/** + * @author Ryszard Wiśniewski + */ +public class JniPackageDecoder { + public ResPackage decode(JniPackage jniPkg, ResTable resTable) + throws AndrolibException { + ResPackage pkg = new ResPackage(resTable, jniPkg.id, jniPkg.name); + ResValueFactory valueFactory = pkg.getValueFactory(); + + JniConfig[] jniConfigs = jniPkg.configs; + for (int i = 0; i < jniConfigs.length; i++) { + JniConfig jniConfig = jniConfigs[i]; + + ResConfigFlags flags = new ResConfigFlags(jniConfig); + ResConfig config; + if (pkg.hasConfig(flags)) { + config = pkg.getConfig(flags); + } else { + config = new ResConfig(flags); + pkg.addConfig(config); + } + + JniEntry[] jniEntries = jniConfig.entries; + for (int j = 0; j < jniEntries.length; j++) { + JniEntry jniEntry = jniEntries[j]; + + ResType type; + String typeName = jniEntry.type; + if (pkg.hasType(typeName)) { + type = pkg.getType(typeName); + } else { + type = new ResType(typeName, resTable, pkg); + pkg.addType(type); + } + + ResID resID = new ResID(jniEntry.resID); + ResResSpec spec; + if (pkg.hasResSpec(resID)) { + spec = pkg.getResSpec(resID); + } else { + spec = new ResResSpec(resID, jniEntry.name, pkg, type); + pkg.addResSpec(spec); + type.addResSpec(spec); + } + + ResValue value = valueFactory.factory(jniEntry); + ResResource res = + new ResResource(config, spec, value); + + config.addResource(res); + spec.addResource(res); + pkg.addResource(res); + } + } + + return pkg; + } +} diff --git a/src/brut/androlib/res/decoder/ResFileDecoder.java b/src/brut/androlib/res/decoder/ResFileDecoder.java new file mode 100644 index 00000000..c0f58877 --- /dev/null +++ b/src/brut/androlib/res/decoder/ResFileDecoder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.AndrolibException; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFileDecoder { + private final ResStreamDecoderContainer mDecoders; + + public ResFileDecoder(ResStreamDecoderContainer decoders) { + this.mDecoders = decoders; + } + + public void decode(Directory inDir, String inFileName, Directory outDir, + String outResName) throws AndrolibException { + String ext = ""; + int extPos = inFileName.lastIndexOf("."); + if (extPos != -1) { + ext = inFileName.substring(extPos); + } + + if (inFileName.startsWith("raw/")) { + decode(inDir, inFileName, outDir, outResName + ext, "raw"); + return; + } + if (inFileName.endsWith(".9.png")) { + decode(inDir, inFileName, outDir, outResName + ".png", "raw"); + return; + } + if (inFileName.endsWith(".xml")) { + decode(inDir, inFileName, outDir, outResName + ".xml", "xml"); + return; + } + if (inFileName.endsWith(".html")) { + decode(inDir, inFileName, outDir, outResName + ".html", "xml"); + return; + } + + decode(inDir, inFileName, outDir, outResName + ext, "raw"); + } + + public void decode(Directory inDir, String inFileName, Directory outDir, + String outFileName, String decoder) throws AndrolibException { + try { + InputStream in = inDir.getFileInput(inFileName); + OutputStream out = outDir.getFileOutput(outFileName); + mDecoders.decode(in, out, decoder); + in.close(); + out.close(); + } catch (AndrolibException ex) { + throw new AndrolibException(String.format( + "Could not decode file \"%s\" to \"%s\"", + inFileName, outFileName), ex); + } catch (IOException ex) { + throw new AndrolibException(String.format( + "Could not decode file \"%s\" to \"%s\"", + inFileName, outFileName), ex); + } catch (DirectoryException ex) { + throw new AndrolibException(String.format( + "Could not decode file \"%s\" to \"%s\"", + inFileName, outFileName), ex); + } + } +} diff --git a/src/brut/androlib/res/decoder/ResRawStreamDecoder.java b/src/brut/androlib/res/decoder/ResRawStreamDecoder.java new file mode 100644 index 00000000..be38c823 --- /dev/null +++ b/src/brut/androlib/res/decoder/ResRawStreamDecoder.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.AndrolibException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResRawStreamDecoder implements ResStreamDecoder { + public void decode(InputStream in, OutputStream out) + throws AndrolibException { + try { + IOUtils.copy(in, out); + } catch (IOException ex) { + throw new AndrolibException("Could not decode raw stream", ex); + } + } +} diff --git a/src/brut/androlib/res/decoder/ResStreamDecoder.java b/src/brut/androlib/res/decoder/ResStreamDecoder.java new file mode 100644 index 00000000..73bd7975 --- /dev/null +++ b/src/brut/androlib/res/decoder/ResStreamDecoder.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.AndrolibException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResStreamDecoder { + public void decode(InputStream in, OutputStream out) + throws AndrolibException; +} diff --git a/src/brut/androlib/res/decoder/ResStreamDecoderContainer.java b/src/brut/androlib/res/decoder/ResStreamDecoderContainer.java new file mode 100644 index 00000000..71c8a6fe --- /dev/null +++ b/src/brut/androlib/res/decoder/ResStreamDecoderContainer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.AndrolibException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStreamDecoderContainer { + private final Map mDecoders = + new HashMap(); + + public void decode(InputStream in, OutputStream out, String decoderName) + throws AndrolibException { + getDecoder(decoderName).decode(in, out); + } + + public ResStreamDecoder getDecoder(String name) throws AndrolibException { + ResStreamDecoder decoder = mDecoders.get(name); + if (decoder== null) { + throw new AndrolibException("Undefined decoder: " + name); + } + return decoder; + } + + public void setDecoder(String name, ResStreamDecoder decoder) { + mDecoders.put(name, decoder); + } +} diff --git a/src/brut/androlib/res/decoder/ResXmlSerializer.java b/src/brut/androlib/res/decoder/ResXmlSerializer.java new file mode 100644 index 00000000..5c4f943d --- /dev/null +++ b/src/brut/androlib/res/decoder/ResXmlSerializer.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import brut.androlib.*; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.value.ResAttr; +import java.io.IOException; +import org.xmlpull.mxp1_serializer.MXSerializer; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResXmlSerializer extends MXSerializer { + private final static String RES_NAMESPACE = + "http://schemas.android.com/apk/res/android"; + + private ResPackage mCurrentPackage; + private boolean mDecodingEnabled = true; + + @Override + public XmlSerializer attribute(String namespace, String name, String value) + throws IOException, IllegalArgumentException, IllegalStateException + { + if (! mDecodingEnabled) { + return super.attribute(namespace, name, value); + } + if (namespace == null || namespace.isEmpty()) { + return super.attribute(namespace, name, +// AndrolibResources.escapeForResXml(value) + value + ); + } + String pkgName = RES_NAMESPACE.equals(namespace) ? + "android" : mCurrentPackage.getName(); + + try { + ResAttr attr = (ResAttr) mCurrentPackage.getResTable() + .getValue(pkgName, "attr", name); + value = attr.convertToResXmlFormat( + mCurrentPackage.getValueFactory().factory(value)); + } catch (AndrolibException ex) { + throw new IllegalArgumentException(String.format( + "could not decode attribute: ns=%s, name=%s, value=%s", + getPrefix(namespace, false), name, value), ex); + } + + if (value == null) { + return this; + } + +// if ("id".equals(name) && value.startsWith("@id")) { + if (value.startsWith("@id")) { + value = "@+id" + value.substring(3); + } + return super.attribute(namespace, name, value); + } + + @Override + public XmlSerializer text(String text) throws IOException { + if (mDecodingEnabled) { + text = AndrolibResources.escapeForResXml(text); + } + return super.text(text); + } + + @Override + public XmlSerializer text(char[] buf, int start, int len) throws IOException { + if (mDecodingEnabled) { + return this.text(new String(buf, start, len)); + } + return super.text(buf, start, len); + } + + @Override + public void startDocument(String encoding, Boolean standalone) throws + IOException, IllegalArgumentException, IllegalStateException { + super.startDocument(encoding != null ? encoding : "UTF-8", standalone); + super.out.write("\n"); + super.setPrefix("android", RES_NAMESPACE); + } + + public void setCurrentPackage(ResPackage package_) { + mCurrentPackage = package_; + } + + public boolean setDecodingEnabled(boolean escapeRefs) { + boolean oldVal = mDecodingEnabled; + this.mDecodingEnabled = escapeRefs; + return oldVal; + } +} diff --git a/src/brut/androlib/res/decoder/ResXmlStreamDecoder.java b/src/brut/androlib/res/decoder/ResXmlStreamDecoder.java new file mode 100644 index 00000000..83863c1b --- /dev/null +++ b/src/brut/androlib/res/decoder/ResXmlStreamDecoder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.decoder; + +import android.content.res.XmlBlock; +import brut.androlib.AndrolibException; +import java.io.*; +import org.apache.commons.io.IOUtils; +import org.xmlpull.v1.*; +import org.xmlpull.v1.wrapper.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResXmlStreamDecoder implements ResStreamDecoder { + private final ResXmlSerializer mSerializer; + + public ResXmlStreamDecoder(ResXmlSerializer serializer) { + this.mSerializer = serializer; + } + + public void decode(InputStream in, OutputStream out) + throws AndrolibException { + try { + XmlPullParserWrapper par = + getResXmlParserWrapper(in); + XmlSerializerWrapper ser = + getXmlWrapperFactory().newSerializerWrapper(mSerializer); + ser.setOutput(out, null); + mSerializer.setDecodingEnabled(true); + + while (par.nextToken() != XmlPullParser.END_DOCUMENT) { + ser.event(par); + } + ser.flush(); + } catch (IOException ex) { + throw new AndrolibException("could not decode XML stream", ex); + } catch (IllegalArgumentException ex) { + throw new AndrolibException("could not decode XML stream", ex); + } catch (IllegalStateException ex) { + throw new AndrolibException("could not decode XML stream", ex); + } catch (XmlPullParserException ex) { + throw new AndrolibException("could not decode XML stream", ex); + } + } + + private XmlPullParserWrapper getResXmlParserWrapper(InputStream in) + throws IOException, XmlPullParserException { + XmlBlock xml = new XmlBlock(copyStreamToByteArray(in)); + XmlPullParser parser = xml.newParser(); + return getXmlWrapperFactory().newPullParserWrapper(parser); + } + + private XmlPullWrapperFactory getXmlWrapperFactory() + throws XmlPullParserException { + return XmlPullWrapperFactory.newInstance(); + } + + private byte[] copyStreamToByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(in, out); + return out.toByteArray(); + } + +} diff --git a/src/brut/androlib/res/jni/JniBagItem.java b/src/brut/androlib/res/jni/JniBagItem.java new file mode 100644 index 00000000..84296c5a --- /dev/null +++ b/src/brut/androlib/res/jni/JniBagItem.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniBagItem { + public final int resID; + public final JniEntry entry; + + public JniBagItem(int resID, JniEntry entry) { + this.resID = resID; + this.entry = entry; + } +} diff --git a/src/brut/androlib/res/jni/JniConfig.java b/src/brut/androlib/res/jni/JniConfig.java new file mode 100644 index 00000000..4f2aa2df --- /dev/null +++ b/src/brut/androlib/res/jni/JniConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniConfig { + public final int mcc; + public final int mnc; + + public final char[] language; + public final char[] country; + + public final int orientation; + public final int touchscreen; + public final int density; + + public final int keyboard; + public final int navigation; + public final int inputFlags; + + public final int screenWidth; + public final int screenHeight; + public final int screenLayout; + + public final int sdkVersion; + + public final JniEntry[] entries; + + public JniConfig(int mcc, int mnc, char[] language, char[] country, + int orientation, int touchscreen, int density, int keyboard, + int navigation, int inputFlags, int screenWidth, int screenHeight, + int screenLayout, int sdkVersion, JniEntry[] entries) { + this.mcc = mcc; + this.mnc = mnc; + this.language = language; + this.country = country; + this.orientation = orientation; + this.touchscreen = touchscreen; + this.density = density; + this.keyboard = keyboard; + this.navigation = navigation; + this.inputFlags = inputFlags; + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + this.screenLayout = screenLayout; + this.sdkVersion = sdkVersion; + this.entries = entries; + } +} diff --git a/src/brut/androlib/res/jni/JniEntry.java b/src/brut/androlib/res/jni/JniEntry.java new file mode 100644 index 00000000..15c8acd6 --- /dev/null +++ b/src/brut/androlib/res/jni/JniEntry.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniEntry { + public final int resID; + public final String name; + public final String type; + + public final int valueType; + + public final boolean boolVal; + public final int intVal; + public final float floatVal; + public final String strVal; + + public final int bagParent; + public final JniBagItem[] bagItems; + + public JniEntry(int resID, String name, String type, int valueType, + boolean boolVal, int intVal, float floatVal, String strVal, + int bagParent, JniBagItem[] bagItems) { + this.resID = resID; + this.name = name; + this.type = type; + this.valueType = valueType; + this.boolVal = boolVal; + this.intVal = intVal; + this.floatVal = floatVal; + this.strVal = strVal; + this.bagParent = bagParent; + this.bagItems = bagItems; + } +} diff --git a/src/brut/androlib/res/jni/JniPackage.java b/src/brut/androlib/res/jni/JniPackage.java new file mode 100644 index 00000000..5a61242b --- /dev/null +++ b/src/brut/androlib/res/jni/JniPackage.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniPackage { + public final int id; + public final String name; + public final JniConfig[] configs; + + public JniPackage(int id, String name, JniConfig[] configs) { + this.id = id; + this.name = name; + this.configs = configs; + } +} diff --git a/src/brut/androlib/res/jni/JniPackageGroup.java b/src/brut/androlib/res/jni/JniPackageGroup.java new file mode 100644 index 00000000..a9704a7b --- /dev/null +++ b/src/brut/androlib/res/jni/JniPackageGroup.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniPackageGroup { + public final JniPackage[] packages; + + public JniPackageGroup(JniPackage[] packages) { + this.packages = packages; + } +} diff --git a/src/brut/androlib/res/jni/JniType.java b/src/brut/androlib/res/jni/JniType.java new file mode 100644 index 00000000..e875fea3 --- /dev/null +++ b/src/brut/androlib/res/jni/JniType.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * under the License. + */ + +package brut.androlib.res.jni; + +/** + * @author Ryszard Wiśniewski + */ +public class JniType { + public final int entryCount; + public final int typeSpecFlags; + public final JniConfig[] configs; + + public JniType(int entryCount, int typeSpecFlags, JniConfig[] configs) { + this.entryCount = entryCount; + this.typeSpecFlags = typeSpecFlags; + this.configs = configs; + } +} diff --git a/src/resources.arsc b/src/resources.arsc new file mode 100644 index 00000000..ffb43687 Binary files /dev/null and b/src/resources.arsc differ