Adding brut.apktool :/

This commit is contained in:
Connor Tumbleson 2012-09-19 20:27:35 -05:00
parent 60c806f2e2
commit 4db49ab347
96 changed files with 11218 additions and 1 deletions

@ -1 +0,0 @@
Subproject commit f7d1e37031f32285ac368e38b21da4fc7574a99d

202
brut.apktool/LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

11
brut.apktool/NOTICE Normal file
View File

@ -0,0 +1,11 @@
apktool
Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
This product includes software developed by:
* Ryszard Wiśniewski (brut.alll@gmail.com)
* JesusFreke (http://code.google.com/p/smali/)
* Dmitry Skiba (http://code.google.com/p/android4me/)
* Tahseen Ur Rehman (http://code.google.com/p/radixtree/)
* Android Open Source Project (http://source.android.com/)
* The Apache Software Foundation (http://www.apache.org/)

98
brut.apktool/NOTICE-smali Normal file
View File

@ -0,0 +1,98 @@
The majority of smali/baksmali is written and copyrighted by me (Ben Gruver)
and released under the following license:
*******************************************************************************
Copyright (c) 2010 Ben Gruver (JesusFreke)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Various portions of the code are taken from the Android Open Source Project,
and are used in accordance with the following license:
*******************************************************************************
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.
*******************************************************************************
The smali mojo plugin is (very loosely) based on an unknown mojo plugin with
the following license:
*******************************************************************************
Copyright 2001-2005 The Apache Software Foundation.
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.
*******************************************************************************
The RadixTree implementation in the "util" project is taken from
http://code.google.com/p/radixtree/ (version .3), and is used with minor
modifications in accordance with the following license:
*******************************************************************************
The MIT License
Copyright (c) 2008 Tahseen Ur Rehman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*******************************************************************************

View File

@ -0,0 +1,333 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.apktool;
import brut.androlib.Androlib;
import brut.androlib.AndrolibException;
import brut.androlib.ApkDecoder;
import brut.androlib.ApktoolProperties;
import brut.androlib.err.CantFindFrameworkResException;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.err.OutDirExistsException;
import brut.androlib.res.util.ExtFile;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.logging.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class Main {
public static void main(String[] args)
throws IOException, AndrolibException, InterruptedException {
try {
Verbosity verbosity = Verbosity.NORMAL;
int i;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-v".equals(opt) || "--verbose".equals(opt)) {
if (verbosity != Verbosity.NORMAL) {
throw new InvalidArgsError();
}
verbosity = Verbosity.VERBOSE;
} else if ("-q".equals(opt) || "--quiet".equals(opt)) {
if (verbosity != Verbosity.NORMAL) {
throw new InvalidArgsError();
}
verbosity = Verbosity.QUIET;
} else {
throw new InvalidArgsError();
}
}
setupLogging(verbosity);
if (args.length <= i) {
throw new InvalidArgsError();
}
String cmd = args[i];
args = Arrays.copyOfRange(args, i + 1, args.length);
if ("d".equals(cmd) || "decode".equals(cmd)) {
cmdDecode(args);
} else if ("b".equals(cmd) || "build".equals(cmd)) {
cmdBuild(args);
} else if ("if".equals(cmd) || "install-framework".equals(cmd)) {
cmdInstallFramework(args);
} else if ("publicize-resources".equals(cmd)) {
cmdPublicizeResources(args);
} else {
throw new InvalidArgsError();
}
} catch (InvalidArgsError ex) {
usage();
System.exit(1);
}
}
private static void cmdDecode(String[] args) throws InvalidArgsError,
AndrolibException {
ApkDecoder decoder = new ApkDecoder();
int i;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-s".equals(opt) || "--no-src".equals(opt)) {
decoder.setDecodeSources(ApkDecoder.DECODE_SOURCES_NONE);
} else if ("-d".equals(opt) || "--debug".equals(opt)) {
decoder.setDebugMode(true);
} else if ("-b".equals(opt) || "--no-debug-info".equals(opt)) {
decoder.setBaksmaliDebugMode(false);
} else if ("-t".equals(opt) || "--frame-tag".equals(opt)) {
i++;
if (i >= args.length) {
throw new InvalidArgsError();
}
decoder.setFrameworkTag(args[i]);
} else if ("-f".equals(opt) || "--force".equals(opt)) {
decoder.setForceDelete(true);
} else if ("-r".equals(opt) || "--no-res".equals(opt)) {
decoder.setDecodeResources(ApkDecoder.DECODE_RESOURCES_NONE);
} else if ("--keep-broken-res".equals(opt)) {
decoder.setKeepBrokenResources(true);
} else {
throw new InvalidArgsError();
}
}
String outName = null;
if (args.length == i + 2) {
outName = args[i + 1];
} else if (args.length == i + 1) {
outName = args[i];
outName = outName.endsWith(".apk") ?
outName.substring(0, outName.length() - 4) : outName + ".out";
outName = new File(outName).getName();
} else {
throw new InvalidArgsError();
}
File outDir = new File(outName);
decoder.setOutDir(outDir);
decoder.setApkFile(new File(args[i]));
try {
decoder.decode();
} catch (OutDirExistsException ex) {
System.out.println(
"Destination directory (" + outDir.getAbsolutePath() + ") " +
"already exists. Use -f switch if you want to overwrite it.");
System.exit(1);
} catch (InFileNotFoundException ex) {
System.out.println(
"Input file (" + args[i] + ") " +
"was not found or was not readable.");
System.exit(1);
} catch (CantFindFrameworkResException ex) {
System.out.println(
"Can't find framework resources for package of id: " +
String.valueOf(ex.getPkgId()) + ". You must install proper " +
"framework files, see project website for more info.");
System.exit(1);
}
}
private static void cmdBuild(String[] args) throws InvalidArgsError,
AndrolibException {
// hold all the fields
HashMap<String, Boolean> flags = new HashMap<String, Boolean>();
flags.put("forceBuildAll", false);
flags.put("debug", false);
flags.put("verbose", false);
flags.put("injectOriginal", false);
flags.put("framework", false);
flags.put("update", false);
int i;
int skip = 0;
ExtFile mOrigApk = null;
for (i = 0; i < args.length; i++) {
String opt = args[i];
if (! opt.startsWith("-")) {
break;
}
if ("-f".equals(opt) || "--force-all".equals(opt)) {
flags.put("forceBuildAll", true);
} else if ("-d".equals(opt) || "--debug".equals(opt)) {
flags.put("debug", true);
} else if ("-v".equals(opt) || "--verbose".equals(opt)) {
flags.put("verbose", true);
} else if ("-o".equals(opt) || "--original".equals(opt)) {
if (args.length >= 4) {
throw new InvalidArgsError();
} else {
flags.put("injectOriginal", true);
mOrigApk = new ExtFile(args[i + 1]);
skip = 1;
}
} else {
throw new InvalidArgsError();
}
}
String appDirName;
File outFile = null;
switch (args.length - i - skip) {
case 0:
appDirName = ".";
break;
case 2:
outFile = new File(args[i + 1 + skip]);
case 1:
appDirName = args[i + skip];
break;
default:
throw new InvalidArgsError();
}
new Androlib().build(new File(appDirName), outFile, flags, mOrigApk);
}
private static void cmdInstallFramework(String[] args)
throws AndrolibException {
String tag = null;
switch (args.length) {
case 2:
tag = args[1];
case 1:
new Androlib().installFramework(new File(args[0]), tag);
return;
}
throw new InvalidArgsError();
}
private static void cmdPublicizeResources(String[] args)
throws InvalidArgsError, AndrolibException {
if (args.length != 1) {
throw new InvalidArgsError();
}
new Androlib().publicizeResources(new File(args[0]));
}
private static void usage() {
System.out.println(
"Apktool v" + Androlib.getVersion() + " - a tool for reengineering Android apk files\n" +
"Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>\n" +
"with smali v" + ApktoolProperties.get("smaliVersion") +
", and baksmali v" + ApktoolProperties.get("baksmaliVersion") + "\n" +
"Updated by iBotPeaches <connor.tumbleson@gmail.com> \n" +
"Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n" +
"\n" +
"Usage: apktool [-q|--quiet OR -v|--verbose] COMMAND [...]\n" +
"\n" +
"COMMANDs are:\n" +
"\n" +
" d[ecode] [OPTS] <file.apk> [<dir>]\n" +
" Decode <file.apk> to <dir>.\n" +
"\n" +
" OPTS:\n" +
"\n" +
" -s, --no-src\n" +
" Do not decode sources.\n" +
" -r, --no-res\n" +
" Do not decode resources.\n" +
" -d, --debug\n" +
" Decode in debug mode. Check project page for more info.\n" +
" -b, --no-debug-info\n" +
" Baksmali -- don't write out debug info (.local, .param, .line, etc.)\n" +
" -f, --force\n" +
" Force delete destination directory.\n" +
" -t <tag>, --frame-tag <tag>\n" +
" Try to use framework files tagged by <tag>.\n" +
" --keep-broken-res\n" +
" Use if there was an error and some resources were dropped, e.g.:\n" +
" \"Invalid config flags detected. Dropping resources\", but you\n" +
" want to decode them anyway, even with errors. You will have to\n" +
" fix them manually before building." +
"\n\n" +
" b[uild] [OPTS] [<app_path>] [<out_file>]\n" +
" Build an apk from already decoded application located in <app_path>.\n" +
"\n" +
" It will automatically detect, whether files was changed and perform\n" +
" needed steps only.\n" +
"\n" +
" If you omit <app_path> then current directory will be used.\n" +
" If you omit <out_file> then <app_path>/dist/<name_of_original.apk>\n" +
" will be used.\n" +
"\n" +
" OPTS:\n" +
"\n" +
" -f, --force-all\n" +
" Skip changes detection and build all files.\n" +
" -d, --debug\n" +
" Build in debug mode. Check project page for more info.\n" +
" -o, --original\n" +
" Build resources into original APK. Retains signature." +
"\n" +
" if|install-framework <framework.apk> [<tag>]\n" +
" Install framework file to your system.\n" +
"\n" +
"For additional info, see: https://github.com/iBotPeaches/brut.apktool" +
"\n" +
"For smali/baksmali info, see: http://code.google.com/p/smali/"
);
}
private static void setupLogging(Verbosity verbosity) {
Logger logger = Logger.getLogger("");
for (Handler handler : logger.getHandlers()) {
logger.removeHandler(handler);
}
if (verbosity == Verbosity.QUIET) {
return;
}
Handler handler = new ConsoleHandler();
logger.addHandler(handler);
if (verbosity == Verbosity.VERBOSE) {
handler.setLevel(Level.ALL);
logger.setLevel(Level.ALL);
} else {
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return record.getLevel().toString().charAt(0) + ": "
+ record.getMessage()
+ System.getProperty("line.separator");
}
});
}
}
private static enum Verbosity {
NORMAL, VERBOSE, QUIET;
}
static class InvalidArgsError extends AndrolibException {
}
}

View File

@ -0,0 +1,36 @@
/*
* 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 org.xmlpull.v1.XmlPullParser;
import android.util.AttributeSet;
/**
* The XML parsing interface returned for an XML resource. This is a standard
* XmlPullParser interface, as well as an extended AttributeSet interface and
* an additional close() method on this interface for the client to indicate
* when it is done reading the resource.
*/
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
/**
* Close this interface to the resource. Calls on the interface are no
* longer value after this call.
*/
public void close();
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2008 Android4ME
*
* 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;
/**
* @author Dmitry Skiba
*
*/
public interface AttributeSet {
int getAttributeCount();
String getAttributeName(int index);
String getAttributeValue(int index);
String getPositionDescription();
int getAttributeNameResource(int index);
int getAttributeListValue(int index,String options[],int defaultValue);
boolean getAttributeBooleanValue(int index,boolean defaultValue);
int getAttributeResourceValue(int index,int defaultValue);
int getAttributeIntValue(int index,int defaultValue);
int getAttributeUnsignedIntValue(int index,int defaultValue);
float getAttributeFloatValue(int index,float defaultValue);
String getIdAttribute();
String getClassAttribute();
int getIdAttributeResourceValue(int index);
int getStyleAttribute();
String getAttributeValue(String namespace, String attribute);
int getAttributeListValue(String namespace,String attribute,String options[],int defaultValue);
boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue);
int getAttributeResourceValue(String namespace,String attribute,int defaultValue);
int getAttributeIntValue(String namespace,String attribute,int defaultValue);
int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue);
float getAttributeFloatValue(String namespace,String attribute,float defaultValue);
//TODO: remove
int getAttributeValueType(int index);
int getAttributeValueData(int index);
}

View File

@ -0,0 +1,263 @@
/*
* 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 <var>data</var> field holds a resource identifier. */
public static final int TYPE_REFERENCE = 0x01;
/** The <var>data</var> 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 <var>string</var> field holds string data. In addition, if
* <var>data</var> is non-zero then it is the string block
* index of the string and <var>assetCookie</var> is the set of
* assets the string came from. */
public static final int TYPE_STRING = 0x03;
/** The <var>data</var> field holds an IEEE 754 floating point number. */
public static final int TYPE_FLOAT = 0x04;
/** The <var>data</var> field holds a complex number encoding a
* dimension value. */
public static final int TYPE_DIMENSION = 0x05;
/** The <var>data</var> 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
* <var>data</var> field holds a generic integer value. */
public static final int TYPE_FIRST_INT = 0x10;
/** The <var>data</var> field holds a number that was
* originally specified in decimal. */
public static final int TYPE_INT_DEC = 0x10;
/** The <var>data</var> field holds a number that was
* originally specified in hexadecimal (0xn). */
public static final int TYPE_INT_HEX = 0x11;
/** The <var>data</var> 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 <var>data</var> field holds a color that was originally
* specified as #aarrggbb. */
public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
/** The <var>data</var> field holds a color that was originally
* specified as #rrggbb. */
public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
/** The <var>data</var> field holds a color that was originally
* specified as #argb. */
public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
/** The <var>data</var> 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_MANTISSA_SHIFT);
private static final float[] RADIX_MULTS = new float[] {
1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
};
/**
* Retrieve the base value from a complex data integer. This uses the
* {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of
* the data to compute a floating point representation of the number they
* describe. The units are ignored.
*
* @param complex A complex data value.
*
* @return A floating point value corresponding to the complex data.
*/
public static float complexToFloat(int complex)
{
return (complex&(TypedValue.COMPLEX_MANTISSA_MASK
<<TypedValue.COMPLEX_MANTISSA_SHIFT))
* RADIX_MULTS[(complex>>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) {
String res =String.format("%08x", data);
char[] vals = res.toCharArray();
switch (type) {
default:
case TYPE_INT_COLOR_ARGB8://#AaRrGgBb
break;
case TYPE_INT_COLOR_RGB8://#FFRrGgBb->#RrGgBb
res = res.substring(2);
break;
case TYPE_INT_COLOR_ARGB4://#AARRGGBB->#ARGB
res = new StringBuffer().append(vals[0]).append(vals[2]).append(vals[4]).append(vals[6]).toString();
break;
case TYPE_INT_COLOR_RGB4://#FFRRGGBB->#RGB
res = new StringBuffer().append(vals[2]).append(vals[4]).append(vals[6]).toString();
break;
}
return "#" + res;
} else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
String res;
switch (type) {
default:
case TYPE_INT_DEC:
res = Integer.toString(data);
break;
//defined before
/*case TYPE_INT_HEX:
res = "0x" + Integer.toHexString(data);
break;
case TYPE_INT_BOOLEAN:
res = (data != 0) ? "true":"false";
break;*/
}
return res;
}
return null;
}
};

View File

@ -0,0 +1,575 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.java.AndrolibJava;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtFile;
import brut.androlib.src.SmaliBuilder;
import brut.androlib.src.SmaliDecoder;
import brut.common.BrutException;
import brut.directory.*;
import brut.util.BrutIO;
import brut.util.OS;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class Androlib {
private final AndrolibResources mAndRes = new AndrolibResources();
public ResTable getResTable(ExtFile apkFile) throws AndrolibException {
return mAndRes.getResTable(apkFile, true);
}
public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg) throws AndrolibException {
return mAndRes.getResTable(apkFile, loadMainPkg);
}
public void decodeSourcesRaw(ExtFile apkFile, File outDir, boolean debug)
throws AndrolibException {
try {
if (debug) {
LOGGER.warning("Debug mode not available.");
}
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw classes.dex file...");
apkFile.getDirectory().copyToDir(outDir, "classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decodeSourcesSmali(File apkFile, File outDir, boolean debug, boolean bakdeb)
throws AndrolibException {
try {
File smaliDir = new File(outDir, SMALI_DIRNAME);
OS.rmdir(smaliDir);
smaliDir.mkdirs();
LOGGER.info("Baksmaling...");
SmaliDecoder.decode(apkFile, smaliDir, debug, bakdeb);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
public void decodeSourcesJava(ExtFile apkFile, File outDir, boolean debug)
throws AndrolibException {
LOGGER.info("Decoding Java sources...");
new AndrolibJava().decode(apkFile, outDir);
}
public void decodeManifestRaw(ExtFile apkFile, File outDir)
throws AndrolibException {
try {
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw manifest...");
apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decodeManifestFull(ExtFile apkFile, File outDir,
ResTable resTable) throws AndrolibException {
mAndRes.decodeManifest(resTable, apkFile, outDir);
}
public void decodeResourcesRaw(ExtFile apkFile, File outDir)
throws AndrolibException {
try {
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw resources...");
apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decodeResourcesFull(ExtFile apkFile, File outDir,
ResTable resTable) throws AndrolibException {
mAndRes.decode(resTable, apkFile, outDir);
}
public void decodeRawFiles(ExtFile apkFile, File outDir)
throws AndrolibException {
LOGGER.info("Copying assets and libs...");
try {
Directory in = apkFile.getDirectory();
if (in.containsDir("assets")) {
in.copyToDir(outDir, "assets");
}
if (in.containsDir("lib")) {
in.copyToDir(outDir, "lib");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void writeMetaFile(File mOutDir, Map<String, Object> meta)
throws AndrolibException {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
// options.setIndent(4);
Yaml yaml = new Yaml(options);
FileWriter writer = null;
try {
writer = new FileWriter(new File(mOutDir, "apktool.yml"));
yaml.dump(meta, writer);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ex) {}
}
}
}
public Map<String, Object> readMetaFile(ExtFile appDir)
throws AndrolibException {
InputStream in = null;
try {
in = appDir.getDirectory().getFileInput("apktool.yml");
Yaml yaml = new Yaml();
return (Map<String, Object>) yaml.load(in);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {}
}
}
}
public void build(File appDir, File outFile,
HashMap<String, Boolean> flags, ExtFile origApk) throws AndrolibException {
build(new ExtFile(appDir), outFile, flags, origApk);
}
public void build(ExtFile appDir, File outFile,
HashMap<String, Boolean> flags, ExtFile origApk) throws AndrolibException {
Map<String, Object> meta = readMetaFile(appDir);
Object t1 = meta.get("isFrameworkApk");
flags.put("framework", t1 == null ? false : (Boolean) t1);
mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo"));
// check the orig apk
if (flags.get("injectOriginal")) {
if (!origApk.isFile() || !origApk.canRead()) {
throw new InFileNotFoundException();
} else {
mOrigApkFile = origApk;
}
}
if (outFile == null) {
String outFileName = (String) meta.get("apkFileName");
outFile = new File(appDir, "dist" + File.separator +
(outFileName == null ? "out.apk" : outFileName));
}
new File(appDir, APK_DIRNAME).mkdirs();
buildSources(appDir, flags);
buildResources(appDir, flags,
(Map<String, Object>) meta.get("usesFramework"));
buildLib(appDir, flags);
buildApk(appDir, outFile,flags);
}
public void buildSources(File appDir, HashMap<String, Boolean> flags)
throws AndrolibException {
if (! buildSourcesRaw(appDir, flags)
&& ! buildSourcesSmali(appDir, flags)
&& ! buildSourcesJava(appDir, flags)
) {
LOGGER.warning("Could not find sources");
}
}
public boolean buildSourcesRaw(File appDir,
HashMap<String, Boolean> flags) throws AndrolibException {
try {
File working = new File(appDir, "classes.dex");
if (! working.exists()) {
return false;
}
if (flags.get("debug")) {
LOGGER.warning("Debug mode not available.");
}
File stored = new File(appDir, APK_DIRNAME + "/classes.dex");
if (flags.get("forceBuildAll") || isModified(working, stored)) {
LOGGER.info("Copying classes.dex file...");
BrutIO.copyAndClose(new FileInputStream(working),
new FileOutputStream(stored));
}
return true;
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildSourcesSmali(File appDir,
HashMap<String, Boolean> flags) throws AndrolibException {
ExtFile smaliDir = new ExtFile(appDir, "smali");
if (! smaliDir.exists()) {
return false;
}
File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
if (! flags.get("forceBuildAll")) {
LOGGER.info("Checking whether sources has changed...");
}
if (flags.get("forceBuildAll") || isModified(smaliDir, dex)) {
LOGGER.info("Smaling...");
dex.delete();
SmaliBuilder.build(smaliDir, dex, flags);
}
return true;
}
public boolean buildSourcesJava(File appDir,
HashMap<String, Boolean> flags) throws AndrolibException {
File javaDir = new File(appDir, "src");
if (! javaDir.exists()) {
return false;
}
File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
if (! flags.get("forceBuildAll")) {
LOGGER.info("Checking whether sources has changed...");
}
if (flags.get("forceBuildAll") || isModified(javaDir, dex)) {
LOGGER.info("Building java sources...");
dex.delete();
new AndrolibJava().build(javaDir, dex);
}
return true;
}
public void buildResources(ExtFile appDir, HashMap<String, Boolean> flags,
Map<String, Object> usesFramework)
throws AndrolibException {
if (! buildResourcesRaw(appDir, flags)
&& ! buildResourcesFull(appDir, flags, usesFramework)
&& ! buildManifest(appDir, flags, usesFramework)) {
LOGGER.warning("Could not find resources");
}
}
public boolean buildResourcesRaw(ExtFile appDir, HashMap<String, Boolean> flags)
throws AndrolibException {
try {
if (! new File(appDir, "resources.arsc").exists()) {
return false;
}
File apkDir = new File(appDir, APK_DIRNAME);
if (! flags.get("forceBuildAll")) {
LOGGER.info("Checking whether resources has changed...");
}
if (flags.get("forceBuildAll") || isModified(
newFiles(APK_RESOURCES_FILENAMES, appDir),
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
LOGGER.info("Copying raw resources...");
appDir.getDirectory()
.copyToDir(apkDir, APK_RESOURCES_FILENAMES);
}
return true;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildResourcesFull(File appDir, HashMap<String, Boolean> flags,
Map<String, Object> usesFramework)
throws AndrolibException {
try {
if (! new File(appDir, "res").exists()) {
return false;
}
if (! flags.get("forceBuildAll")) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(appDir, APK_DIRNAME);
if (flags.get("forceBuildAll") || isModified(
newFiles(APP_RESOURCES_FILENAMES, appDir),
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
LOGGER.info("Building resources...");
File apkFile = File.createTempFile("APKTOOL", null);
apkFile.delete();
File ninePatch = new File(appDir, "9patch");
if (! ninePatch.exists()) {
ninePatch = null;
}
mAndRes.aaptPackage(
apkFile,
new File(appDir, "AndroidManifest.xml"),
new File(appDir, "res"),
ninePatch, null, parseUsesFramework(usesFramework),
flags
);
Directory tmpDir = new ExtFile(apkFile).getDirectory();
tmpDir.copyToDir(apkDir,
tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES :
APK_RESOURCES_WITHOUT_RES_FILENAMES);
}
return true;
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildManifestRaw(ExtFile appDir, HashMap<String, Boolean> flags)
throws AndrolibException {
try {
File apkDir = new File(appDir, APK_DIRNAME);
LOGGER.info("Copying raw AndroidManifest.xml...");
appDir.getDirectory()
.copyToDir(apkDir, APK_MANIFEST_FILENAMES);
return true;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildManifest(ExtFile appDir, HashMap<String, Boolean> flags,
Map<String, Object> usesFramework)
throws AndrolibException {
try {
if (! new File(appDir, "AndroidManifest.xml").exists()) {
return false;
}
if (! flags.get("forceBuildAll")) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(appDir, APK_DIRNAME);
if (flags.get("forceBuildAll") || isModified(
newFiles(APK_MANIFEST_FILENAMES, appDir),
newFiles(APK_MANIFEST_FILENAMES, apkDir))) {
LOGGER.info("Building AndroidManifest.xml...");
File apkFile = File.createTempFile("APKTOOL", null);
apkFile.delete();
File ninePatch = new File(appDir, "9patch");
if (! ninePatch.exists()) {
ninePatch = null;
}
mAndRes.aaptPackage(
apkFile,
new File(appDir, "AndroidManifest.xml"),
null,
ninePatch, null, parseUsesFramework(usesFramework),
flags
);
Directory tmpDir = new ExtFile(apkFile).getDirectory();
tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES);
}
return true;
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} catch (AndrolibException ex) {
LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file.");
return buildManifestRaw(appDir, flags);
}
}
public void buildLib(File appDir, HashMap<String, Boolean> flags)
throws AndrolibException {
File working = new File(appDir, "lib");
if (! working.exists()) {
return;
}
File stored = new File(appDir, APK_DIRNAME + "/lib");
if (flags.get("forceBuildAll") || isModified(working, stored)) {
LOGGER.info("Copying libs...");
try {
OS.rmdir(stored);
OS.cpdir(working, stored);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
}
public void buildApk(File appDir, File outApk, HashMap<String, Boolean> flags)
throws AndrolibException {
LOGGER.info("Building apk file...");
if (outApk.exists()) {
outApk.delete();
} else {
File outDir = outApk.getParentFile();
if (outDir != null && ! outDir.exists()) {
outDir.mkdirs();
}
}
File assetDir = new File(appDir, "assets");
if (! assetDir.exists()) {
assetDir = null;
}
mAndRes.aaptPackage(outApk, null, null,
new File(appDir, APK_DIRNAME), assetDir, null, flags);
/* check for re-insert */
if (flags.get("injectOriginal")) {
try {
LOGGER.info("Building resources into original apk file...");
ZipFile editOrig = new ZipFile(mOrigApkFile.getAbsoluteFile());
// no compression levels, paras
ZipParameters parameters = new ZipParameters();
parameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
parameters.setCompressionLevel(0);
parameters.setIncludeRootFolder(true);
parameters.setRootFolderInZip("/");
// add res folder
editOrig.addFolder(new File(appDir, APK_DIRNAME + "/res").getAbsolutePath(), parameters);
// add assets, if there
if (assetDir != null) {
//editOrig.addFolder(new File(appDir, APK_DIRNAME + "/assets").getAbsolutePath(), parameters);
}
// add resources.arsc
parameters.setFileNameInZip("resources.arsc");
// editOrig.addFile(new File(appDir, "resources.arsc"), parameters);
} catch(ZipException ex) {
throw new AndrolibException(ex);
}
}
}
public void publicizeResources(File arscFile) throws AndrolibException {
mAndRes.publicizeResources(arscFile);
}
public void installFramework(File frameFile, String tag)
throws AndrolibException {
mAndRes.installFramework(frameFile, tag);
}
public boolean isFrameworkApk(ResTable resTable) {
for (ResPackage pkg : resTable.listMainPackages()) {
if (pkg.getId() < 64) {
return true;
}
}
return false;
}
public static String getVersion() {
String version = ApktoolProperties.get("application.version");
return version.endsWith("-SNAPSHOT") ?
version.substring(0, version.length() - 9) + '.' +
ApktoolProperties.get("git.commit.id.abbrev")
: version;
}
private File[] parseUsesFramework(Map<String, Object> usesFramework)
throws AndrolibException {
if (usesFramework == null) {
return null;
}
List<Integer> ids = (List<Integer>) usesFramework.get("ids");
if (ids == null || ids.isEmpty()) {
return null;
}
String tag = (String) usesFramework.get("tag");
File[] files = new File[ids.size()];
int i = 0;
for (int id : ids) {
files[i++] = mAndRes.getFrameworkApk(id, tag);
}
return files;
}
private boolean isModified(File working, File stored) {
if (! stored.exists()) {
return true;
}
return BrutIO.recursiveModifiedTime(working) >
BrutIO.recursiveModifiedTime(stored);
}
private boolean isModified(File[] working, File[] stored) {
for (int i = 0; i < stored.length; i++) {
if (! stored[i].exists()) {
return true;
}
}
return BrutIO.recursiveModifiedTime(working) >
BrutIO.recursiveModifiedTime(stored);
}
private File[] newFiles(String[] names, File dir) {
File[] files = new File[names.length];
for (int i = 0; i < names.length; i++) {
files[i] = new File(dir, names[i]);
}
return files;
}
public void setApkFile(File apkFile) {
mOrigApkFile = new ExtFile(apkFile);
}
private ExtFile mOrigApkFile = null;
private final static Logger LOGGER =
Logger.getLogger(Androlib.class.getName());
private final static String SMALI_DIRNAME = "smali";
private final static String APK_DIRNAME = "build/apk";
private final static String[] APK_RESOURCES_FILENAMES =
new String[]{"resources.arsc", "AndroidManifest.xml", "res"};
private final static String[] APK_RESOURCES_WITHOUT_RES_FILENAMES =
new String[]{"resources.arsc", "AndroidManifest.xml"};
private final static String[] APP_RESOURCES_FILENAMES =
new String[]{"AndroidManifest.xml", "res"};
private final static String[] APK_MANIFEST_FILENAMES =
new String[]{"AndroidManifest.xml"};
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import brut.common.BrutException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,275 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import brut.androlib.err.InFileNotFoundException;
import brut.androlib.err.OutDirExistsException;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtFile;
import brut.common.BrutException;
import brut.directory.DirectoryException;
import brut.util.OS;
import java.io.File;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ApkDecoder {
public ApkDecoder() {
this(new Androlib());
}
public ApkDecoder(Androlib androlib) {
mAndrolib = androlib;
}
public ApkDecoder(File apkFile) {
this(apkFile, new Androlib());
}
public ApkDecoder(File apkFile, Androlib androlib) {
mAndrolib = androlib;
setApkFile(apkFile);
}
public void setApkFile(File apkFile) {
mApkFile = new ExtFile(apkFile);
mResTable = null;
}
public void setOutDir(File outDir) throws AndrolibException {
mOutDir = outDir;
}
public void decode() throws AndrolibException {
File outDir = getOutDir();
if (! mForceDelete && outDir.exists()) {
throw new OutDirExistsException();
}
if (! mApkFile.isFile() || ! mApkFile.canRead() ) {
throw new InFileNotFoundException();
}
try {
OS.rmdir(outDir);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
outDir.mkdirs();
if (hasSources()) {
switch (mDecodeSources) {
case DECODE_SOURCES_NONE:
mAndrolib.decodeSourcesRaw(mApkFile, outDir, mDebug);
break;
case DECODE_SOURCES_SMALI:
mAndrolib.decodeSourcesSmali(mApkFile, outDir, mDebug, mBakDeb);
break;
case DECODE_SOURCES_JAVA:
mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug);
break;
}
}
if (hasResources()) {
switch (mDecodeResources) {
case DECODE_RESOURCES_NONE:
mAndrolib.decodeResourcesRaw(mApkFile, outDir);
break;
case DECODE_RESOURCES_FULL:
mAndrolib.decodeResourcesFull(mApkFile, outDir,
getResTable());
break;
}
} else {
// if there's no resources.asrc, decode the manifest without looking up
// attribute references
if (hasManifest()) {
switch (mDecodeResources) {
case DECODE_RESOURCES_NONE:
mAndrolib.decodeManifestRaw(mApkFile, outDir);
break;
case DECODE_RESOURCES_FULL:
mAndrolib.decodeManifestFull(mApkFile, outDir,
getResTable());
break;
}
}
}
mAndrolib.decodeRawFiles(mApkFile, outDir);
writeMetaFile();
}
public void setDecodeSources(short mode) throws AndrolibException {
if (mode != DECODE_SOURCES_NONE && mode != DECODE_SOURCES_SMALI
&& mode != DECODE_SOURCES_JAVA) {
throw new AndrolibException("Invalid decode sources mode: " + mode);
}
mDecodeSources = mode;
}
public void setDecodeResources(short mode) throws AndrolibException {
if (mode != DECODE_RESOURCES_NONE && mode != DECODE_RESOURCES_FULL) {
throw new AndrolibException("Invalid decode resources mode");
}
mDecodeResources = mode;
}
public void setDebugMode(boolean debug) {
mDebug = debug;
}
public void setBaksmaliDebugMode(boolean bakdeb) {
mBakDeb = bakdeb;
}
public void setForceDelete(boolean forceDelete) {
mForceDelete = forceDelete;
}
public void setFrameworkTag(String tag) throws AndrolibException {
mFrameTag = tag;
if (mResTable != null) {
getResTable().setFrameTag(tag);
}
}
public void setKeepBrokenResources(boolean keepBrokenResources) {
mKeepBrokenResources = keepBrokenResources;
}
public ResTable getResTable() throws AndrolibException {
if (mResTable == null) {
boolean hasResources = hasResources();
boolean hasManifest = hasManifest();
if (! (hasManifest || hasResources)) {
throw new AndrolibException(
"Apk doesn't contain either AndroidManifest.xml file or resources.arsc file");
}
AndrolibResources.sKeepBroken = mKeepBrokenResources;
mResTable = mAndrolib.getResTable(mApkFile, hasResources);
mResTable.setFrameTag(mFrameTag);
}
return mResTable;
}
public boolean hasSources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasManifest() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasResources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public final static short DECODE_SOURCES_NONE = 0x0000;
public final static short DECODE_SOURCES_SMALI = 0x0001;
public final static short DECODE_SOURCES_JAVA = 0x0002;
public final static short DECODE_RESOURCES_NONE = 0x0100;
public final static short DECODE_RESOURCES_FULL = 0x0101;
private File getOutDir() throws AndrolibException {
if (mOutDir == null) {
throw new AndrolibException("Out dir not set");
}
return mOutDir;
}
private void writeMetaFile() throws AndrolibException {
Map<String, Object> meta = new LinkedHashMap<String, Object>();
meta.put("version", Androlib.getVersion());
meta.put("apkFileName", mApkFile.getName());
if (mDecodeResources != DECODE_RESOURCES_NONE && (hasManifest() || hasResources())) {
meta.put("isFrameworkApk",
Boolean.valueOf(mAndrolib.isFrameworkApk(getResTable())));
putUsesFramework(meta);
putSdkInfo(meta);
}
mAndrolib.writeMetaFile(mOutDir, meta);
}
private void putUsesFramework(Map<String, Object> meta)
throws AndrolibException {
Set<ResPackage> pkgs = getResTable().listFramePackages();
if (pkgs.isEmpty()) {
return;
}
Integer[] ids = new Integer[pkgs.size()];
int i = 0;
for (ResPackage pkg : pkgs) {
ids[i++] = pkg.getId();
}
Arrays.sort(ids);
Map<String, Object> uses = new LinkedHashMap<String, Object>();
uses.put("ids", ids);
if (mFrameTag != null) {
uses.put("tag", mFrameTag);
}
meta.put("usesFramework", uses);
}
private void putSdkInfo(Map<String, Object> meta)
throws AndrolibException {
Map<String, String> info = getResTable().getSdkInfo();
if (info.size() > 0) {
meta.put("sdkInfo", info);
}
}
private final Androlib mAndrolib;
private ExtFile mApkFile;
private File mOutDir;
private ResTable mResTable;
private short mDecodeSources = DECODE_SOURCES_SMALI;
private short mDecodeResources = DECODE_RESOURCES_FULL;
private boolean mDebug = false;
private boolean mForceDelete = false;
private String mFrameTag;
private boolean mKeepBrokenResources = false;
private boolean mBakDeb = true;
}

View File

@ -0,0 +1,76 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Logger;
import org.jf.baksmali.baksmali;
import org.jf.smali.main;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ApktoolProperties {
public static String get(String key) {
return get().getProperty(key);
}
public static Properties get() {
if (sProps == null) {
loadProps();
}
return sProps;
}
private static void loadProps() {
InputStream in = ApktoolProperties.class
.getResourceAsStream("apktool.properties");
sProps = new Properties();
try {
sProps.load(in);
in.close();
} catch (IOException ex) {
LOGGER.warning("Can't load properties.");
}
InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("properties/baksmali.properties");
Properties properties = new Properties();
String version = "(unknown)";
try {
properties.load(templateStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
}
sProps.put("baksmaliVersion", version);
templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
properties = new Properties();
version = "(unknown)";
try {
properties.load(templateStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
}
sProps.put("smaliVersion", version);
}
private static Properties sProps;
private static final Logger LOGGER =
Logger.getLogger(ApktoolProperties.class.getName());
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.err;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class CantFind9PatchChunk extends AndrolibException {
public CantFind9PatchChunk(Throwable cause) {
super(cause);
}
public CantFind9PatchChunk(String message, Throwable cause) {
super(message, cause);
}
public CantFind9PatchChunk(String message) {
super(message);
}
public CantFind9PatchChunk() {
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.err;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class CantFindFrameworkResException extends AndrolibException {
public CantFindFrameworkResException(Throwable cause, int id) {
super(cause);
mPkgId = id;
}
public CantFindFrameworkResException(int id) {
mPkgId = id;
}
public int getPkgId() {
return mPkgId;
}
private final int mPkgId;
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.err;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class InFileNotFoundException extends AndrolibException {
public InFileNotFoundException(Throwable cause) {
super(cause);
}
public InFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public InFileNotFoundException(String message) {
super(message);
}
public InFileNotFoundException() {
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.err;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class OutDirExistsException extends AndrolibException {
public OutDirExistsException(Throwable cause) {
super(cause);
}
public OutDirExistsException(String message, Throwable cause) {
super(message, cause);
}
public OutDirExistsException(String message) {
super(message);
}
public OutDirExistsException() {
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.err;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class UndefinedResObject extends AndrolibException {
public UndefinedResObject(Throwable cause) {
super(cause);
}
public UndefinedResObject(String message, Throwable cause) {
super(message, cause);
}
public UndefinedResObject(String message) {
super(message);
}
public UndefinedResObject() {
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.java;
import brut.androlib.res.util.ExtFile;
import java.io.File;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class AndrolibJava {
public void decode(ExtFile apkFile, File outDir) {
throw new UnsupportedOperationException("Not yet implemented");
}
public void build(File javaDir, File dex) {
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.mod;
import java.io.Writer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class IndentingWriter extends org.jf.util.IndentingWriter {
public IndentingWriter(Writer writer) {
super(writer);
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.mod;
import java.io.*;
import org.antlr.runtime.*;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.jf.dexlib.DexFile;
import org.jf.smali.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class SmaliMod {
public static boolean assembleSmaliFile(InputStream smaliStream,
String name, DexFile dexFile, boolean verboseErrors,
boolean oldLexer, boolean printTokens)
throws IOException, RecognitionException {
CommonTokenStream tokens;
boolean lexerErrors = false;
LexerErrorInterface lexer;
if (oldLexer) {
ANTLRInputStream input = new ANTLRInputStream(smaliStream, "UTF-8");
input.name = name;
lexer = new smaliLexer(input);
tokens = new CommonTokenStream((TokenSource)lexer);
} else {
InputStreamReader reader =
new InputStreamReader(smaliStream, "UTF-8");
lexer = new smaliFlexLexer(reader);
tokens = new CommonTokenStream((TokenSource)lexer);
}
if (printTokens) {
tokens.getTokens();
for (int i=0; i<tokens.size(); i++) {
Token token = tokens.get(i);
if (token.getChannel() == smaliLexer.HIDDEN) {
continue;
}
System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
}
}
smaliParser parser = new smaliParser(tokens);
parser.setVerboseErrors(verboseErrors);
smaliParser.smali_file_return result = parser.smali_file();
if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
return false;
}
CommonTree t = (CommonTree) result.getTree();
CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
treeStream.setTokenStream(tokens);
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
dexGen.dexFile = dexFile;
dexGen.smali_file();
if (dexGen.getNumberOfSyntaxErrors() > 0) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,583 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res;
import brut.androlib.AndrolibException;
import brut.androlib.err.CantFindFrameworkResException;
import brut.androlib.res.data.*;
import brut.androlib.res.decoder.*;
import brut.androlib.res.decoder.ARSCDecoder.ARSCData;
import brut.androlib.res.decoder.ARSCDecoder.FlagsOffset;
import brut.androlib.res.util.*;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.common.BrutException;
import brut.directory.*;
import brut.util.*;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.zip.*;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
final public class AndrolibResources {
public ResTable getResTable(ExtFile apkFile) throws AndrolibException {
return getResTable(apkFile, true);
}
public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg) throws AndrolibException {
ResTable resTable = new ResTable(this);
if (loadMainPkg) {
loadMainPkg(resTable, apkFile);
}
return resTable;
}
public ResPackage loadMainPkg(ResTable resTable, ExtFile apkFile)
throws AndrolibException {
LOGGER.info("Loading resource table...");
ResPackage[] pkgs = getResPackagesFromApk(
apkFile, resTable, sKeepBroken);
ResPackage pkg = null;
switch (pkgs.length) {
case 1:
pkg = pkgs[0];
break;
case 2:
if (pkgs[0].getName().equals("android")) {
LOGGER.warning("Skipping \"android\" package group");
pkg = pkgs[1];
} else if (pkgs[0].getName().equals("com.htc")) {
LOGGER.warning("Skipping \"htc\" stupid package group");
pkg = pkgs[1];
}
break;
}
if (pkg == null) {
throw new AndrolibException(
"Arsc files with zero or multiple packages");
}
resTable.addPackage(pkg, true);
LOGGER.info("Loaded.");
return pkg;
}
public ResPackage loadFrameworkPkg(ResTable resTable, int id,
String frameTag) throws AndrolibException {
File apk = getFrameworkApk(id, frameTag);
LOGGER.info("Loading resource table from file: " + apk);
ResPackage[] pkgs = getResPackagesFromApk(
new ExtFile(apk), resTable, true);
if (pkgs.length != 1) {
throw new AndrolibException(
"Arsc files with zero or multiple packages");
}
ResPackage pkg = pkgs[0];
if (pkg.getId() != id) {
throw new AndrolibException("Expected pkg of id: " +
String.valueOf(id) + ", got: " + pkg.getId());
}
resTable.addPackage(pkg, false);
LOGGER.info("Loaded.");
return pkg;
}
public void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir)
throws AndrolibException {
Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
// Set ResAttrDecoder
duo.m2.setAttrDecoder(new ResAttrDecoder());
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
// Fake ResPackage
attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null));
Directory inApk, out;
try {
inApk = apkFile.getDirectory();
out = new FileDirectory(outDir);
LOGGER.info("Decoding AndroidManifest.xml with only framework resources...");
fileDecoder.decodeManifest(
inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decode(ResTable resTable, ExtFile apkFile, File outDir)
throws AndrolibException {
Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
attrDecoder.setCurrentPackage(
resTable.listMainPackages().iterator().next());
Directory inApk, in = null, out;
try {
inApk = apkFile.getDirectory();
out = new FileDirectory(outDir);
LOGGER.info("Decoding AndroidManifest.xml with resources...");
fileDecoder.decodeManifest(
inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");
if (inApk.containsDir("res")) {
in = inApk.getDir("res");
}
out = out.createDir("res");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
ExtMXSerializer xmlSerializer = getResXmlSerializer();
for (ResPackage pkg : resTable.listMainPackages()) {
attrDecoder.setCurrentPackage(pkg);
LOGGER.info("Decoding file-resources...");
for (ResResource res : pkg.listFiles()) {
fileDecoder.decode(res, in, out);
}
LOGGER.info("Decoding values */* XMLs...");
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
generateValuesFile(valuesFile, out, xmlSerializer);
}
generatePublicXml(pkg, out, xmlSerializer);
LOGGER.info("Done.");
}
AndrolibException decodeError = duo.m2.getFirstError();
if (decodeError != null) {
throw decodeError;
}
}
public void setSdkInfo(Map<String, String> map) {
if(map != null) {
mMinSdkVersion = map.get("minSdkVersion");
mTargetSdkVersion = map.get("targetSdkVersion");
mMaxSdkVersion = map.get("maxSdkVersion");
}
}
public void aaptPackage(File apkFile, File manifest, File resDir,
File rawDir, File assetDir, File[] include, HashMap<String, Boolean> flags)
throws AndrolibException {
List<String> cmd = new ArrayList<String>();
cmd.add("aapt");
cmd.add("p");
if (flags.get("verbose")) {
cmd.add("-v");
}
if (flags.get("update")) {
cmd.add("-u");
}
if (mMinSdkVersion != null) {
cmd.add("--min-sdk-version");
cmd.add(mMinSdkVersion);
}
if (mTargetSdkVersion != null) {
cmd.add("--target-sdk-version");
cmd.add(mTargetSdkVersion);
}
if (mMaxSdkVersion != null) {
cmd.add("--max-sdk-version");
cmd.add(mMaxSdkVersion);
}
cmd.add("-F");
cmd.add(apkFile.getAbsolutePath());
if (flags.get("framework")) {
cmd.add("-x");
// cmd.add("-0");
// cmd.add("arsc");
}
if (include != null) {
for (File file : include) {
cmd.add("-I");
cmd.add(file.getPath());
}
}
if (resDir != null) {
cmd.add("-S");
cmd.add(resDir.getAbsolutePath());
}
if (manifest != null) {
cmd.add("-M");
cmd.add(manifest.getAbsolutePath());
}
if (assetDir != null) {
cmd.add("-A");
cmd.add(assetDir.getAbsolutePath());
}
if (rawDir != null) {
cmd.add(rawDir.getAbsolutePath());
}
try {
OS.exec(cmd.toArray(new String[0]));
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
public boolean detectWhetherAppIsFramework(File appDir)
throws AndrolibException {
File publicXml = new File(appDir, "res/values/public.xml");
if (! publicXml.exists()) {
return false;
}
Iterator<String> it;
try {
it = IOUtils.lineIterator(
new FileReader(new File(appDir, "res/values/public.xml")));
} catch (FileNotFoundException ex) {
throw new AndrolibException(
"Could not detect whether app is framework one", ex);
}
it.next();
it.next();
return it.next().contains("0x01");
}
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 Duo<ResFileDecoder, AXmlResourceParser> getResFileDecoder() {
ResStreamDecoderContainer decoders =
new ResStreamDecoderContainer();
decoders.setDecoder("raw", new ResRawStreamDecoder());
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
AXmlResourceParser axmlParser = new AXmlResourceParser();
axmlParser.setAttrDecoder(new ResAttrDecoder());
decoders.setDecoder("xml",
new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
return new Duo<ResFileDecoder, AXmlResourceParser>(
new ResFileDecoder(decoders), axmlParser);
}
public Duo<ResFileDecoder, AXmlResourceParser> getManifestFileDecoder() {
ResStreamDecoderContainer decoders =
new ResStreamDecoderContainer();
AXmlResourceParser axmlParser = new AXmlResourceParser();
decoders.setDecoder("xml",
new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
return new Duo<ResFileDecoder, AXmlResourceParser>(
new ResFileDecoder(decoders), axmlParser);
}
public ExtMXSerializer getResXmlSerializer() {
ExtMXSerializer serial = new ExtMXSerializer();
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION
, " ");
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR,
System.getProperty("line.separator"));
serial.setProperty(ExtMXSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8");
serial.setDisabledAttrEscape(true);
return serial;
}
private void generateValuesFile(ResValuesFile valuesFile, Directory out,
ExtXmlSerializer serial) throws AndrolibException {
try {
OutputStream outStream = out.getFileOutput(valuesFile.getPath());
serial.setOutput((outStream), null);
serial.startDocument(null, null);
serial.startTag(null, "resources");
for (ResResource res : valuesFile.listResources()) {
if (valuesFile.isSynthesized(res)) {
continue;
}
((ResValuesXmlSerializable) res.getValue())
.serializeToResValuesXml(serial, res);
}
serial.endTag(null, "resources");
serial.newLine();
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 generatePublicXml(ResPackage pkg, Directory out,
XmlSerializer serial) throws AndrolibException {
// try {
// OutputStream outStream = out.getFileOutput("values/public.xml");
// serial.setOutput(outStream, null);
// serial.startDocument(null, null);
// serial.startTag(null, "resources");
//
// for (ResResSpec spec : pkg.listResSpecs()) {
// serial.startTag(null, "public");
// serial.attribute(null, "type", spec.getType().getName());
// serial.attribute(null, "name", spec.getName());
// serial.attribute(null, "id", String.format(
// "0x%08x", spec.getId().id));
// serial.endTag(null, "public");
// }
//
// serial.endTag(null, "resources");
// serial.endDocument();
// serial.flush();
// outStream.close();
// } catch (IOException ex) {
// throw new AndrolibException(
// "Could not generate public.xml file", ex);
// } catch (DirectoryException ex) {
// throw new AndrolibException(
// "Could not generate public.xml file", ex);
// }
}
private ResPackage[] getResPackagesFromApk(ExtFile apkFile,
ResTable resTable, boolean keepBroken) throws AndrolibException {
try {
return ARSCDecoder.decode(
apkFile.getDirectory().getFileInput("resources.arsc"), false,
keepBroken, resTable).getPackages();
} catch (DirectoryException ex) {
throw new AndrolibException(
"Could not load resources.arsc from file: " + apkFile, ex);
}
}
public File getFrameworkApk(int id, String frameTag)
throws AndrolibException {
File dir = getFrameworkDir();
File apk;
if (frameTag != null) {
apk = new File(dir, String.valueOf(id) + '-' + frameTag + ".apk");
if (apk.exists()) {
return apk;
}
}
apk = new File(dir, String.valueOf(id) + ".apk");
if (apk.exists()) {
return apk;
}
if (id == 1) {
InputStream in = null;
OutputStream out = null;
try {
in = AndrolibResources.class.getResourceAsStream(
"/brut/androlib/android-framework.jar");
out = new FileOutputStream(apk);
IOUtils.copy(in, out);
return apk;
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {}
}
}
}
throw new CantFindFrameworkResException(id);
}
public void installFramework(File frameFile, String tag)
throws AndrolibException {
InputStream in = null;
ZipOutputStream out = null;
try {
ZipFile zip = new ZipFile(frameFile);
ZipEntry entry = zip.getEntry("resources.arsc");
if (entry == null) {
throw new AndrolibException("Can't find resources.arsc file");
}
in = zip.getInputStream(entry);
byte[] data = IOUtils.toByteArray(in);
ARSCData arsc = ARSCDecoder.decode(
new ByteArrayInputStream(data), true, true);
publicizeResources(data, arsc.getFlagsOffsets());
File outFile = new File(getFrameworkDir(),
String.valueOf(arsc.getOnePackage().getId()) +
(tag == null ? "" : '-' + tag) + ".apk");
out = new ZipOutputStream(new FileOutputStream(outFile));
out.setMethod(ZipOutputStream.STORED);
CRC32 crc = new CRC32();
crc.update(data);
entry = new ZipEntry("resources.arsc");
entry.setSize(data.length);
entry.setCrc(crc.getValue());
out.putNextEntry(entry);
out.write(data);
LOGGER.info("Framework installed to: " + outFile);
} catch (ZipException ex) {
throw new AndrolibException(ex);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {}
}
}
}
public void publicizeResources(File arscFile) throws AndrolibException {
byte[] data = new byte[(int) arscFile.length()];
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(arscFile);
in.read(data);
publicizeResources(data);
out = new FileOutputStream(arscFile);
out.write(data);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {}
}
}
}
public void publicizeResources(byte[] arsc) throws AndrolibException {
publicizeResources(arsc,
ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true)
.getFlagsOffsets());
}
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets)
throws AndrolibException {
for (FlagsOffset flags : flagsOffsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while(offset < end) {
arsc[offset] |= (byte) 0x40;
offset += 4;
}
}
}
private File getFrameworkDir() throws AndrolibException {
String path;
/* store in user-home, for Mac OS X */
if (System.getProperty("os.name").equals("Mac OS X")) {
path = System.getProperty("user.home") + File.separatorChar +
"Library/Application Support/apktool/framework"; }
else {
path = System.getProperty("user.home") + File.separatorChar +
"apktool" + File.separatorChar + "framework";
}
File dir = new File(path);
if (! dir.exists()) {
if (! dir.mkdirs()) {
throw new AndrolibException("Can't create directory: " + dir);
}
}
return dir;
}
public File getAndroidResourcesFile() throws AndrolibException {
try {
return Jar.getResourceAsFile("/brut/androlib/android-framework.jar");
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
// TODO: dirty static hack. I have to refactor decoding mechanisms.
public static boolean sKeepBroken = false;
private final static Logger LOGGER =
Logger.getLogger(AndrolibResources.class.getName());
private String mMinSdkVersion = null;
private String mMaxSdkVersion = null;
private String mTargetSdkVersion = null;
}

View File

@ -0,0 +1,167 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
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.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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 {
tagResIdsForFile(resTable, dir, fileName);
} 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<String> 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 void tagResIdsForFile(ResTable resTable, Directory dir,
String fileName) throws IOException, DirectoryException,
AndrolibException {
Iterator<String> 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) {
try {
ResResSpec spec = resTable.getResSpec(resID);
out.println(String.format(
RES_NAME_FORMAT, spec.getFullName()));
} catch (UndefinedResObject ex) {
if (! R_FILE_PATTERN.matcher(fileName).matches()) {
LOGGER.warning(String.format(
"Undefined resource spec in %s: 0x%08x"
, fileName, resID));
}
}
}
}
out.println(line);
}
out.close();
}
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._]+)$");
private final static Pattern R_FILE_PATTERN = Pattern.compile(
".*R\\$[a-z]+\\.smali$");
private final static Logger LOGGER =
Logger.getLogger(ResSmaliUpdater.class.getName());
}

View File

@ -0,0 +1,74 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResConfig {
private final ResConfigFlags mFlags;
private final Map<ResResSpec, ResResource> mResources =
new LinkedHashMap<ResResSpec, ResResource>();
public ResConfig(ResConfigFlags flags) {
this.mFlags = flags;
}
public Set<ResResource> listResources() {
return new LinkedHashSet<ResResource>(mResources.values());
}
public ResResource getResource(ResResSpec spec) throws AndrolibException {
ResResource res = mResources.get(spec);
if (res == null) {
throw new UndefinedResObject(String.format(
"resource: spec=%s, config=%s", spec, this));
}
return res;
}
public Set<ResResSpec> listResSpecs() {
return mResources.keySet();
}
public ResConfigFlags getFlags() {
return mFlags;
}
public void addResource(ResResource res)
throws AndrolibException {
addResource(res, false);
}
public void addResource(ResResource res, boolean overwrite)
throws AndrolibException {
ResResSpec spec = res.getResSpec();
if (mResources.put(spec, res) != null && ! overwrite) {
throw new AndrolibException(String.format(
"Multiple resources: spec=%s, config=%s", spec, this));
}
}
@Override
public String toString() {
return mFlags.toString();
}
}

View File

@ -0,0 +1,443 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import java.util.logging.Logger;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResConfigFlags {
public final short mcc;
public final short mnc;
public final char[] language;
public final char[] country;
public final byte orientation;
public final byte touchscreen;
public final short density;
public final byte keyboard;
public final byte navigation;
public final byte inputFlags;
public final short screenWidth;
public final short screenHeight;
public final short sdkVersion;
public final byte screenLayout;
public final byte uiMode;
public final short smallestScreenWidthDp;
public final short screenWidthDp;
public final short screenHeightDp;
public final boolean isInvalid;
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;
sdkVersion = 0;
screenLayout = SCREENLONG_ANY | SCREENSIZE_ANY;
uiMode = UI_MODE_TYPE_ANY | UI_MODE_NIGHT_ANY;
smallestScreenWidthDp = 0;
screenWidthDp = 0;
screenHeightDp = 0;
isInvalid = false;
mQualifiers = "";
}
public ResConfigFlags(short mcc, short mnc, char[] language, char[] country,
byte orientation, byte touchscreen, short density, byte keyboard,
byte navigation, byte inputFlags, short screenWidth,
short screenHeight, short sdkVersion, byte screenLayout,
byte uiMode, short smallestScreenWidthDp, short screenWidthDp,
short screenHeightDp, boolean isInvalid) {
if (orientation < 0 || orientation > 3) {
LOGGER.warning("Invalid orientation value: " + orientation);
orientation = 0;
isInvalid = true;
}
if (touchscreen < 0 || touchscreen > 3) {
LOGGER.warning("Invalid touchscreen value: " + touchscreen);
touchscreen = 0;
isInvalid = true;
}
if (density < -1) {
LOGGER.warning("Invalid density value: " + density);
density = 0;
isInvalid = true;
}
if (keyboard < 0 || keyboard > 3) {
LOGGER.warning("Invalid keyboard value: " + keyboard);
keyboard = 0;
isInvalid = true;
}
if (navigation < 0 || navigation > 4) {
LOGGER.warning("Invalid navigation value: " + navigation);
navigation = 0;
isInvalid = true;
}
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.sdkVersion = sdkVersion;
this.screenLayout = screenLayout;
this.uiMode = uiMode;
this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
this.isInvalid = isInvalid;
mQualifiers = generateQualifiers();
}
public String getQualifiers() {
return mQualifiers;
}
private String generateQualifiers() {
StringBuilder ret = new StringBuilder();
if (mcc != 0) {
ret.append("-mcc").append(String.format("%03d", 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);
}
}
if (smallestScreenWidthDp != 0) {
ret.append("-sw").append(smallestScreenWidthDp).append("dp");
}
if (screenWidthDp != 0) {
ret.append("-w").append(screenWidthDp).append("dp");
}
if (screenHeightDp != 0) {
ret.append("-h").append(screenHeightDp).append("dp");
}
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;
case SCREENSIZE_XLARGE:
ret.append("-xlarge");
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 (uiMode & MASK_UI_MODE_TYPE) {
case UI_MODE_TYPE_CAR:
ret.append("-car");
break;
case UI_MODE_TYPE_DESK:
ret.append("-desk");
break;
case UI_MODE_TYPE_TELEVISION:
ret.append("-television");
break;
case UI_MODE_TYPE_SMALLUI:
ret.append("-smallui");
break;
case UI_MODE_TYPE_MEDIUMUI:
ret.append("-mediumui");
break;
case UI_MODE_TYPE_LARGEUI:
ret.append("-largeui");
break;
case UI_MODE_TYPE_HUGEUI:
ret.append("-hugeui");
break;
case UI_MODE_TYPE_APPLIANCE:
ret.append("-appliance");
break;
}
switch (uiMode & MASK_UI_MODE_NIGHT) {
case UI_MODE_NIGHT_YES:
ret.append("-night");
break;
case UI_MODE_NIGHT_NO:
ret.append("-notnight");
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_TV:
ret.append("-tvdpi");
break;
case DENSITY_XHIGH:
ret.append("-xhdpi");
break;
case DENSITY_XXHIGH:
ret.append("-xxhdpi");
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 > getNaturalSdkVersionRequirement()) {
ret.append("-v").append(sdkVersion);
}
if (isInvalid) {
ret.append("-ERR" + sErrCounter++);
}
return ret.toString();
}
private short getNaturalSdkVersionRequirement() {
if (smallestScreenWidthDp != 0 || screenWidthDp != 0
|| screenHeightDp != 0) {
return 13;
}
if ((uiMode & (MASK_UI_MODE_TYPE | MASK_UI_MODE_NIGHT)) != 0) {
return 8;
}
if ((screenLayout & (MASK_SCREENSIZE | MASK_SCREENLONG)) != 0
|| density != DENSITY_DEFAULT) {
return 4;
}
return 0;
}
@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;
return this.mQualifiers.equals(other.mQualifiers);
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + this.mQualifiers.hashCode();
return hash;
}
// TODO: Dirty static hack. This counter should be a part of ResPackage,
// but it would be hard right now and this feature is very rarely used.
private static int sErrCounter = 0;
public final static byte ORIENTATION_ANY = 0;
public final static byte ORIENTATION_PORT = 1;
public final static byte ORIENTATION_LAND = 2;
public final static byte ORIENTATION_SQUARE = 3;
public final static byte TOUCHSCREEN_ANY = 0;
public final static byte TOUCHSCREEN_NOTOUCH = 1;
public final static byte TOUCHSCREEN_STYLUS = 2;
public final static byte TOUCHSCREEN_FINGER = 3;
public final static short DENSITY_DEFAULT = 0;
public final static short DENSITY_LOW = 120;
public final static short DENSITY_MEDIUM = 160;
public final static short DENSITY_TV = 213;
public final static short DENSITY_HIGH = 240;
public final static short DENSITY_XHIGH = 320;
public final static short DENSITY_XXHIGH = 480;
public final static short DENSITY_NONE = -1;
public final static byte KEYBOARD_ANY = 0;
public final static byte KEYBOARD_NOKEYS = 1;
public final static byte KEYBOARD_QWERTY = 2;
public final static byte KEYBOARD_12KEY = 3;
public final static byte NAVIGATION_ANY = 0;
public final static byte NAVIGATION_NONAV = 1;
public final static byte NAVIGATION_DPAD = 2;
public final static byte NAVIGATION_TRACKBALL = 3;
public final static byte NAVIGATION_WHEEL = 4;
public final static byte MASK_KEYSHIDDEN = 0x3;
public final static byte KEYSHIDDEN_ANY = 0x0;
public final static byte KEYSHIDDEN_NO = 0x1;
public final static byte KEYSHIDDEN_YES = 0x2;
public final static byte KEYSHIDDEN_SOFT = 0x3;
public final static byte MASK_NAVHIDDEN = 0xc;
public final static byte NAVHIDDEN_ANY = 0x0;
public final static byte NAVHIDDEN_NO = 0x4;
public final static byte NAVHIDDEN_YES = 0x8;
public final static byte MASK_SCREENSIZE = 0x0f;
public final static byte SCREENSIZE_ANY = 0x00;
public final static byte SCREENSIZE_SMALL = 0x01;
public final static byte SCREENSIZE_NORMAL = 0x02;
public final static byte SCREENSIZE_LARGE = 0x03;
public final static byte SCREENSIZE_XLARGE = 0x04;
public final static byte MASK_SCREENLONG = 0x30;
public final static byte SCREENLONG_ANY = 0x00;
public final static byte SCREENLONG_NO = 0x10;
public final static byte SCREENLONG_YES = 0x20;
public final static byte MASK_UI_MODE_TYPE = 0x0f;
public final static byte UI_MODE_TYPE_ANY = 0x00;
public final static byte UI_MODE_TYPE_NORMAL = 0x01;
public final static byte UI_MODE_TYPE_DESK = 0x02;
public final static byte UI_MODE_TYPE_CAR = 0x03;
public final static byte UI_MODE_TYPE_TELEVISION = 0x04;
public final static byte UI_MODE_TYPE_APPLIANCE = 0x05;
public final static byte UI_MODE_TYPE_SMALLUI = 0x0c;
public final static byte UI_MODE_TYPE_MEDIUMUI = 0x0d;
public final static byte UI_MODE_TYPE_LARGEUI = 0x0e;
public final static byte UI_MODE_TYPE_HUGEUI = 0x0f;
public final static byte MASK_UI_MODE_NIGHT = 0x30;
public final static byte UI_MODE_NIGHT_ANY = 0x00;
public final static byte UI_MODE_NIGHT_NO = 0x10;
public final static byte UI_MODE_NIGHT_YES = 0x20;
private static final Logger LOGGER =
Logger.getLogger(ResConfigFlags.class.getName());
}

View File

@ -0,0 +1,70 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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 = 17;
hash = 31 * 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;
}
}

View File

@ -0,0 +1,220 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.res.data.value.ResFileValue;
import brut.androlib.res.data.value.ResValueFactory;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResPackage {
private final ResTable mResTable;
private final int mId;
private final String mName;
private final Map<ResID, ResResSpec> mResSpecs =
new LinkedHashMap<ResID, ResResSpec>();
private final Map<ResConfigFlags, ResConfig> mConfigs =
new LinkedHashMap<ResConfigFlags, ResConfig>();
private final Map<String, ResType> mTypes =
new LinkedHashMap<String, ResType>();
private final Set<ResID> mSynthesizedRes = new HashSet<ResID>();
private ResValueFactory mValueFactory;
public ResPackage(ResTable resTable, int id, String name) {
this.mResTable = resTable;
this.mId = id;
this.mName = name;
}
public List<ResResSpec> listResSpecs() {
return new ArrayList<ResResSpec>(mResSpecs.values());
}
public boolean hasResSpec(ResID resID) {
return mResSpecs.containsKey(resID);
}
public ResResSpec getResSpec(ResID resID) throws UndefinedResObject {
ResResSpec spec = mResSpecs.get(resID);
if (spec == null) {
throw new UndefinedResObject("resource spec: " + resID.toString());
}
return spec;
}
public List<ResConfig> getConfigs() {
return new ArrayList<ResConfig>(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 UndefinedResObject("config: " + flags);
}
return config;
}
public ResConfig getOrCreateConfig(ResConfigFlags flags)
throws AndrolibException {
ResConfig config = mConfigs.get(flags);
if (config == null) {
config = new ResConfig(flags);
mConfigs.put(flags, config);
}
return config;
}
public List<ResType> listTypes() {
return new ArrayList<ResType>(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 UndefinedResObject("type: " + typeName);
}
return type;
}
public Set<ResResource> listFiles() {
Set<ResResource> ret = new HashSet<ResResource>();
for (ResResSpec spec : mResSpecs.values()) {
for (ResResource res : spec.listResources()) {
if (res.getValue() instanceof ResFileValue) {
ret.add(res);
}
}
}
return ret;
}
public Collection<ResValuesFile> listValuesFiles() {
Map<Duo<ResType, ResConfig>, ResValuesFile> ret =
new HashMap<Duo<ResType, ResConfig>, ResValuesFile>();
for (ResResSpec spec : mResSpecs.values()) {
for (ResResource res : spec.listResources()) {
if (res.getValue() instanceof ResValuesXmlSerializable) {
ResType type = res.getResSpec().getType();
ResConfig config = res.getConfig();
Duo<ResType, ResConfig> key =
new Duo<ResType, ResConfig>(type, config);
ResValuesFile values = ret.get(key);
if (values == null) {
values = new ResValuesFile(this, type, config);
ret.put(key, values);
}
values.addResource(res);
}
}
}
return ret.values();
}
public ResTable getResTable() {
return mResTable;
}
public int getId() {
return mId;
}
public String getName() {
return mName;
}
boolean isSynthesized(ResID resId) {
return mSynthesizedRes.contains(resId);
}
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) {
}
public void addSynthesizedRes(int resId) {
mSynthesizedRes.add(new ResID(resId));
}
@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 = 17;
hash = 31 * hash + (this.mResTable != null ? this.mResTable.hashCode() : 0);
hash = 31 * hash + this.mId;
return hash;
}
public ResValueFactory getValueFactory() {
if (mValueFactory == null) {
mValueFactory = new ResValueFactory(this);
}
return mValueFactory;
}
}

View File

@ -0,0 +1,125 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResResSpec {
private final ResID mId;
private final String mName;
private final ResPackage mPackage;
private final ResType mType;
private final Map<ResConfigFlags, ResResource> mResources =
new LinkedHashMap<ResConfigFlags, ResResource>();
public ResResSpec(ResID id, String name, ResPackage pkg, ResType type) {
this.mId = id;
this.mName = name;
this.mPackage = pkg;
this.mType = type;
}
public Set<ResResource> listResources() {
return new LinkedHashSet<ResResource>(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 UndefinedResObject(String.format(
"resource: spec=%s, config=%s", this, config));
}
return res;
}
public boolean hasResource(ResConfig config) {
return hasResource(config.getFlags());
}
private boolean hasResource(ResConfigFlags flags) {
return mResources.containsKey(flags);
}
public ResResource getDefaultResource() throws AndrolibException {
return getResource(new ResConfigFlags());
}
public boolean hasDefaultResource() {
return mResources.containsKey(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 StringUtils.replace(mName, "\"", "q");
}
public ResPackage getPackage() {
return mPackage;
}
public ResType getType() {
return mType;
}
public void addResource(ResResource res)
throws AndrolibException {
addResource(res, false);
}
public void addResource(ResResource res, boolean overwrite)
throws AndrolibException {
ResConfigFlags flags = res.getConfig().getFlags();
if (mResources.put(flags, res) != null && ! overwrite) {
throw new AndrolibException(String.format("Multiple resources: spec=%s, config=%s", this, flags));
}
}
@Override
public String toString() {
return mId.toString() + " " + mType.toString() + "/" + mName;
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.value.ResValue;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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;
}
public void replace(ResValue value) throws AndrolibException {
ResResource res = new ResResource(mConfig, mResSpec, value);
mConfig.addResource(res, true);
mResSpec.addResource(res, true);
}
@Override
public String toString() {
return getFilePath();
}
}

View File

@ -0,0 +1,137 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.value.ResValue;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResTable {
private final AndrolibResources mAndRes;
private final Map<Integer, ResPackage> mPackagesById =
new HashMap<Integer, ResPackage>();
private final Map<String, ResPackage> mPackagesByName =
new HashMap<String, ResPackage>();
private final Set<ResPackage> mMainPackages =
new LinkedHashSet<ResPackage>();
private final Set<ResPackage> mFramePackages =
new LinkedHashSet<ResPackage>();
private String mFrameTag;
private Map<String, String> mSdkInfo = new LinkedHashMap<String, String>();
public ResTable() {
mAndRes = null;
}
public ResTable(AndrolibResources andRes) {
mAndRes = andRes;
}
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<ResPackage> listMainPackages() {
return mMainPackages;
}
public Set<ResPackage> listFramePackages() {
return mFramePackages;
}
public ResPackage getPackage(int id) throws AndrolibException {
ResPackage pkg = mPackagesById.get(id);
if (pkg != null) {
return pkg;
}
if (mAndRes != null) {
return mAndRes.loadFrameworkPkg(this, id, mFrameTag);
}
throw new UndefinedResObject(String.format("package: id=%d", id));
}
public ResPackage getPackage(String name) throws AndrolibException {
ResPackage pkg = mPackagesByName.get(name);
if (pkg == null) {
throw new UndefinedResObject("package: name=" + name);
}
return pkg;
}
public boolean hasPackage(int id) {
return mPackagesById.containsKey(id);
}
public boolean hasPackage(String name) {
return mPackagesByName.containsKey(name);
}
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);
} else {
mFramePackages.add(pkg);
}
}
public void setFrameTag(String tag) {
mFrameTag = tag;
}
public Map<String, String> getSdkInfo() {
return mSdkInfo;
}
public void addSdkInfo(String key, String value) {
mSdkInfo.put(key, value);
}
public void clearSdkInfo() {
mSdkInfo.clear();
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObject;
import java.util.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public final class ResType {
private final String mName;
private final Map<String, ResResSpec> mResSpecs =
new LinkedHashMap<String, ResResSpec>();
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<ResResSpec> listResSpecs() {
return new LinkedHashSet<ResResSpec>(mResSpecs.values());
}
public ResResSpec getResSpec(String name) throws AndrolibException {
ResResSpec spec = mResSpecs.get(name);
if (spec == null) {
throw new UndefinedResObject(String.format(
"resource spec: %s/%s", getName(), name));
}
return spec;
}
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;
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResValuesFile {
private final ResPackage mPackage;
private final ResType mType;
private final ResConfig mConfig;
private final Set<ResResource> mResources =
new LinkedHashSet<ResResource>();
public ResValuesFile(ResPackage pkg, ResType type, ResConfig config) {
this.mPackage = pkg;
this.mType = type;
this.mConfig = config;
}
public String getPath() {
return "values" + mConfig.getFlags().getQualifiers()
+ "/" + mType.getName()
+ (mType.getName().endsWith("s") ? "" : "s")
+ ".xml";
}
public Set<ResResource> listResources() {
return mResources;
}
public ResType getType() {
return mType;
}
public ResConfig getConfig() {
return mConfig;
}
public boolean isSynthesized(ResResource res) {
return mPackage.isSynthesized(res.getResSpec().getId());
}
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 = 17;
hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0);
hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0);
return hash;
}
}

View File

@ -0,0 +1,87 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
import org.apache.commons.lang3.StringUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializable {
private String mRawItems;
ResArrayValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items) {
super(parent);
mItems = new ResScalarValue[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = items[i].m2;
}
}
public ResArrayValue(ResReferenceValue parent, ResScalarValue[] items) {
super(parent);
mItems = items;
}
@Override
public void serializeToResValuesXml(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 (int i = 0; i < mItems.length; i++) {
serializer.startTag(null, "item");
serializer.text(mItems[i].encodeAsResXmlItemValue());
serializer.endTag(null, "item");
}
serializer.endTag(null, type);
}
public String getType() throws AndrolibException {
if (mItems.length == 0) {
return null;
}
String type = mItems[0].getType();
for (int i = 1; i < mItems.length; i++) {
if (mItems[i].encodeAsResXmlItemValue().startsWith("@string")) {
return "string";
} else if (mItems[i].encodeAsResXmlItemValue().startsWith("@drawable")) {
return null;
} else if (!"string".equals(type) && !"integer".equals(type)) {
return null;
} else if (!type.equals(mItems[i].getType())) {
return null;
}
}
return type;
}
private final ResScalarValue[] mItems;
public static final int BAG_KEY_ARRAY_START = 0x02000000;
}

View File

@ -0,0 +1,175 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max,
Boolean l10n) {
super(parentVal);
mType = type;
mMin = min;
mMax = max;
mL10n = l10n;
}
public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException {
return null;
}
@Override
public void serializeToResValuesXml(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);
}
if (mMin != null) {
serializer.attribute(null, "min", mMin.toString());
}
if (mMax != null) {
serializer.attribute(null, "max", mMax.toString());
}
if (mL10n != null && mL10n) {
serializer.attribute(null, "localization", "suggested");
}
serializeBody(serializer, res);
serializer.endTag(null, "attr");
}
public static ResAttr factory(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory,
ResPackage pkg) throws AndrolibException {
int type = ((ResIntValue) items[0].m2).getValue();
int scalarType = type & 0xffff;
Integer min = null, max = null;
Boolean l10n = null;
int i;
for (i = 1; i < items.length; i++) {
switch (items[i].m1) {
case BAG_KEY_ATTR_MIN:
min = ((ResIntValue) items[i].m2).getValue();
continue;
case BAG_KEY_ATTR_MAX:
max = ((ResIntValue) items[i].m2).getValue();
continue;
case BAG_KEY_ATTR_L10N:
l10n = ((ResIntValue) items[i].m2).getValue() != 0;
continue;
}
break;
}
if (i == items.length) {
return new ResAttr(parent, scalarType, min, max, l10n);
}
Duo<ResReferenceValue, ResIntValue>[] attrItems =
new Duo[items.length - i];
int j = 0;
for (; i < items.length; i++) {
int resId = items[i].m1;
pkg.addSynthesizedRes(resId);
attrItems[j++] = new Duo<ResReferenceValue, ResIntValue>(
factory.newReference(resId, null), (ResIntValue) items[i].m2);
}
switch (type & 0xff0000) {
case TYPE_ENUM:
return new ResEnumAttr(
parent, scalarType, min, max, l10n, attrItems);
case TYPE_FLAGS:
return new ResFlagsAttr(
parent, scalarType, min, max, l10n, attrItems);
}
throw new AndrolibException("Could not decode attr value");
}
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 int mType;
private final Integer mMin;
private final Integer mMax;
private final Boolean mL10n;
public static final int BAG_KEY_ATTR_TYPE = 0x01000000;
private static final int BAG_KEY_ATTR_MIN = 0x01000001;
private static final int BAG_KEY_ATTR_MAX = 0x01000002;
private static final int BAG_KEY_ATTR_L10N = 0x01000003;
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;
private static final int TYPE_ENUM = 0x00010000;
private static final int TYPE_FLAGS = 0x00020000;
}

View File

@ -0,0 +1,64 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
protected final ResReferenceValue mParent;
public ResBagValue(ResReferenceValue parent) {
this.mParent = parent;
}
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException {
String type = res.getResSpec().getType().getName();
if ("style".equals(type)) {
new ResStyleValue(mParent, new Duo[0], null)
.serializeToResValuesXml(serializer, res);
return;
}
if ("array".equals(type)) {
new ResArrayValue(mParent, new Duo[0])
.serializeToResValuesXml(serializer, res);
return;
}
if ("plurals".equals(type)) {
new ResPluralsValue(mParent, new Duo[0])
.serializeToResValuesXml(serializer, res);
return;
}
serializer.startTag(null, "item");
serializer.attribute(null, "type", type);
serializer.attribute(null, "name", res.getResSpec().getName());
serializer.endTag(null, "item");
}
public ResReferenceValue getParent() {
return mParent;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResBoolValue extends ResScalarValue {
private final boolean mValue;
public ResBoolValue(boolean value, String rawValue) {
super("bool", rawValue);
this.mValue = value;
}
public boolean getValue() {
return mValue;
}
protected String encodeAsResXml() {
return mValue ? "true" : "false";
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResColorValue extends ResIntValue {
public ResColorValue(int value, String rawValue) {
super(value, rawValue, "color");
}
@Override
protected String encodeAsResXml() {
return String.format("#%08x", mValue);
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import android.util.TypedValue;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResDimenValue extends ResIntValue {
public ResDimenValue(int value, String rawValue) {
super(value, rawValue, "dimen");
}
@Override
protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(TypedValue.TYPE_DIMENSION, mValue);
}
}

View File

@ -0,0 +1,84 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.util.Duo;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResEnumAttr extends ResAttr {
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
super(parent, type, min, max, l10n);
mItems = items;
}
@Override
public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException {
if (value instanceof ResIntValue) {
String ret = decodeValue(((ResIntValue) value).getValue());
if (ret != null) {
return ret;
}
}
return super.convertToResXmlFormat(value);
}
@Override
protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException {
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
int intVal = duo.m2.getValue();
serializer.startTag(null, "enum");
serializer.attribute(null, "name", duo.m1.getReferent().getName());
serializer.attribute(null, "value", String.valueOf(intVal));
serializer.endTag(null, "enum");
}
}
private String decodeValue(int value) throws AndrolibException {
String value2 = mItemsCache.get(value);
if (value2 == null) {
ResReferenceValue ref = null;
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
if (duo.m2.getValue() == value) {
ref = duo.m1;
break;
}
}
if (ref != null) {
value2 = ref.getReferent().getName();
mItemsCache.put(value, value2);
}
}
return value2;
}
private final Duo<ResReferenceValue, ResIntValue>[] mItems;
private final Map<Integer, String> mItemsCache =
new HashMap<Integer, String>();
}

View File

@ -0,0 +1,42 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,160 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.util.Duo;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResFlagsAttr extends ResAttr {
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max, Boolean l10n, Duo<ResReferenceValue, ResIntValue>[] items) {
super(parent, type, min, max, l10n);
mItems = new FlagItem[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = new FlagItem(items[i].m1, items[i].m2.getValue());
}
}
@Override
public String convertToResXmlFormat(ResScalarValue value)
throws AndrolibException {
if (! (value instanceof ResIntValue)) {
return super.convertToResXmlFormat(value);
}
loadFlags();
int intVal = ((ResIntValue) value).getValue();
if (intVal == 0) {
return renderFlags(mZeroFlags);
}
FlagItem[] flagItems = new FlagItem[mFlags.length];
int[] flags = new int[mFlags.length];
int flagsCount = 0;
for (int i = 0; i < mFlags.length; i++) {
FlagItem flagItem = mFlags[i];
int flag = flagItem.flag;
if ((intVal & flag) != flag) {
continue;
}
if (! isSubpartOf(flag, flags)) {
flags[flagsCount] = flag;
flagItems[flagsCount++] = flagItem;
}
}
return renderFlags(Arrays.copyOf(flagItems, flagsCount));
}
@Override
protected void serializeBody(XmlSerializer serializer, ResResource res)
throws AndrolibException, IOException {
for (int i = 0; i < mItems.length; i++) {
FlagItem item = mItems[i];
serializer.startTag(null, "flag");
serializer.attribute(null, "name", item.getValue());
serializer.attribute(null, "value",
String.format("0x%08x", item.flag));
serializer.endTag(null, "flag");
}
}
private boolean isSubpartOf(int flag, int[] flags) {
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & flag) == flag) {
return true;
}
}
return false;
}
private String renderFlags(FlagItem[] flags) throws AndrolibException {
String ret = "";
for (int i = 0; i < flags.length; i++) {
ret += "|" + flags[i].getValue();
}
if (ret.isEmpty()) {
return ret;
}
return ret.substring(1);
}
private void loadFlags() {
if (mFlags != null) {
return;
}
FlagItem[] zeroFlags = new FlagItem[mItems.length];
int zeroFlagsCount = 0;
FlagItem[] flags = new FlagItem[mItems.length];
int flagsCount = 0;
for (int i = 0; i < mItems.length; i++) {
FlagItem item = mItems[i];
if (item.flag == 0) {
zeroFlags[zeroFlagsCount++] = item;
} else {
flags[flagsCount++] = item;
}
}
mZeroFlags = Arrays.copyOf(zeroFlags, zeroFlagsCount);
mFlags = Arrays.copyOf(flags, flagsCount);
Arrays.sort(mFlags, new Comparator<FlagItem>() {
public int compare(FlagItem o1, FlagItem o2) {
return Integer.valueOf(Integer.bitCount(o2.flag))
.compareTo(Integer.bitCount(o1.flag));
}
});
}
private final FlagItem[] mItems;
private FlagItem[] mZeroFlags;
private FlagItem[] mFlags;
private static class FlagItem {
public final ResReferenceValue ref;
public final int flag;
public String value;
public FlagItem(ResReferenceValue ref, int flag) {
this.ref = ref;
this.flag = flag;
}
public String getValue() throws AndrolibException {
if (value == null) {
value = ref.getReferent().getName();
}
return value;
}
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResFloatValue extends ResScalarValue {
private final float mValue;
public ResFloatValue(float value, String rawValue) {
super("float", rawValue);
this.mValue = value;
}
public float getValue() {
return mValue;
}
protected String encodeAsResXml() {
return String.valueOf(mValue);
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import android.util.TypedValue;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResFractionValue extends ResIntValue {
public ResFractionValue(int value, String rawValue) {
super(value, rawValue, "fraction");
}
@Override
protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(TypedValue.TYPE_FRACTION, mValue);
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResIdValue extends ResValue implements ResValuesXmlSerializable {
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) throws IOException, AndrolibException {
serializer.startTag(null, "item");
serializer.attribute(null, "type", res.getResSpec().getType().getName());
serializer.attribute(null, "name", res.getResSpec().getName());
serializer.endTag(null, "item");
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import android.util.TypedValue;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResIntValue extends ResScalarValue {
protected final int mValue;
private int type;
public ResIntValue(int value, String rawValue, int type) {
this(value, rawValue, "integer");
this.type = type;
}
public ResIntValue(int value, String rawValue, String type) {
super(type, rawValue);
this.mValue = value;
}
public int getValue() {
return mValue;
}
protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(type, mValue);
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.Duo;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializable {
ResPluralsValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items) {
super(parent);
mItems = new ResScalarValue[6];
for (int i = 0; i < items.length; i++) {
mItems[items[i].m1 - BAG_KEY_PLURALS_START] =
(ResScalarValue) items[i].m2;
}
}
@Override
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException {
serializer.startTag(null, "plurals");
serializer.attribute(null, "name", res.getResSpec().getName());
for (int i = 0; i < mItems.length; i++) {
ResScalarValue item = mItems[i];
if (item == null) {
continue;
}
ResScalarValue rawValue = item;
serializer.startTag(null, "item");
serializer.attribute(null, "quantity", QUANTITY_MAP[i]);
if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue.encodeAsResXmlValue())) {
serializer.text(item.encodeAsResXmlValueExt());
} else {
String recode = item.encodeAsResXmlValue();
//Dirty, but working fix @miuirussia
for (int j = 0; j < 10; j++) {
recode = StringUtils.replace(recode, "%" + Integer.toString(j) + "$" + Integer.toString(j) + "$", "%" + Integer.toString(j) + "$");
}
serializer.text(recode);
}
serializer.endTag(null, "item");
}
serializer.endTag(null, "plurals");
}
private final ResScalarValue[] mItems;
public static final int BAG_KEY_PLURALS_START = 0x01000004;
public static final int BAG_KEY_PLURALS_END = 0x01000009;
private static final String[] QUANTITY_MAP =
new String[] {"other", "zero", "one", "two", "few", "many"};
}

View File

@ -0,0 +1,68 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 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 <brut.alll@gmail.com>
*/
public class ResReferenceValue extends ResIntValue {
private final ResPackage mPackage;
private final boolean mTheme;
public ResReferenceValue(ResPackage package_, int value, String rawValue) {
this(package_, value, rawValue, false);
}
public ResReferenceValue(ResPackage package_, int value, String rawValue,
boolean theme) {
super(value, rawValue, "reference");
mPackage = package_;
mTheme = theme;
}
protected String encodeAsResXml() throws AndrolibException {
if (isNull()) {
return "@null";
}
ResResSpec spec = getReferent();
boolean newId =
spec.hasDefaultResource() &&
spec.getDefaultResource().getValue() instanceof ResIdValue;
/* generate the beginning to fix @android */
String mStart = (mTheme ? '?' : '@') + (newId ? "+" : "");
// mStart = mStart.replace("@android", "@*android");
/* now dump back */
return mStart +
spec.getFullName(mPackage,
mTheme && spec.getType().getName().equals("attr"));
}
public ResResSpec getReferent() throws AndrolibException {
return mPackage.getResTable().getResSpec(getValue());
}
public boolean isNull() {
return mValue == 0;
}
}

View File

@ -0,0 +1,132 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.androlib.res.xml.ResXmlEncodable;
import brut.androlib.res.xml.ResXmlEncoders;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public abstract class ResScalarValue extends ResValue
implements ResXmlEncodable, ResValuesXmlSerializable {
protected final String mType;
protected final String mRawValue;
protected ResScalarValue(String type, String rawValue) {
mType = type;
mRawValue = rawValue;
}
public String encodeAsResXmlAttr() throws AndrolibException {
if (mRawValue != null) {
return mRawValue;
}
return encodeAsResXml().replace("@android:", "@*android:");
}
public String encodeAsResXmlItemValue() throws AndrolibException {
return encodeAsResXmlValue().replace("@android:", "@*android:");
}
public String encodeAsResXmlValue() throws AndrolibException {
if (mRawValue != null) {
return mRawValue;
}
return encodeAsResXmlValueExt().replace("@android:", "@*android:");
}
public String encodeAsResXmlValueExt() throws AndrolibException {
String rawValue = mRawValue;
if (rawValue != null) {
if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue)) {
int count = 1;
StringBuilder result = new StringBuilder();
String tmp1[] = rawValue.split("%%", -1);
int tmp1_sz = tmp1.length;
for(int i=0;i<tmp1_sz;i++) {
String cur1 = tmp1[i];
String tmp2[] = cur1.split("%", -1);
int tmp2_sz = tmp2.length;
for(int j=0;j<tmp2_sz;j++) {
String cur2 = tmp2[j];
result.append(cur2);
if(j != (tmp2_sz-1)) {
result.append('%').append(count).append('$');
count++;
}
}
if(i != (tmp1_sz-1)) {
result.append("%%");
}
}
rawValue = result.toString();
}
return rawValue;
}
return encodeAsResXml();
}
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException {
String type = res.getResSpec().getType().getName();
boolean item = !"reference".equals(mType) && !type.equals(mType);
String body = encodeAsResXmlValue();
/* check for resource reference */
if (body.contains("@")){
if(!res.getFilePath().contains("string")) {
item = true;
}
}
/* check for using attrib as node or item */
String tagName = item ? "item" : type;
serializer.startTag(null, tagName);
if (item) {
serializer.attribute(null, "type", type);
}
serializer.attribute(null, "name", res.getResSpec().getName());
serializeExtraXmlAttrs(serializer, res);
if (! body.isEmpty()) {
serializer.ignorableWhitespace(body);
}
serializer.endTag(null, tagName);
}
public String getType() {
return mType;
}
protected void serializeExtraXmlAttrs(XmlSerializer serializer,
ResResource res) throws IOException {
}
protected abstract String encodeAsResXml() throws AndrolibException;
}

View File

@ -0,0 +1,66 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResXmlEncoders;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResStringValue extends ResScalarValue {
public ResStringValue(String value) {
this(value, "string");
}
public ResStringValue(String value, String type) {
super(type, value);
}
@Override
public String encodeAsResXmlAttr() {
return ResXmlEncoders.encodeAsResXmlAttr(mRawValue);
}
@Override
public String encodeAsResXmlItemValue() {
return ResXmlEncoders.enumerateNonPositionalSubstitutions(
ResXmlEncoders.encodeAsXmlValue(mRawValue));
}
@Override
public String encodeAsResXmlValue() {
return ResXmlEncoders.encodeAsXmlValue(mRawValue);
}
@Override
protected String encodeAsResXml() throws AndrolibException {
throw new UnsupportedOperationException();
}
@Override
protected void serializeExtraXmlAttrs(XmlSerializer serializer,
ResResource res) throws IOException {
if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(mRawValue)) {
serializer.attribute(null, "formatted", "false");
}
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializable {
ResStyleValue(ResReferenceValue parent,
Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
super(parent);
mItems = new Duo[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = new Duo<ResReferenceValue, ResScalarValue>(
factory.newReference(items[i].m1, null), items[i].m2);
}
}
@Override
public void serializeToResValuesXml(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.encodeAsResXmlAttr());
}
for (int i = 0; i < mItems.length; i++) {
ResResSpec spec = mItems[i].m1.getReferent();
ResAttr attr = (ResAttr) spec.getDefaultResource().getValue();
String value = attr.convertToResXmlFormat(mItems[i].m2);
if (value == null) {
value = mItems[i].m2.encodeAsResXmlValue();
}
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");
}
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
}

View File

@ -0,0 +1,24 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResValue {
}

View File

@ -0,0 +1,101 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.data.value;
import android.util.TypedValue;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResPackage;
import brut.util.Duo;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResValueFactory {
private final ResPackage mPackage;
public ResValueFactory(ResPackage pakage_) {
this.mPackage = pakage_;
}
public ResScalarValue factory(int type, int value, String rawValue)
throws AndrolibException {
switch (type) {
case TypedValue.TYPE_REFERENCE:
return newReference(value, rawValue);
case TypedValue.TYPE_ATTRIBUTE:
return newReference(value, rawValue, true);
case TypedValue.TYPE_STRING:
return new ResStringValue(rawValue);
case TypedValue.TYPE_FLOAT:
return new ResFloatValue(Float.intBitsToFloat(value), rawValue);
case TypedValue.TYPE_DIMENSION:
return new ResDimenValue(value, rawValue);
case TypedValue.TYPE_FRACTION:
return new ResFractionValue(value, rawValue);
case TypedValue.TYPE_INT_BOOLEAN:
return new ResBoolValue(value != 0, rawValue);
}
if (type >= TypedValue.TYPE_FIRST_COLOR_INT
&& type <= TypedValue.TYPE_LAST_COLOR_INT) {
return new ResColorValue(value, rawValue);
}
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return new ResIntValue(value, rawValue, type);
}
throw new AndrolibException("Invalid value type: "+ type);
}
public ResValue factory(String value) {
if (value.startsWith("res/")) {
return new ResFileValue(value);
}
return new ResStringValue(value);
}
public ResBagValue bagFactory(int parent,
Duo<Integer, ResScalarValue>[] items) throws AndrolibException {
ResReferenceValue parentVal = newReference(parent, null);
if (items.length == 0) {
return new ResBagValue(parentVal);
}
int key = items[0].m1;
if (key == ResAttr.BAG_KEY_ATTR_TYPE) {
return ResAttr.factory(parentVal, items, this, mPackage);
}
if (key == ResArrayValue.BAG_KEY_ARRAY_START) {
return new ResArrayValue(parentVal, items);
}
if (key >= ResPluralsValue.BAG_KEY_PLURALS_START
&& key <= ResPluralsValue.BAG_KEY_PLURALS_END) {
return new ResPluralsValue(parentVal, items);
}
return new ResStyleValue(parentVal, items, this);
}
public ResReferenceValue newReference(int resID, String rawValue) {
return newReference(resID, rawValue, false);
}
public ResReferenceValue newReference(int resID, String rawValue,
boolean theme) {
return new ResReferenceValue(mPackage, resID, rawValue, theme);
}
}

View File

@ -0,0 +1,440 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import android.util.TypedValue;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.*;
import brut.androlib.res.data.value.*;
import brut.util.Duo;
import brut.androlib.res.data.ResTable;
import brut.util.ExtDataInput;
import com.mindprod.ledatastream.LEDataInputStream;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.logging.Logger;
import org.apache.commons.io.input.CountingInputStream;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ARSCDecoder {
public static ARSCData decode(InputStream arscStream,
boolean findFlagsOffsets, boolean keepBroken)
throws AndrolibException {
return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable());
}
public static ARSCData decode(InputStream arscStream,
boolean findFlagsOffsets, boolean keepBroken, ResTable resTable)
throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable,
findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTable();
return new ARSCData(
pkgs,
decoder.mFlagsOffsets == null ? null :
decoder.mFlagsOffsets.toArray(new FlagsOffset[0]),
resTable);
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
}
private ARSCDecoder(InputStream arscStream, ResTable resTable,
boolean storeFlagsOffsets, boolean keepBroken) {
if (storeFlagsOffsets) {
arscStream = mCountIn = new CountingInputStream(arscStream);
mFlagsOffsets = new ArrayList<FlagsOffset>();
} else {
mCountIn = null;
mFlagsOffsets = null;
}
mIn = new ExtDataInput(new LEDataInputStream(arscStream));
mResTable = resTable;
mKeepBroken = keepBroken;
}
private ResPackage[] readTable() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE);
int packageCount = mIn.readInt();
mTableStrings = StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount];
nextChunk();
for (int i = 0; i < packageCount; i++) {
packages[i] = readPackage();
}
return packages;
}
private ResPackage readPackage() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_PACKAGE);
int id = (byte) mIn.readInt();
String name = mIn.readNulEndedString(128, true);
/*typeNameStrings*/ mIn.skipInt();
/*typeNameCount*/ mIn.skipInt();
/*specNameStrings*/ mIn.skipInt();
/*specNameCount*/ mIn.skipInt();
mTypeNames = StringBlock.read(mIn);
mSpecNames = StringBlock.read(mIn);
mResId = id << 24;
mPkg = new ResPackage(mResTable, id, name);
nextChunk();
while (mHeader.type == Header.TYPE_TYPE) {
readType();
}
return mPkg;
}
private ResType readType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_TYPE);
byte id = mIn.readByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
mMissingResSpecs = new boolean[entryCount];
Arrays.fill(mMissingResSpecs, true);
if (mFlagsOffsets != null) {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
}
/*flags*/ mIn.skipBytes(entryCount * 4);
mResId = (0xff000000 & mResId) | id << 16;
mType = new ResType(mTypeNames.getString(id - 1), mResTable, mPkg);
mPkg.addType(mType);
while (nextChunk().type == Header.TYPE_CONFIG) {
readConfig();
}
addMissingResSpecs();
return mType;
}
private ResConfig readConfig() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_CONFIG);
/*typeId*/ mIn.skipInt();
int entryCount = mIn.readInt();
/*entriesStart*/ mIn.skipInt();
ResConfigFlags flags = readConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount);
if (flags.isInvalid) {
String resName = mType.getName() + flags.getQualifiers();
if (mKeepBroken) {
LOGGER.warning(
"Invalid config flags detected: " + resName);
} else {
LOGGER.warning(
"Invalid config flags detected. Dropping resources: " + resName);
}
}
mConfig = flags.isInvalid && ! mKeepBroken ?
null : mPkg.getOrCreateConfig(flags);
for (int i = 0; i < entryOffsets.length; i++) {
if (entryOffsets[i] != -1) {
mMissingResSpecs[i] = false;
mResId = (mResId & 0xffff0000) | i;
readEntry();
}
}
return mConfig;
}
private void readEntry() throws IOException, AndrolibException {
/*size*/ mIn.skipBytes(2);
short flags = mIn.readShort();
int specNamesId = mIn.readInt();
ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ?
readValue() : readComplexEntry();
if (mConfig == null) {
return;
}
ResID resId = new ResID(mResId);
ResResSpec spec;
if (mPkg.hasResSpec(resId)) {
spec = mPkg.getResSpec(resId);
} else {
spec = new ResResSpec(
resId, mSpecNames.getString(specNamesId), mPkg, mType);
mPkg.addResSpec(spec);
mType.addResSpec(spec);
}
ResResource res = new ResResource(mConfig, spec, value);
mConfig.addResource(res);
spec.addResource(res);
mPkg.addResource(res);
}
private ResBagValue readComplexEntry() throws IOException,
AndrolibException {
int parent = mIn.readInt();
int count = mIn.readInt();
ResValueFactory factory = mPkg.getValueFactory();
Duo<Integer, ResScalarValue>[] items = new Duo[count];
for (int i = 0; i < count; i++) {
items[i] = new Duo<Integer, ResScalarValue>(
mIn.readInt(), (ResScalarValue) readValue());
}
return factory.bagFactory(parent, items);
}
private ResValue readValue() throws IOException, AndrolibException {
/*size*/ mIn.skipCheckShort((short) 8);
/*zero*/ mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte();
int data = mIn.readInt();
return type == TypedValue.TYPE_STRING ?
mPkg.getValueFactory().factory(mTableStrings.getHTML(data)) :
mPkg.getValueFactory().factory(type, data, null);
}
private ResConfigFlags readConfigFlags() throws IOException, AndrolibException {
int size = mIn.readInt();
if (size < 28) {
throw new AndrolibException("Config size < 28");
}
boolean isInvalid = false;
short mcc = mIn.readShort();
short mnc = mIn.readShort();
char[] language = new char[]{
(char) mIn.readByte(), (char) mIn.readByte()};
char[] country = new char[]{
(char) mIn.readByte(), (char) mIn.readByte()};
byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte();
short density = mIn.readShort();
byte keyboard = mIn.readByte();
byte navigation = mIn.readByte();
byte inputFlags = mIn.readByte();
/*inputPad0*/ mIn.skipBytes(1);
short screenWidth = mIn.readShort();
short screenHeight = mIn.readShort();
short sdkVersion = mIn.readShort();
/*minorVersion, now must always be 0*/ mIn.skipBytes(2);
byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = mIn.readByte();
uiMode = mIn.readByte();
smallestScreenWidthDp = mIn.readShort();
}
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = mIn.readShort();
screenHeightDp = mIn.readShort();
}
if (size >= 40) {
// mIn.skipBytes(2);
}
int exceedingSize = size - KNOWN_CONFIG_BYTES;
if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
mIn.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf);
if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String.format(
"Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
KNOWN_CONFIG_BYTES));
} else {
LOGGER.warning(String.format(
"Config flags size > %d. Exceeding bytes: 0x%X.",
KNOWN_CONFIG_BYTES, exceedingBI));
isInvalid = true;
}
}
return new ResConfigFlags(mcc, mnc, language, country, orientation,
touchscreen, density, keyboard, navigation, inputFlags,
screenWidth, screenHeight, sdkVersion, screenLayout, uiMode,
smallestScreenWidthDp, screenWidthDp, screenHeightDp, isInvalid);
}
private void addMissingResSpecs() throws AndrolibException {
int resId = mResId & 0xffff0000;
for (int i = 0; i < mMissingResSpecs.length; i++) {
if (! mMissingResSpecs[i]) {
continue;
}
ResResSpec spec = new ResResSpec(new ResID(resId | i),
String.format("APKTOOL_DUMMY_%04x", i), mPkg, mType);
mPkg.addResSpec(spec);
mType.addResSpec(spec);
ResValue value = new ResBoolValue(false, null);
ResResource res = new ResResource(
mPkg.getOrCreateConfig(new ResConfigFlags()), spec, value);
mPkg.addResource(res);
mConfig.addResource(res);
spec.addResource(res);
}
}
private Header nextChunk() throws IOException {
return mHeader = Header.read(mIn);
}
private void checkChunkType(int expectedType) throws AndrolibException {
if (mHeader.type != expectedType) {
throw new AndrolibException(String.format(
"Invalid chunk type: expected=0x%08x, got=0x%08x",
expectedType, mHeader.type));
}
}
private void nextChunkCheckType(int expectedType)
throws IOException, AndrolibException {
nextChunk();
checkChunkType(expectedType);
}
private final ExtDataInput mIn;
private final ResTable mResTable;
private final CountingInputStream mCountIn;
private final List<FlagsOffset> mFlagsOffsets;
private final boolean mKeepBroken;
private Header mHeader;
private StringBlock mTableStrings;
private StringBlock mTypeNames;
private StringBlock mSpecNames;
private ResPackage mPkg;
private ResType mType;
private ResConfig mConfig;
private int mResId;
private boolean[] mMissingResSpecs;
private final static short ENTRY_FLAG_COMPLEX = 0x0001;
public static class Header {
public final short type;
public final int chunkSize;
public Header(short type, int size) {
this.type = type;
this.chunkSize = size;
}
public static Header read(ExtDataInput in) throws IOException {
short type;
try {
type = in.readShort();
} catch (EOFException ex) {
return new Header(TYPE_NONE, 0);
}
in.skipBytes(2);
return new Header(type, in.readInt());
}
public final static short
TYPE_NONE = -1,
TYPE_TABLE = 0x0002,
TYPE_PACKAGE = 0x0200,
TYPE_TYPE = 0x0202,
TYPE_CONFIG = 0x0201;
}
public static class FlagsOffset {
public final int offset;
public final int count;
public FlagsOffset(int offset, int count) {
this.offset = offset;
this.count = count;
}
}
private static final Logger LOGGER =
Logger.getLogger(ARSCDecoder.class.getName());
private static final int KNOWN_CONFIG_BYTES = 36;
public static class ARSCData {
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets,
ResTable resTable) {
mPackages = packages;
mFlagsOffsets = flagsOffsets;
mResTable = resTable;
}
public FlagsOffset[] getFlagsOffsets() {
return mFlagsOffsets;
}
public ResPackage[] getPackages() {
return mPackages;
}
public ResPackage getOnePackage() throws AndrolibException {
if (mPackages.length != 1) {
throw new AndrolibException(
"Arsc file contains zero or multiple packages");
}
return mPackages[0];
}
public ResTable getResTable() {
return mResTable;
}
private final ResPackage[] mPackages;
private final FlagsOffset[] mFlagsOffsets;
private final ResTable mResTable;
}
}

View File

@ -0,0 +1,139 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.AndrolibException;
import brut.androlib.err.CantFind9PatchChunk;
import brut.util.ExtDataInput;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class Res9patchStreamDecoder implements ResStreamDecoder {
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
try {
byte[] data = IOUtils.toByteArray(in);
BufferedImage im = ImageIO.read(new ByteArrayInputStream(data));
int w = im.getWidth(), h = im.getHeight();
BufferedImage im2 = new BufferedImage(
w + 2, h + 2, BufferedImage.TYPE_4BYTE_ABGR);
if (im.getType() == BufferedImage.TYPE_4BYTE_ABGR) {
im2.getRaster().setRect(1, 1, im.getRaster());
} else {
im2.getGraphics().drawImage(im, 1, 1, null);
}
NinePatch np = getNinePatch(data);
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
int[] xDivs = np.xDivs;
for (int i = 0; i < xDivs.length; i += 2) {
drawHLine(im2, 0, xDivs[i] + 1, xDivs[i + 1]);
}
int[] yDivs = np.yDivs;
for (int i = 0; i < yDivs.length; i += 2) {
drawVLine(im2, 0, yDivs[i] + 1, yDivs[i + 1]);
}
ImageIO.write(im2, "png", out);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
private NinePatch getNinePatch(byte[] data)
throws AndrolibException, IOException {
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
find9patchChunk(di);
return NinePatch.decode(di);
}
private void find9patchChunk(DataInput di)
throws AndrolibException, IOException {
di.skipBytes(8);
while (true) {
int size;
try {
size = di.readInt();
} catch (IOException ex) {
throw new CantFind9PatchChunk("Cant find nine patch chunk", ex);
}
if (di.readInt() == NP_CHUNK_TYPE) {
return;
}
di.skipBytes(size + 4);
}
}
private void drawHLine(BufferedImage im, int y, int x1, int x2) {
for (int x = x1; x <= x2; x++) {
im.setRGB(x, y, NP_COLOR);
}
}
private void drawVLine(BufferedImage im, int x, int y1, int y2) {
for (int y = y1; y <= y2; y++) {
im.setRGB(x, y, NP_COLOR);
}
}
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
private static final int NP_COLOR = 0xff000000;
private static class NinePatch {
public final int padLeft, padRight, padTop, padBottom;
public final int[] xDivs, yDivs;
public NinePatch(int padLeft, int padRight, int padTop, int padBottom,
int[] xDivs, int[] yDivs) {
this.padLeft = padLeft;
this.padRight = padRight;
this.padTop = padTop;
this.padBottom = padBottom;
this.xDivs = xDivs;
this.yDivs = yDivs;
}
public static NinePatch decode(ExtDataInput di) throws IOException {
di.skipBytes(1);
byte numXDivs = di.readByte();
byte numYDivs = di.readByte();
di.skipBytes(1);
di.skipBytes(8);
int padLeft = di.readInt();
int padRight = di.readInt();
int padTop = di.readInt();
int padBottom = di.readInt();
di.skipBytes(4);
int[] xDivs = di.readIntArray(numXDivs);
int[] yDivs = di.readIntArray(numYDivs);
return new NinePatch(padLeft, padRight, padTop, padBottom,
xDivs, yDivs);
}
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.value.ResAttr;
import brut.androlib.res.data.value.ResScalarValue;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResAttrDecoder {
public String decode(int type, int value, String rawValue, int attrResId)
throws AndrolibException {
ResScalarValue resValue = mCurrentPackage.getValueFactory()
.factory(type, value, rawValue);
String decoded = null;
if (attrResId != 0) {
ResAttr attr = (ResAttr) getCurrentPackage().getResTable()
.getResSpec(attrResId).getDefaultResource().getValue();
decoded = attr.convertToResXmlFormat(resValue);
}
return decoded != null ? decoded : resValue.encodeAsResXmlAttr();
}
public ResPackage getCurrentPackage() throws AndrolibException {
if (mCurrentPackage == null) {
throw new AndrolibException("Current package not set");
}
return mCurrentPackage;
}
public void setCurrentPackage(ResPackage currentPackage) {
mCurrentPackage = currentPackage;
}
private ResPackage mCurrentPackage;
}

View File

@ -0,0 +1,145 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.AndrolibException;
import brut.androlib.err.CantFind9PatchChunk;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.data.value.ResBoolValue;
import brut.androlib.res.data.value.ResFileValue;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ResFileDecoder {
private final ResStreamDecoderContainer mDecoders;
public ResFileDecoder(ResStreamDecoderContainer decoders) {
this.mDecoders = decoders;
}
public void decode(ResResource res, Directory inDir, Directory outDir)
throws AndrolibException {
ResFileValue fileValue = (ResFileValue) res.getValue();
String inFileName = fileValue.getStrippedPath();
String outResName = res.getFilePath();
String typeName = res.getResSpec().getType().getName();
String ext = null;
String outFileName;
int extPos = inFileName.lastIndexOf(".");
if (extPos == -1) {
outFileName = outResName;
} else {
ext = inFileName.substring(extPos);
outFileName = outResName + ext;
}
try {
if (typeName.equals("raw")) {
decode(inDir, inFileName, outDir, outFileName, "raw");
return;
}
if (typeName.equals("drawable") || typeName.equals("mipmap")) {
if (inFileName.toLowerCase().endsWith(".9.png")) {
outFileName = outResName + ".9" + ext;
try {
decode(
inDir, inFileName, outDir, outFileName, "9patch");
return;
} catch (CantFind9PatchChunk ex) {
LOGGER.log(Level.WARNING, String.format(
"Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.",
inFileName
), ex);
outDir.removeFile(outFileName);
outFileName = outResName + ext;
}
}
if (! ".xml".equals(ext)) {
decode(inDir, inFileName, outDir, outFileName, "raw");
return;
}
}
decode(inDir, inFileName, outDir, outFileName, "xml");
} catch (AndrolibException ex) {
LOGGER.log(Level.SEVERE, String.format(
"Could not decode file, replacing by FALSE value: %s",
inFileName, outFileName), ex);
res.replace(new ResBoolValue(false, null));
}
}
public void decode(Directory inDir, String inFileName, Directory outDir,
String outFileName, String decoder) throws AndrolibException {
InputStream in = null;
OutputStream out = null;
try {
in = inDir.getFileInput(inFileName);
out = outDir.getFileOutput(outFileName);
mDecoders.decode(in, out, decoder);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
try{
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
}
public void decodeManifest(Directory inDir, String inFileName, Directory outDir,
String outFileName) throws AndrolibException {
InputStream in = null;
OutputStream out = null;
try {
in = inDir.getFileInput(inFileName);
out = outDir.getFileOutput(outFileName);
((XmlPullStreamDecoder)mDecoders.getDecoder("xml")).decodeManifest(in, out);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
try{
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
}
private final static Logger LOGGER =
Logger.getLogger(ResFileDecoder.class.getName());
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 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 <brut.alll@gmail.com>
*/
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);
}
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.AndrolibException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public interface ResStreamDecoder {
public void decode(InputStream in, OutputStream out)
throws AndrolibException;
}

View File

@ -0,0 +1,48 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 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 <brut.alll@gmail.com>
*/
public class ResStreamDecoderContainer {
private final Map<String, ResStreamDecoder> mDecoders =
new HashMap<String, ResStreamDecoder>();
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);
}
}

View File

@ -0,0 +1,347 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtDataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
* @author Dmitry Skiba
*
* Block of strings, used in binary xml and arsc.
*
* TODO:
* - implement get()
*
*/
public class StringBlock {
/**
* Reads whole (including chunk type) string block from stream.
* Stream must be at the chunk type.
*/
public static StringBlock read(ExtDataInput reader) throws IOException {
reader.skipCheckInt(CHUNK_TYPE);
int chunkSize = reader.readInt();
int stringCount = reader.readInt();
int styleOffsetCount = reader.readInt();
int flags = reader.readInt();
int stringsOffset = reader.readInt();
int stylesOffset = reader.readInt();
StringBlock block = new StringBlock();
block.m_isUTF8 = (flags & UTF8_FLAG) != 0;
block.m_stringOffsets = reader.readIntArray(stringCount);
block.m_stringOwns = new int[stringCount];
for (int i=0;i<stringCount;i++) {
block.m_stringOwns[i] = -1;
}
if (styleOffsetCount != 0) {
block.m_styleOffsets = reader.readIntArray(styleOffsetCount);
}
{
int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset;
if ((size % 4) != 0) {
throw new IOException("String data size is not multiple of 4 (" + size + ").");
}
block.m_strings = new byte[size];
reader.readFully(block.m_strings);
}
if (stylesOffset != 0) {
int size = (chunkSize - stylesOffset);
if ((size % 4) != 0) {
throw new IOException("Style data size is not multiple of 4 (" + size + ").");
}
block.m_styles = reader.readIntArray(size / 4);
}
return block;
}
/**
* Returns number of strings in block.
*/
public int getCount() {
return m_stringOffsets != null
? m_stringOffsets.length
: 0;
}
/**
* Returns raw string (without any styling information) at specified index.
*/
public String getString(int index) {
if (index < 0
|| m_stringOffsets == null
|| index >= m_stringOffsets.length) {
return null;
}
int offset = m_stringOffsets[index];
int length;
if (! m_isUTF8) {
length = getShort(m_strings, offset) * 2;
offset += 2;
} else {
offset += getVarint(m_strings, offset)[1];
int[] varint = getVarint(m_strings, offset);
offset += varint[1];
length = varint[0];
}
return decodeString(offset, length);
}
/**
* Not yet implemented.
*
* Returns string with style information (if any).
*/
public CharSequence get(int index) {
return getString(index);
}
/**
* Returns string with style tags (html-like).
*/
public String getHTML(int index) {
String raw = getString(index);
if (raw == null) {
return raw;
}
int[] style = getStyle(index);
if (style == null) {
return ResXmlEncoders.escapeXmlChars(raw);
}
StringBuilder html = new StringBuilder(raw.length() + 32);
int[] opened = new int[style.length / 3];
int offset = 0, depth = 0;
while (true) {
int i = -1, j;
for (j = 0; j != style.length; j += 3) {
if (style[j + 1] == -1) {
continue;
}
if (i == -1 || style[i + 1] > style[j + 1]) {
i = j;
}
}
int start = ((i != -1) ? style[i + 1] : raw.length());
for (j = depth - 1; j >= 0; j--) {
int last = opened[j];
int end = style[last + 2];
if (end >= start) {
break;
}
if (offset <= end) {
html.append(ResXmlEncoders.escapeXmlChars(
raw.substring(offset, end + 1)));
offset = end + 1;
}
outputStyleTag(getString(style[last]), html, true);
}
depth = j + 1;
if (offset < start) {
html.append(ResXmlEncoders.escapeXmlChars(
raw.substring(offset, start)));
offset = start;
}
if (i == -1) {
break;
}
outputStyleTag(getString(style[i]), html, false);
style[i + 1] = -1;
opened[depth++] = i;
}
return html.toString();
}
private void outputStyleTag(String tag, StringBuilder builder,
boolean close) {
builder.append('<');
if (close) {
builder.append('/');
}
int pos = tag.indexOf(';');
if (pos == -1) {
builder.append(tag);
} else {
builder.append(tag.substring(0, pos));
if (! close) {
boolean loop = true;
while (loop) {
int pos2 = tag.indexOf('=', pos + 1);
builder.append(' ').append(tag.substring(pos + 1, pos2))
.append("=\"");
pos = tag.indexOf(';', pos2 + 1);
String val;
if (pos != -1) {
val = tag.substring(pos2 + 1, pos);
} else {
loop = false;
val = tag.substring(pos2 + 1);
}
builder.append(ResXmlEncoders.escapeXmlChars(val))
.append('"');
}
}
}
builder.append('>');
}
/**
* Finds index of the string.
* Returns -1 if the string was not found.
*/
public int find(String string) {
if (string == null) {
return -1;
}
for (int i = 0; i != m_stringOffsets.length; ++i) {
int offset = m_stringOffsets[i];
int length = getShort(m_strings, offset);
if (length != string.length()) {
continue;
}
int j = 0;
for (; j != length; ++j) {
offset += 2;
if (string.charAt(j) != getShort(m_strings, offset)) {
break;
}
}
if (j == length) {
return i;
}
}
return -1;
}
///////////////////////////////////////////// implementation
private StringBlock() {
}
/**
* Returns style information - array of int triplets,
* where in each triplet:
* * first int is index of tag name ('b','i', etc.)
* * second int is tag start index in string
* * third int is tag end index in string
*/
private int[] getStyle(int index) {
if (m_styleOffsets == null || m_styles == null
|| index >= m_styleOffsets.length) {
return null;
}
int offset = m_styleOffsets[index] / 4;
int style[];
{
int count = 0;
for (int i = offset; i < m_styles.length; ++i) {
if (m_styles[i] == -1) {
break;
}
count += 1;
}
if (count == 0 || (count % 3) != 0) {
return null;
}
style = new int[count];
}
for (int i = offset, j = 0; i < m_styles.length;) {
if (m_styles[i] == -1) {
break;
}
style[j++] = m_styles[i++];
}
return style;
}
private String decodeString(int offset, int length) {
try {
return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode(
ByteBuffer.wrap(m_strings, offset, length)).toString();
} catch (CharacterCodingException ex) {
LOGGER.log(Level.WARNING, null, ex);
return null;
}
}
private static final int getShort(byte[] array, int offset) {
return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff;
}
private static final int getShort(int[] array, int offset) {
int value = array[offset / 4];
if ((offset % 4) / 2 == 0) {
return (value & 0xFFFF);
} else {
return (value >>> 16);
}
}
private static final int[] getVarint(byte[] array, int offset) {
int val = array[offset];
boolean more = (val & 0x80) != 0;
val &= 0x7f;
if (! more) {
return new int[]{val, 1};
} else {
return new int[]{val << 8 | array[offset + 1] & 0xff, 2};
}
}
public boolean touch(int index, int own) {
if (index < 0
|| m_stringOwns == null
|| index >= m_stringOwns.length) {
return false;
}
if(m_stringOwns[index] == -1) {
m_stringOwns[index] = own;
return true;
} else if (m_stringOwns[index] == own) {
return true;
} else {
return false;
}
}
private int[] m_stringOffsets;
private byte[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private boolean m_isUTF8;
private int[] m_stringOwns;
private static final CharsetDecoder UTF16LE_DECODER =
Charset.forName("UTF-16LE").newDecoder();
private static final CharsetDecoder UTF8_DECODER =
Charset.forName("UTF-8").newDecoder();
private static final Logger LOGGER =
Logger.getLogger(StringBlock.class.getName());
private static final int CHUNK_TYPE = 0x001C0001;
private static final int UTF8_FLAG = 0x00000100;
}

View File

@ -0,0 +1,125 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.decoder;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtXmlSerializer;
import java.io.*;
import java.util.logging.Logger;
import org.xmlpull.v1.*;
import org.xmlpull.v1.wrapper.*;
import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class XmlPullStreamDecoder implements ResStreamDecoder {
public XmlPullStreamDecoder(XmlPullParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
}
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
final ResTable resTable = ((AXmlResourceParser)mParser).getAttrDecoder().getCurrentPackage().getResTable();
XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory){
boolean hideSdkInfo = false;
@Override
public void event(XmlPullParser pp) throws XmlPullParserException, IOException {
int type = pp.getEventType();
if (type == XmlPullParser.START_TAG) {
if ("uses-sdk".equalsIgnoreCase(pp.getName())) {
try {
hideSdkInfo = parseAttr(pp);
if(hideSdkInfo) {
return;
}
} catch (AndrolibException e) {}
}
} else if (hideSdkInfo && type == XmlPullParser.END_TAG &&
"uses-sdk".equalsIgnoreCase(pp.getName())) {
return;
}
super.event(pp);
}
private boolean parseAttr(XmlPullParser pp) throws AndrolibException {
ResTable restable = resTable;
for (int i = 0; i < pp.getAttributeCount(); i++) {
final String a_ns = "http://schemas.android.com/apk/res/android";
String ns = pp.getAttributeNamespace (i);
if (a_ns.equalsIgnoreCase(ns)) {
String name = pp.getAttributeName (i);
String value = pp.getAttributeValue (i);
if (name != null && value != null) {
if (name.equalsIgnoreCase("minSdkVersion") ||
name.equalsIgnoreCase("targetSdkVersion") ||
name.equalsIgnoreCase("maxSdkVersion")) {
restable.addSdkInfo(name, value);
} else {
restable.clearSdkInfo();
return false;//Found unknown flags
}
}
} else {
resTable.clearSdkInfo();
return false;//Found unknown flags
}
}
return true;
}
};
par.setInput(in, null);
ser.setOutput(out, null);
while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par);
}
ser.flush();
} catch (XmlPullParserException ex) {
throw new AndrolibException("Could not decode XML", ex);
} catch (IOException ex) {
throw new AndrolibException("Could not decode XML", ex);
}
}
public void decodeManifest(InputStream in, OutputStream out)
throws AndrolibException {
mOptimizeForManifest = true;
try {
decode(in, out);
} finally {
mOptimizeForManifest = false;
}
}
private final XmlPullParser mParser;
private final ExtXmlSerializer mSerial;
private boolean mOptimizeForManifest = false;
private final static Logger LOGGER =
Logger.getLogger(XmlPullStreamDecoder.class.getName());
}

View File

@ -0,0 +1,63 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.util;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import brut.directory.ZipRODirectory;
import java.io.File;
import java.net.URI;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ExtFile extends File {
public ExtFile(File file) {
super(file.getPath());
}
public ExtFile(URI uri) {
super(uri);
}
public ExtFile(File parent, String child) {
super(parent, child);
}
public ExtFile(String parent, String child) {
super(parent, child);
}
public ExtFile(String pathname) {
super(pathname);
}
public Directory getDirectory() throws DirectoryException {
if (mDirectory == null) {
if (isDirectory()) {
mDirectory = new FileDirectory(this);
} else {
mDirectory = new ZipRODirectory(this);
}
}
return mDirectory;
}
private Directory mDirectory;
}

View File

@ -0,0 +1,79 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.util;
import java.io.*;
import org.xmlpull.mxp1_serializer.MXSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer {
@Override
public void startDocument(String encoding, Boolean standalone) throws
IOException, IllegalArgumentException, IllegalStateException {
super.startDocument(encoding != null ? encoding : mDefaultEncoding,
standalone);
this.newLine();
}
@Override
protected void writeAttributeValue(String value, Writer out)
throws IOException {
if (mIsDisabledAttrEscape) {
out.write(value);
return;
}
super.writeAttributeValue(value, out);
}
@Override
public void setOutput(OutputStream os, String encoding) throws IOException {
super.setOutput(os, encoding != null ? encoding : mDefaultEncoding);
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
return mDefaultEncoding;
}
return super.getProperty(name);
}
@Override
public void setProperty(String name, Object value)
throws IllegalArgumentException, IllegalStateException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
mDefaultEncoding = (String) value;
} else {
super.setProperty(name, value);
}
}
public ExtXmlSerializer newLine() throws IOException {
super.out.write(lineSeparator);
return this;
}
public void setDisabledAttrEscape(boolean disabled) {
mIsDisabledAttrEscape = disabled;
}
private String mDefaultEncoding;
private boolean mIsDisabledAttrEscape = false;
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.util;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public interface ExtXmlSerializer extends XmlSerializer {
public ExtXmlSerializer newLine() throws IOException;
public void setDisabledAttrEscape(boolean disabled);
public static final String PROPERTY_SERIALIZER_INDENTATION =
"http://xmlpull.org/v1/doc/properties.html#serializer-indentation";
public static final String PROPERTY_SERIALIZER_LINE_SEPARATOR =
"http://xmlpull.org/v1/doc/properties.html#serializer-line-separator";
public static final String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING";
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.xml;
import brut.androlib.AndrolibException;
import brut.androlib.res.data.ResResource;
import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public interface ResValuesXmlSerializable {
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException;
}

View File

@ -0,0 +1,27 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.xml;
import brut.androlib.AndrolibException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public interface ResXmlEncodable {
public String encodeAsResXmlAttr() throws AndrolibException;
public String encodeAsResXmlValue() throws AndrolibException;
}

View File

@ -0,0 +1,202 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.res.xml;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public final class ResXmlEncoders {
public static String escapeXmlChars(String str) {
return str.replace("&", "&amp;").replace("<", "&lt;");
}
public static String encodeAsResXmlAttr(String str) {
if (str.isEmpty()) {
return str;
}
char[] chars = str.toCharArray();
StringBuilder out = new StringBuilder(str.length() + 10);
switch (chars[0]) {
case '#':
case '@':
case '?':
out.append('\\');
}
for (char c : chars) {
switch (c) {
case '\\':
out.append('\\');
break;
case '"':
out.append("&quot;");
continue;
case '\n':
out.append("\\n");
continue;
default:
if (!isPrintableChar(c)) {
out.append(String.format("\\u%04x", (int) c));
continue;
}
}
out.append(c);
}
return out.toString();
}
public static String encodeAsXmlValue(String str) {
if (str.isEmpty()) {
return str;
}
char[] chars = str.toCharArray();
StringBuilder out = new StringBuilder(str.length() + 10);
switch (chars[0]) {
case '#':
case '@':
case '?':
out.append('\\');
}
boolean isInStyleTag = false;
int startPos = 0;
boolean enclose = false;
boolean wasSpace = true;
for (char c : chars) {
if (isInStyleTag) {
if (c == '>') {
isInStyleTag = false;
startPos = out.length() + 1;
enclose = false;
}
} else if (c == ' ') {
if (wasSpace) {
enclose = true;
}
wasSpace = true;
} else {
wasSpace = false;
switch (c) {
case '\\':
out.append('\\');
break;
case '\'':
case '\n':
enclose = true;
break;
case '"':
out.append('\\');
break;
case '<':
isInStyleTag = true;
if (enclose) {
out.insert(startPos, '"').append('"');
}
break;
default:
if (!isPrintableChar(c)) {
out.append(String.format("\\u%04x", (int) c));
continue;
}
}
}
out.append(c);
}
if (enclose || wasSpace) {
out.insert(startPos, '"').append('"');
}
return out.toString();
}
public static boolean hasMultipleNonPositionalSubstitutions(String str) {
return findNonPositionalSubstitutions(str, 2).size() > 1;
}
public static String enumerateNonPositionalSubstitutions(String str) {
List<Integer> subs = findNonPositionalSubstitutions(str, -1);
if (subs.size() < 2) {
return str;
}
StringBuilder out = new StringBuilder();
int pos = 0;
int count = 0;
for (Integer sub : subs) {
out.append(str.substring(pos, ++sub)).append(++count).append('$');
pos = sub;
}
out.append(str.substring(pos));
return out.toString();
}
/**
* It searches for "%", but not "%%" nor "%(\d)+\$"
*/
private static List<Integer> findNonPositionalSubstitutions(String str,
int max) {
int pos = 0;
int pos2 = 0;
int count = 0;
int length = str.length();
List<Integer> ret = new ArrayList<Integer>();
while((pos2 = (pos = str.indexOf('%', pos2)) + 1) != 0) {
if (pos2 == length) {
break;
}
char c = str.charAt(pos2++);
if (c == '%') {
continue;
}
if (c >= '0' && c <= '9' && pos2 < length) {
do {
c = str.charAt(pos2++);
} while (c >= '0' && c <= '9' && pos2 < length);
if (c == '$') {
continue;
}
}
ret.add(pos);
if (max != -1 && ++count >= max) {
break;
}
}
return ret;
}
private static boolean isPrintableChar(char c) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return !Character.isISOControl(c)
&& c != KeyEvent.CHAR_UNDEFINED
&& block != null
&& block != Character.UnicodeBlock.SPECIALS;
}
}

View File

@ -0,0 +1,227 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.src;
import brut.androlib.AndrolibException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jf.dexlib.Code.Analysis.RegisterType;
import org.jf.dexlib.Code.Opcode;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class DebugInjector {
public static void inject(ListIterator<String> it, StringBuilder out)
throws AndrolibException {
new DebugInjector(it, out).inject();
}
private DebugInjector(ListIterator<String> it, StringBuilder out) {
mIt = it;
mOut = out;
}
private void inject() throws AndrolibException {
String definition = nextAndAppend();
if (
definition.contains(" abstract ") ||
definition.contains(" native ")
) {
nextAndAppend();
return;
}
injectParameters(definition);
boolean end = false;
while (!end) {
end = step();
}
}
private void injectParameters(String definition) throws AndrolibException {
int pos = definition.indexOf('(');
if (pos == -1) {
throw new AndrolibException();
}
int pos2 = definition.indexOf(')', pos);
if (pos2 == -1) {
throw new AndrolibException();
}
String params = definition.substring(pos + 1, pos2);
int i = definition.contains(" static ") ? 0 : 1;
int argc = TypeName.listFromInternalName(params).size() + i;
while(i < argc) {
mOut.append(".parameter \"p").append(i).append("\"\n");
i++;
}
}
private boolean step() {
String line = next();
if (line.isEmpty()) {
return false;
}
switch (line.charAt(0)) {
case '#':
return processComment(line);
case ':':
append(line);
return false;
case '.':
return processDirective(line);
default:
return processInstruction(line);
}
}
private boolean processComment(String line) {
if (mFirstInstruction) {
return false;
}
Matcher m = REGISTER_INFO_PATTERN.matcher(line);
while (m.find()) {
String localName = m.group(1);
String localType = null;
switch (RegisterType.Category.valueOf(m.group(2))) {
case Reference:
case Null:
case UninitRef:
case UninitThis:
localType = "Ljava/lang/Object;";
break;
case Boolean:
localType = "Z";
break;
case Integer:
case One:
case Unknown:
localType = "I";
break;
case Uninit:
case Conflicted:
if (mInitializedRegisters.remove(localName)) {
mOut.append(".end local ").append(localName)
.append('\n');
}
continue;
case Short:
case PosShort:
localType = "S";
break;
case Byte:
case PosByte:
localType = "B";
break;
case Char:
localType = "C";
break;
case Float:
localType = "F";
break;
case LongHi:
case LongLo:
localType = "J";
break;
case DoubleHi:
case DoubleLo:
localType = "D";
break;
default:
assert false;
}
mInitializedRegisters.add(localName);
mOut.append(".local ").append(localName).append(", ")
.append(localName).append(':').append(localType).append('\n');
}
return false;
}
private boolean processDirective(String line) {
String line2 = line.substring(1);
if (
line2.startsWith("line ") ||
line2.equals("prologue") ||
line2.startsWith("parameter") ||
line2.startsWith("local ") ||
line2.startsWith("end local ")
) {
return false;
}
append(line);
if (line2.equals("end method")) {
return true;
}
if (
line2.startsWith("annotation ") ||
line2.equals("sparse-switch") ||
line2.startsWith("packed-switch ") ||
line2.startsWith("array-data ")
) {
while(true) {
line2 = nextAndAppend();
if (line2.startsWith(".end ")) {
break;
}
}
}
return false;
}
private boolean processInstruction(String line) {
if (mFirstInstruction) {
mOut.append(".prologue\n");
mFirstInstruction = false;
}
mOut.append(".line ").append(mIt.nextIndex()).append('\n')
.append(line).append('\n');
return false;
}
private String next() {
return mIt.next().trim();
}
private String nextAndAppend() {
String line = next();
append(line);
return line;
}
private void append(String append) {
mOut.append(append).append('\n');
}
private final ListIterator<String> mIt;
private final StringBuilder mOut;
private boolean mFirstInstruction = true;
private final Set<String> mInitializedRegisters = new HashSet<String>();
private static final Pattern REGISTER_INFO_PATTERN =
Pattern.compile("((?:p|v)\\d+)=\\(([^)]+)\\);");
}

View File

@ -0,0 +1,85 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.androlib.mod.SmaliMod;
import java.io.*;
import org.antlr.runtime.RecognitionException;
import org.jf.dexlib.CodeItem;
import org.jf.dexlib.DexFile;
import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class DexFileBuilder {
public void addSmaliFile(File smaliFile) throws AndrolibException {
try {
addSmaliFile(new FileInputStream(smaliFile),
smaliFile.getAbsolutePath());
} catch (FileNotFoundException ex) {
throw new AndrolibException(ex);
}
}
public void addSmaliFile(InputStream smaliStream, String name)
throws AndrolibException {
try {
if (! SmaliMod.assembleSmaliFile(
smaliStream, name, mDexFile, false, false, false)) {
throw new AndrolibException(
"Could not smali file: " + smaliStream);
}
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (RecognitionException ex) {
throw new AndrolibException(ex);
}
}
public void writeTo(File dexFile) throws AndrolibException {
try {
OutputStream out = new FileOutputStream(dexFile);
out.write(getAsByteArray());
out.close();
} catch (IOException ex) {
throw new AndrolibException(
"Could not write dex to file: " + dexFile, ex);
}
}
public byte[] getAsByteArray() {
mDexFile.place();
for (CodeItem codeItem: mDexFile.CodeItemsSection.getItems()) {
codeItem.fixInstructions(true, true);
}
mDexFile.place();
ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
mDexFile.writeTo(out);
byte[] bytes = out.toByteArray();
DexFile.calcSignature(bytes);
DexFile.calcChecksum(bytes);
return bytes;
}
private final DexFile mDexFile = new DexFile();
}

View File

@ -0,0 +1,116 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.androlib.res.util.ExtFile;
import brut.directory.DirectoryException;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class SmaliBuilder {
public static void build(ExtFile smaliDir, File dexFile,
HashMap<String, Boolean> flags)
throws AndrolibException {
new SmaliBuilder(smaliDir, dexFile, flags).build();
}
private SmaliBuilder(ExtFile smaliDir, File dexFile, HashMap<String, Boolean> flags) {
mSmaliDir = smaliDir;
mDexFile = dexFile;
mFlags = flags;
}
private void build() throws AndrolibException {
try {
mDexBuilder = new DexFileBuilder();
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
buildFile(fileName);
}
mDexBuilder.writeTo(mDexFile);
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void buildFile(String fileName) throws AndrolibException,
IOException {
File inFile = new File(mSmaliDir, fileName);
InputStream inStream = new FileInputStream(inFile);
if (fileName.endsWith(".smali")) {
mDexBuilder.addSmaliFile(inFile);
return;
}
if (! fileName.endsWith(".java")) {
LOGGER.warning("Unknown file type, ignoring: " + inFile);
return;
}
StringBuilder out = new StringBuilder();
List<String> lines = IOUtils.readLines(inStream);
if (!mFlags.containsKey("debug")) {
final String[] linesArray = lines.toArray(new String[0]);
for (int i = 2; i < linesArray.length - 2; i++) {
out.append(linesArray[i]).append('\n');
}
} else {
lines.remove(lines.size() - 1);
lines.remove(lines.size() - 1);
ListIterator<String> it = lines.listIterator(2);
out.append(".source \"").append(inFile.getName()).append("\"\n");
while (it.hasNext()) {
String line = it.next().trim();
if (line.isEmpty() || line.charAt(0) == '#' ||
line.startsWith(".source")) {
continue;
}
if (line.startsWith(".method ")) {
it.previous();
DebugInjector.inject(it, out);
continue;
}
out.append(line).append('\n');
}
}
mDexBuilder.addSmaliFile(
IOUtils.toInputStream(out.toString()), fileName);
}
private final ExtFile mSmaliDir;
private final File mDexFile;
private final HashMap<String, Boolean> mFlags;
private DexFileBuilder mDexBuilder;
private final static Logger LOGGER =
Logger.getLogger(SmaliBuilder.class.getName());
}

View File

@ -0,0 +1,62 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.src;
import brut.androlib.AndrolibException;
import java.io.File;
import java.io.IOException;
import org.jf.baksmali.baksmali;
import org.jf.baksmali.main;
import org.jf.dexlib.Code.Analysis.ClassPath;
import org.jf.dexlib.DexFile;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class SmaliDecoder {
public static void decode(File apkFile, File outDir, boolean debug, boolean bakdeb)
throws AndrolibException {
new SmaliDecoder(apkFile, outDir, debug, bakdeb).decode();
}
private SmaliDecoder(File apkFile, File outDir, boolean debug, boolean bakdeb) {
mApkFile = apkFile;
mOutDir = outDir;
mDebug = debug;
mBakDeb = bakdeb;
}
private void decode() throws AndrolibException {
if (mDebug) {
ClassPath.dontLoadClassPath = true;
}
try {
baksmali.disassembleDexFile(mApkFile.getAbsolutePath(),
new DexFile(mApkFile), false, mOutDir.getAbsolutePath(), null,
null, null, false, true, true, mBakDeb, false, false,
mDebug ? main.DIFFPRE: 0, false, false, null);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
private final File mApkFile;
private final File mOutDir;
private final boolean mDebug;
private final boolean mBakDeb;
}

View File

@ -0,0 +1,209 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.util.Duo;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class TypeName {
public final String package_;
public final String type;
public final String innerType;
public final int array;
public TypeName(String type, int array) {
this(null, type, null, array);
}
public TypeName(String package_, String type, String innerType, int array) {
this.package_ = package_;
this.type = type;
this.innerType = innerType;
this.array = array;
}
public String getShortenedName() {
return getName("java.lang".equals(package_), isFileOwner());
}
public String getName() {
return getName(false, false);
}
public String getName(boolean excludePackage, boolean separateInner) {
String name =
(package_ == null || excludePackage ? "" : package_ + '.') +
type +
(innerType != null ? (separateInner ? '$' : '.') + innerType : "");
for (int i = 0; i < array; i++) {
name += "[]";
}
return name;
}
public String getJavaFilePath() {
return getFilePath(isFileOwner()) + ".java";
}
public String getSmaliFilePath() {
return getFilePath(true) + ".smali";
}
public String getFilePath(boolean separateInner) {
return package_.replace('.', File.separatorChar) + File.separatorChar
+ type + (separateInner && isInner() ? "$" + innerType : "");
}
public boolean isInner() {
return innerType != null;
}
public boolean isArray() {
return array != 0;
}
public boolean isFileOwner() {
if (mIsFileOwner == null) {
mIsFileOwner = true;
if (isInner()) {
char c = innerType.charAt(0);
if (c < '0' || c > '9') {
mIsFileOwner = false;
}
}
}
return mIsFileOwner;
}
@Override
public String toString() {
return getName();
}
public static TypeName fromInternalName(String internal)
throws AndrolibException {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
if (duo.m2 != internal.length()) {
throw new AndrolibException(
"Invalid internal name: " + internal);
}
return duo.m1;
}
public static List<TypeName> listFromInternalName(String internal)
throws AndrolibException {
List<TypeName> types = new ArrayList<TypeName>();
while (! internal.isEmpty()) {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
types.add(duo.m1);
internal = internal.substring(duo.m2);
}
return types;
}
public static Duo<TypeName, Integer> fetchFromInternalName(String internal)
throws AndrolibException {
String origInternal = internal;
int array = 0;
boolean isArray = false;
do {
if (internal.isEmpty()) {
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
isArray = internal.charAt(0) == '[';
if (isArray) {
array++;
internal = internal.substring(1);
}
} while (isArray);
int length = array + 1;
String package_ = null;
String type = null;
String innerType = null;
switch (internal.charAt(0)) {
case 'B':
type = "byte";
break;
case 'C':
type = "char";
break;
case 'D':
type = "double";
break;
case 'F':
type = "float";
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
case 'S':
type = "short";
break;
case 'Z':
type = "boolean";
break;
case 'V':
type = "void";
break;
case 'L':
int pos = internal.indexOf(';');
if (pos == -1) {
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
length += pos;
internal = internal.substring(1, pos);
pos = internal.lastIndexOf('/');
if (pos == -1) {
package_ = "";
type = internal;
} else {
package_ = internal.substring(0, pos).replace('/', '.');
type = internal.substring(pos + 1);
}
pos = type.indexOf('$');
if (pos != -1) {
innerType = type.substring(pos + 1);
type = type.substring(0, pos);
}
break;
default:
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
return new Duo<TypeName, Integer>(
new TypeName(package_, type, innerType, array), length);
}
private Boolean mIsFileOwner;
}

View File

@ -0,0 +1,319 @@
/*
* @(#)LEDataInputStream.java
*
* Summary: Little-Endian version of DataInputStream.
*
* Copyright: (c) 1998-2010 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.1+
*
* Created with: IntelliJ IDEA IDE.
*
* Version History:
* 1.8 2007-05-24
*/
package com.mindprod.ledatastream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Little-Endian version of DataInputStream.
* <p/>
* Very similar to DataInputStream except it reads
* little-endian instead of big-endian binary data. We can't extend
* DataInputStream directly since it has only final methods, though
* DataInputStream itself is not final. This forces us implement
* LEDataInputStream with a DataInputStream object, and use wrapper methods.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.8 2007-05-24
* @since 1998
*/
public final class LEDataInputStream implements DataInput
{
// ------------------------------ CONSTANTS ------------------------------
/**
* undisplayed copyright notice.
*
* @noinspection UnusedDeclaration
*/
private static final String EMBEDDED_COPYRIGHT =
"copyright (c) 1999-2010 Roedy Green, Canadian Mind Products, http://mindprod.com";
// ------------------------------ FIELDS ------------------------------
/**
* to get at the big-Endian methods of a basic DataInputStream
*
* @noinspection WeakerAccess
*/
protected final DataInputStream dis;
/**
* to get at the a basic readBytes method.
*
* @noinspection WeakerAccess
*/
protected final InputStream is;
/**
* work array for buffering input.
*
* @noinspection WeakerAccess
*/
protected final byte[] work;
// -------------------------- PUBLIC STATIC METHODS --------------------------
/**
* Note. This is a STATIC method!
*
* @param in stream to read UTF chars from (endian irrelevant)
*
* @return string from stream
* @throws IOException if read fails.
*/
public static String readUTF( DataInput in ) throws IOException
{
return DataInputStream.readUTF( in );
}
// -------------------------- PUBLIC INSTANCE METHODS --------------------------
/**
* constructor.
*
* @param in binary inputstream of little-endian data.
*/
public LEDataInputStream( InputStream in )
{
this.is = in;
this.dis = new DataInputStream( in );
work = new byte[8];
}
/**
* close.
*
* @throws IOException if close fails.
*/
public final void close() throws IOException
{
dis.close();
}
/**
* Read bytes. Watch out, read may return fewer bytes than requested.
*
* @param ba where the bytes go.
* @param off offset in buffer, not offset in file.
* @param len count of bytes to read.
*
* @return how many bytes read.
* @throws IOException if read fails.
*/
public final int read( byte ba[], int off, int len ) throws IOException
{
// For efficiency, we avoid one layer of wrapper
return is.read( ba, off, len );
}
/**
* read only a one-byte boolean.
*
* @return true or false.
* @throws IOException if read fails.
* @see java.io.DataInput#readBoolean()
*/
public final boolean readBoolean() throws IOException
{
return dis.readBoolean();
}
/**
* read byte.
*
* @return the byte read.
* @throws IOException if read fails.
* @see java.io.DataInput#readByte()
*/
public final byte readByte() throws IOException
{
return dis.readByte();
}
/**
* Read on char. like DataInputStream.readChar except little endian.
*
* @return little endian 16-bit unicode char from the stream.
* @throws IOException if read fails.
*/
public final char readChar() throws IOException
{
dis.readFully( work, 0, 2 );
return ( char ) ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) );
}
/**
* Read a double. like DataInputStream.readDouble except little endian.
*
* @return little endian IEEE double from the datastream.
* @throws IOException
*/
public final double readDouble() throws IOException
{
return Double.longBitsToDouble( readLong() );
}
/**
* Read one float. Like DataInputStream.readFloat except little endian.
*
* @return little endian IEEE float from the datastream.
* @throws IOException if read fails.
*/
public final float readFloat() throws IOException
{
return Float.intBitsToFloat( readInt() );
}
/**
* Read bytes until the array is filled.
*
* @see java.io.DataInput#readFully(byte[])
*/
public final void readFully( byte ba[] ) throws IOException
{
dis.readFully( ba, 0, ba.length );
}
/**
* Read bytes until the count is satisfied.
*
* @throws IOException if read fails.
* @see java.io.DataInput#readFully(byte[],int,int)
*/
public final void readFully( byte ba[],
int off,
int len ) throws IOException
{
dis.readFully( ba, off, len );
}
/**
* Read an int, 32-bits. Like DataInputStream.readInt except little endian.
*
* @return little-endian binary int from the datastream
* @throws IOException if read fails.
*/
public final int readInt() throws IOException
{
dis.readFully( work, 0, 4 );
return ( work[ 3 ] ) << 24
| ( work[ 2 ] & 0xff ) << 16
| ( work[ 1 ] & 0xff ) << 8
| ( work[ 0 ] & 0xff );
}
/**
* Read a line.
*
* @return a rough approximation of the 8-bit stream as a 16-bit unicode string
* @throws IOException
* @noinspection deprecation
* @deprecated This method does not properly convert bytes to characters. Use a Reader instead with a little-endian
* encoding.
*/
public final String readLine() throws IOException
{
return dis.readLine();
}
/**
* read a long, 64-bits. Like DataInputStream.readLong except little endian.
*
* @return little-endian binary long from the datastream.
* @throws IOException
*/
public final long readLong() throws IOException
{
dis.readFully( work, 0, 8 );
return ( long ) ( work[ 7 ] ) << 56
|
/* long cast needed or shift done modulo 32 */
( long ) ( work[ 6 ] & 0xff ) << 48
| ( long ) ( work[ 5 ] & 0xff ) << 40
| ( long ) ( work[ 4 ] & 0xff ) << 32
| ( long ) ( work[ 3 ] & 0xff ) << 24
| ( long ) ( work[ 2 ] & 0xff ) << 16
| ( long ) ( work[ 1 ] & 0xff ) << 8
| ( long ) ( work[ 0 ] & 0xff );
}
/**
* Read short, 16-bits. Like DataInputStream.readShort except little endian.
*
* @return little endian binary short from stream.
* @throws IOException if read fails.
*/
public final short readShort() throws IOException
{
dis.readFully( work, 0, 2 );
return ( short ) ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) );
}
/**
* Read UTF counted string.
*
* @return String read.
*/
public final String readUTF() throws IOException
{
return dis.readUTF();
}
/**
* Read an unsigned byte. Note: returns an int, even though says Byte (non-Javadoc)
*
* @throws IOException if read fails.
* @see java.io.DataInput#readUnsignedByte()
*/
public final int readUnsignedByte() throws IOException
{
return dis.readUnsignedByte();
}
/**
* Read an unsigned short, 16 bits. Like DataInputStream.readUnsignedShort except little endian. Note, returns int
* even though it reads a short.
*
* @return little-endian int from the stream.
* @throws IOException if read fails.
*/
public final int readUnsignedShort() throws IOException
{
dis.readFully( work, 0, 2 );
return ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) );
}
/**
* Skip over bytes in the stream. See the general contract of the <code>skipBytes</code> method of
* <code>DataInput</code>.
* <p/>
* Bytes for this operation are read from the contained input stream.
*
* @param n the number of bytes to be skipped.
*
* @return the actual number of bytes skipped.
* @throws IOException if an I/O error occurs.
*/
public final int skipBytes( int n ) throws IOException
{
return dis.skipBytes( n );
}
}

View File

@ -0,0 +1,2 @@
application.version=${version}
git.commit.id.abbrev = ${git.commit.id.abbrev}

View File

@ -0,0 +1,177 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import brut.androlib.res.util.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import java.io.*;
import java.util.HashMap;
import java.util.logging.Logger;
import org.custommonkey.xmlunit.*;
import org.junit.*;
import static org.junit.Assert.*;
import org.xml.sax.SAXException;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class BuildAndDecodeTest {
@BeforeClass
public static void beforeClass() throws BrutException {
sTmpDir = new ExtFile(OS.createTempDirectory());
sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig");
sTestNewDir = new ExtFile(sTmpDir, "testapp-new");
File testApk = new File(sTmpDir, "testapp.apk");
LOGGER.info("Unpacking testapp...");
TestUtils.copyResourceDir(BuildAndDecodeTest.class,
"brut/apktool/testapp/", sTestOrigDir);
LOGGER.info("Building testapp.apk...");
ExtFile blank = null;
new Androlib().build(sTestOrigDir, testApk, BuildAndDecodeTest.returnStock(),blank);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
apkDecoder.setOutDir(sTestNewDir);
apkDecoder.decode();
}
@AfterClass
public static void afterClass() throws BrutException {
OS.rmdir(sTmpDir);
}
@Test
public void valuesArraysTest() throws BrutException {
compareValuesFiles("values-mcc001/arrays.xml");
compareValuesFiles("values-mcc002/arrays.xml");
}
@Test
public void valuesBoolsTest() throws BrutException {
compareValuesFiles("values-mcc001/bools.xml");
}
@Test
public void valuesColorsTest() throws BrutException {
compareValuesFiles("values-mcc001/colors.xml");
}
@Test
public void valuesDimensTest() throws BrutException {
compareValuesFiles("values-mcc001/dimens.xml");
}
@Test
public void valuesIdsTest() throws BrutException {
compareValuesFiles("values-mcc001/ids.xml");
}
@Test
public void valuesIntegersTest() throws BrutException {
compareValuesFiles("values-mcc001/integers.xml");
}
@Test
public void valuesStringsTest() throws BrutException {
compareValuesFiles("values-mcc001/strings.xml");
}
@Test
public void valuesReferencesTest() throws BrutException {
compareValuesFiles("values-mcc002/strings.xml");
}
@Test
public void crossTypeTest() throws BrutException {
compareValuesFiles("values-mcc003/strings.xml");
compareValuesFiles("values-mcc003/integers.xml");
compareValuesFiles("values-mcc003/bools.xml");
}
@Test
public void xmlLiteralsTest() throws BrutException {
compareXmlFiles("res/xml/literals.xml");
}
@Test
public void xmlReferencesTest() throws BrutException {
compareXmlFiles("res/xml/references.xml");
}
@Test
public void qualifiersTest() throws BrutException {
compareValuesFiles("values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp" +
"-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key" +
"-navhidden-dpad/strings.xml");
}
private void compareValuesFiles(String path) throws BrutException {
compareXmlFiles("res/" + path,
new ElementNameAndAttributeQualifier("name"));
}
private void compareXmlFiles(String path) throws BrutException {
compareXmlFiles(path, null);
}
private void compareXmlFiles(String path,
ElementQualifier qualifier) throws BrutException {
DetailedDiff diff;
try {
Reader control = new FileReader(
new File(sTestOrigDir, path));
Reader test = new FileReader(new File(sTestNewDir, path));
diff = new DetailedDiff(new Diff(control, test));
} catch (SAXException ex) {
throw new BrutException(ex);
} catch (IOException ex) {
throw new BrutException(ex);
}
if (qualifier != null) {
diff.overrideElementQualifier(qualifier);
}
assertTrue(path + ": " +
diff.getAllDifferences().toString(), diff.similar());
}
private static HashMap<String, Boolean> returnStock() throws BrutException {
HashMap<String, Boolean> tmp = new HashMap<String, Boolean>();
tmp.put("forceBuildAll", false);
tmp.put("debug", false);
tmp.put("verbose", false);
tmp.put("injectOriginal", false);
tmp.put("framework", false);
tmp.put("update", false);
return tmp;
}
private static ExtFile sTmpDir;
private static ExtFile sTestOrigDir;
private static ExtFile sTestNewDir;
private final static Logger LOGGER =
Logger.getLogger(BuildAndDecodeTest.class.getName());
}

View File

@ -0,0 +1,138 @@
/**
* Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
*
* 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 brut.androlib;
import brut.common.BrutException;
import brut.directory.*;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import org.custommonkey.xmlunit.ElementQualifier;
import org.w3c.dom.Element;
import org.xmlpull.v1.*;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public abstract class TestUtils {
public static Map<String, String> parseStringsXml(File file)
throws BrutException {
try {
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
xpp.setInput(new FileReader(file));
int eventType;
String key = null;
Map<String, String> map = new HashMap<String, String>();
while ((eventType = xpp.next()) != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
if ("string".equals(xpp.getName())) {
int attrCount = xpp.getAttributeCount();
for (int i = 0; i < attrCount; i++) {
if ("name".equals(xpp.getAttributeName(i))) {
key = xpp.getAttributeValue(i);
break;
}
}
}
break;
case XmlPullParser.END_TAG:
if ("string".equals(xpp.getName())) {
key = null;
}
break;
case XmlPullParser.TEXT:
if (key != null) {
map.put(key, xpp.getText());
}
break;
}
}
return map;
} catch (IOException ex) {
throw new BrutException(ex);
} catch (XmlPullParserException ex) {
throw new BrutException(ex);
}
}
/* TODO: move to brut.util.Jar - it's not possible for now, because below
* implementation uses brut.dir. I think I should merge all my projects to
* single brut.common .
*/
public static void copyResourceDir(Class class_, String dirPath, File out)
throws BrutException {
if (! out.exists()) {
out.mkdirs();
}
copyResourceDir(class_, dirPath, new FileDirectory(out));
}
public static void copyResourceDir(Class class_, String dirPath,
Directory out) throws BrutException {
if (class_ == null) {
class_ = Class.class;
}
URL dirURL = class_.getClassLoader().getResource(dirPath);
if (dirURL != null && dirURL.getProtocol().equals("file")) {
DirUtil.copyToDir(new FileDirectory(dirURL.getFile()), out);
return;
}
if (dirURL == null) {
String className = class_.getName().replace(".", "/") + ".class";
dirURL = class_.getClassLoader().getResource(className);
}
if (dirURL.getProtocol().equals("jar")) {
String jarPath;
try {
jarPath = URLDecoder.decode(dirURL.getPath().substring(
5, dirURL.getPath().indexOf("!")), "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new BrutException(ex);
}
DirUtil.copyToDir(new FileDirectory(jarPath), out);
}
}
public static class ResValueElementQualifier implements ElementQualifier {
public boolean qualifyForComparison(Element control, Element test) {
String controlType = control.getTagName();
if ("item".equals(controlType)) {
controlType = control.getAttribute("type");
}
String testType = test.getTagName();
if ("item".equals(testType)) {
testType = test.getAttribute("type");
}
return controlType.equals(testType) && control.getAttribute("name")
.equals(test.getAttribute("name"));
}
}
}

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="brut.apktool.testapp"
xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@ -0,0 +1,6 @@
version: 1.5.0
apkFileName: testapp.apk
isFrameworkApk: false
usesFramework:
ids:
- 1

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_array1">
<item>TEST1</item>
<item>TEST2</item>
<item>TEST3</item>
<item>%2$s foo %1$d</item>
</string-array>
<integer-array name="test_array2">
<item>-1</item>
<item>0</item>
<item>1</item>
</integer-array>
<array name="test_array3">
<item></item>
<item>true</item>
<item>TEST</item>
<item>5</item>
<item>5.5</item>
<item>10.0sp</item>
<item>#ff123456</item>
</array>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="test_bool1">false</bool>
<bool name="test_bool2">true</bool>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="test_color1">#ff123456</color>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="test_dimen1">10.0dip</dimen>
<dimen name="test_dimen2">10.0sp</dimen>
<dimen name="test_dimen3">10.0pt</dimen>
<dimen name="test_dimen4">10.0px</dimen>
<dimen name="test_dimen5">10.0mm</dimen>
<dimen name="test_dimen6">10.0in</dimen>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="test_id1" />
<item type="id" name="test_id2" />
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="test_integer1">-1</integer>
<integer name="test_integer2">0</integer>
<integer name="test_integer3">1</integer>
</resources>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test_string1"></string>
<string name="test_string2">Lorem ipsum...</string>
<string name="test_string3">\@</string>
<string name="test_string4">\?</string>
<string name="test_string5">\#ff123456</string>
<string name="test_string7">&amp;</string>
<string name="test_string8">"'"</string>
<string name="test_string9">\"</string>
<string name="test_string10">\u0005</string>
<string name="test_string11">" foo bar "</string>
<string name="test_string12">"foo
bar"</string>
<string name="test_string13">" foo"<b>bar <i> baz </i></b> <u>foo</u></string>
<string name="test_string14">foo<sometag someattr1="someval1" someattr2="someval2">bar</sometag>baz</string>
<string name="test_string16">foo<b>bar<i>"b
az"</i></b>foo</string>
<string name="test_string17" formatted="false">%d of %d</string>
<string name="test_string18">foo %d bar %</string>
<string name="test_string19">%2$s foo %1$d</string>
<string name="test_string20" formatted="false">%-e foo %,d</string>
<string name="test_string21">%2$-e foo %1$,d</string>
<string name="test_string22" formatted="false">%02d foo %01d</string>
<string name="test_string23" formatted="false">%d foo %1</string>
<string name="test_string24" formatted="false">%1% foo %2%</string>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_array4">
<item>@string/test_string1</item>
<item>@string/test_string2</item>
</string-array>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test_reference1">@string/test1</string>
<string name="test_reference2">@*android:string/ok</string>
<string name="test_reference3">?android:textStyle</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="bool" name="test_crossType_str2bool">TEST</item>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="integer" name="test_crossType_str2int">TEST</item>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="string" name="test_crossType_bool2str">true</item>
<item type="string" name="test_crossType_int2str">5</item>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1" />
</resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
test1=""
test2="Lorem ipsum"
test3="\@"
test4="\?"
test5="#ff123456"
test6="\#ff123456"
test7="&amp;"
test8="'"
test9="&quot;"
test10="\u0005"
test11=" foo bar "
test12="foo \n bar"
test15="005"
/>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
test1="@string/test1"
test2="@*android:string/ok"
test3="?android:textStyle"
/>

View File

@ -0,0 +1,14 @@
Copyright 2011 Ryszard Wiśniewski <brut.alll@gmail.com>
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.