mirror of
https://github.com/revanced/Apktool.git
synced 2024-09-04 21:46:30 +02:00
cleanup of various functions
Removed attr sorting that was never enabled and never worked, added @todo elements for unknown file handling, updated usage output
This commit is contained in:
parent
0ca74eca67
commit
a48c11dc1c
@ -47,17 +47,17 @@ import org.jf.util.ConsoleUtil;
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException,
|
||||
InterruptedException, BrutException {
|
||||
|
||||
|
||||
// set verbosity default
|
||||
Verbosity verbosity = Verbosity.NORMAL;
|
||||
|
||||
|
||||
// cli parser
|
||||
CommandLineParser parser = new PosixParser();
|
||||
CommandLine commandLine = null;
|
||||
|
||||
|
||||
// load options
|
||||
_Options();
|
||||
|
||||
|
||||
try {
|
||||
commandLine = parser.parse(allOptions, args, false);
|
||||
} catch (ParseException ex) {
|
||||
@ -65,7 +65,7 @@ public class Main {
|
||||
usage(commandLine);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// check for verbose / quiet
|
||||
if (commandLine.hasOption("-v") || commandLine.hasOption("--verbose")) {
|
||||
verbosity = Verbosity.VERBOSE;
|
||||
@ -73,7 +73,7 @@ public class Main {
|
||||
verbosity = Verbosity.QUIET;
|
||||
}
|
||||
setupLogging(verbosity);
|
||||
|
||||
|
||||
// check for advance mode
|
||||
if (commandLine.hasOption("advance") || commandLine.hasOption("advanced")) {
|
||||
setAdvanceMode(true);
|
||||
@ -81,7 +81,7 @@ public class Main {
|
||||
|
||||
// @todo use new ability of apache-commons-cli to check hasOption for non-prefixed items
|
||||
boolean cmdFound = false;
|
||||
for (String opt : commandLine.getArgs()) {
|
||||
for (String opt : commandLine.getArgs()) {
|
||||
if (opt.equalsIgnoreCase("d") || opt.equalsIgnoreCase("decode")) {
|
||||
cmdDecode(commandLine);
|
||||
cmdFound = true;
|
||||
@ -96,7 +96,7 @@ public class Main {
|
||||
cmdFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if no commands ran, run the version / usage check.
|
||||
if (cmdFound == false) {
|
||||
if (commandLine.hasOption("version") || commandLine.hasOption("version")) {
|
||||
@ -109,11 +109,11 @@ public class Main {
|
||||
|
||||
private static void cmdDecode(CommandLine cli) throws AndrolibException {
|
||||
ApkDecoder decoder = new ApkDecoder();
|
||||
|
||||
|
||||
int paraCount = cli.getArgList().size();
|
||||
String apkName = (String) cli.getArgList().get(paraCount - 1);
|
||||
File outDir = null;
|
||||
|
||||
|
||||
// check for options
|
||||
if (cli.hasOption("s") || cli.hasOption("no-src")) {
|
||||
decoder.setDecodeSources(ApkDecoder.DECODE_SOURCES_NONE);
|
||||
@ -142,18 +142,18 @@ public class Main {
|
||||
if (cli.hasOption("o") || cli.hasOption("output")) {
|
||||
decoder.setOutDir(new File(cli.getOptionValue("o")));
|
||||
} else {
|
||||
|
||||
|
||||
// make out folder manually using name of apk
|
||||
String outName = apkName;
|
||||
outName = outName.endsWith(".apk") ? outName.substring(0,
|
||||
outName.length() - 4) : outName + ".out";
|
||||
|
||||
|
||||
// make file from path
|
||||
outName = new File(outName).getName();
|
||||
outDir = new File(outName);
|
||||
decoder.setOutDir(outDir);
|
||||
}
|
||||
|
||||
|
||||
decoder.setApkFile(new File(apkName));
|
||||
|
||||
try {
|
||||
@ -189,7 +189,7 @@ public class Main {
|
||||
String appDirName = ".";
|
||||
File outFile = null;
|
||||
Androlib instance = new Androlib();
|
||||
|
||||
|
||||
// hold all the fields
|
||||
HashMap<String, Boolean> flags = new HashMap<String, Boolean>();
|
||||
flags.put("forceBuildAll", false);
|
||||
@ -223,11 +223,11 @@ public class Main {
|
||||
} else {
|
||||
outFile = null;
|
||||
}
|
||||
|
||||
|
||||
if (apkName != null) {
|
||||
appDirName = apkName;
|
||||
}
|
||||
|
||||
|
||||
// try and build apk
|
||||
instance.build(new File(appDirName), outFile, flags,mAaptPath);
|
||||
}
|
||||
@ -238,7 +238,7 @@ public class Main {
|
||||
String apkName = (String) cli.getArgList().get(paraCount - 1);
|
||||
String tag = null;
|
||||
String frame_path = null;
|
||||
|
||||
|
||||
if (cli.hasOption("p") || cli.hasOption("frame-path")) {
|
||||
frame_path = cli.getOptionValue("p");
|
||||
}
|
||||
@ -262,112 +262,112 @@ public class Main {
|
||||
|
||||
@SuppressWarnings("static-access")
|
||||
private static void _Options() {
|
||||
|
||||
|
||||
// create options
|
||||
Option versionOption = OptionBuilder.withLongOpt("version")
|
||||
.withDescription("prints the version then exits")
|
||||
.create("version");
|
||||
|
||||
|
||||
Option advanceOption = OptionBuilder.withLongOpt("advanced")
|
||||
.withDescription("prints advance information.")
|
||||
.create("advance");
|
||||
|
||||
|
||||
Option noSrcOption = OptionBuilder.withLongOpt("no-src")
|
||||
.withDescription("Do not decode sources.")
|
||||
.create("s");
|
||||
|
||||
|
||||
Option noResOption = OptionBuilder.withLongOpt("no-res")
|
||||
.withDescription("Do not decode resources.")
|
||||
.create("r");
|
||||
|
||||
|
||||
Option debugDecOption = OptionBuilder.withLongOpt("debug")
|
||||
.withDescription("Decode in debug mode. Check project page for more info.")
|
||||
.create("d");
|
||||
|
||||
|
||||
Option debugBuiOption = OptionBuilder.withLongOpt("debug")
|
||||
.withDescription("Builds in debug mode. Check project page for more info.")
|
||||
.create("d");
|
||||
|
||||
|
||||
Option noDbgOption = OptionBuilder.withLongOpt("no-debug-info")
|
||||
.withDescription("don't write out debug info (.local, .param, .line, etc.)")
|
||||
.create("b");
|
||||
|
||||
|
||||
Option forceDecOption = OptionBuilder.withLongOpt("force")
|
||||
.withDescription("Force delete destination directory.")
|
||||
.create("f");
|
||||
|
||||
|
||||
Option frameTagOption = OptionBuilder.withLongOpt("frame-tag")
|
||||
.withDescription("Uses framework files tagged by <tag>.")
|
||||
.hasArg(true)
|
||||
.withArgName("tag")
|
||||
.create("t");
|
||||
|
||||
|
||||
Option frameDirOption = OptionBuilder.withLongOpt("frame-path")
|
||||
.withDescription("Uses framework files located in <dir>.")
|
||||
.hasArg(true)
|
||||
.withArgName("dir")
|
||||
.create("p");
|
||||
|
||||
|
||||
Option keepResOption = OptionBuilder.withLongOpt("keep-broken-res")
|
||||
.withDescription("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.")
|
||||
.create("k");
|
||||
|
||||
|
||||
Option forceBuiOption = OptionBuilder.withLongOpt("force-all")
|
||||
.withDescription("Skip changes detection and build all files.")
|
||||
.create("f");
|
||||
|
||||
|
||||
Option aaptOption = OptionBuilder.withLongOpt("aapt")
|
||||
.hasArg(true)
|
||||
.withArgName("loc")
|
||||
.withDescription("Loads aapt from specified location.")
|
||||
.create("a");
|
||||
|
||||
|
||||
Option originalOption = OptionBuilder.withLongOpt("copy-original")
|
||||
.withDescription("Copies original AndroidManifest.xml and META-INF. See project page for more info.")
|
||||
.create("c");
|
||||
|
||||
|
||||
Option tagOption = OptionBuilder.withLongOpt("tag")
|
||||
.withDescription("Tag frameworks using <tag>.")
|
||||
.hasArg(true)
|
||||
.withArgName("tag")
|
||||
.create("t");
|
||||
|
||||
|
||||
Option outputBuiOption = OptionBuilder.withLongOpt("output")
|
||||
.withDescription("The name of apk that gets written. Default is dist/name.apk")
|
||||
.hasArg(true)
|
||||
.withArgName("dir")
|
||||
.create("o");
|
||||
|
||||
|
||||
Option outputDecOption = OptionBuilder.withLongOpt("output")
|
||||
.withDescription("The name of folder that gets written. Default is apk.out")
|
||||
.hasArg(true)
|
||||
.withArgName("dir")
|
||||
.create("o");
|
||||
|
||||
|
||||
Option quietOption = OptionBuilder.withLongOpt("quiet")
|
||||
.create("q");
|
||||
|
||||
|
||||
Option verboseOption = OptionBuilder.withLongOpt("verbose")
|
||||
.create("v");
|
||||
|
||||
|
||||
// check for advance mode
|
||||
if (advanceMode) {
|
||||
DecodeOptions.addOption(debugDecOption);
|
||||
DecodeOptions.addOption(noDbgOption);
|
||||
DecodeOptions.addOption(keepResOption);
|
||||
|
||||
|
||||
BuildOptions.addOption(debugBuiOption);
|
||||
BuildOptions.addOption(aaptOption);
|
||||
BuildOptions.addOption(originalOption);
|
||||
}
|
||||
|
||||
|
||||
// add global options
|
||||
normalOptions.addOption(versionOption);
|
||||
normalOptions.addOption(advanceOption);
|
||||
|
||||
|
||||
// add basic decode options
|
||||
DecodeOptions.addOption(frameTagOption);
|
||||
DecodeOptions.addOption(outputDecOption);
|
||||
@ -375,16 +375,16 @@ public class Main {
|
||||
DecodeOptions.addOption(forceDecOption);
|
||||
DecodeOptions.addOption(noSrcOption);
|
||||
DecodeOptions.addOption(noResOption);
|
||||
|
||||
|
||||
// add basic build options
|
||||
BuildOptions.addOption(outputBuiOption);
|
||||
BuildOptions.addOption(frameDirOption);
|
||||
BuildOptions.addOption(forceBuiOption);
|
||||
|
||||
|
||||
// add basic framework options
|
||||
frameOptions.addOption(tagOption);
|
||||
frameOptions.addOption(frameDirOption);
|
||||
|
||||
|
||||
// add all, loop existing cats then manually add advance
|
||||
for (Object op : normalOptions.getOptions()) {
|
||||
allOptions.addOption((Option)op);
|
||||
@ -415,20 +415,20 @@ public class Main {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void usage(CommandLine commandLine) {
|
||||
|
||||
|
||||
// load basicOptions
|
||||
_Options();
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
|
||||
|
||||
// max their window to 120, if small.
|
||||
int consoleWidth = ConsoleUtil.getConsoleWidth();
|
||||
if (consoleWidth <= 0) {
|
||||
consoleWidth = 120;
|
||||
}
|
||||
formatter.setWidth(consoleWidth);
|
||||
|
||||
|
||||
// print out license info prior to formatter.
|
||||
System.out.println(
|
||||
"Apktool v" + Androlib.getVersion() + " - a tool for reengineering Android apk files\n" +
|
||||
@ -441,19 +441,19 @@ public class Main {
|
||||
}else {
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
|
||||
// 4 usage outputs (general, frameworks, decode, build)
|
||||
formatter.printHelp("apktool " + verbosityHelp(), normalOptions);
|
||||
formatter.printHelp("apktool " + verbosityHelp() + "if|install-framework [options] <framework.apk>", frameOptions);
|
||||
formatter.printHelp("apktool " + verbosityHelp() + "d[ecode] [options] <file_apk>", DecodeOptions);
|
||||
formatter.printHelp("apktool " + verbosityHelp() + "b[uild] [options] <app_path>", BuildOptions);
|
||||
if (advanceMode) {
|
||||
formatter.printHelp("apktool " + verbosityHelp() + "publicize-resources <file_path>",
|
||||
formatter.printHelp("apktool " + verbosityHelp() + "publicize-resources <file_path>",
|
||||
"Make all framework resources public.", emptyOptions, null);
|
||||
} else {
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
|
||||
// print out more information
|
||||
System.out.println(
|
||||
"For additional info, see: http://code.google.com/p/android-apktool/ \n"
|
||||
@ -487,9 +487,9 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAdvanceMode() {
|
||||
return advanceMode;
|
||||
}
|
||||
public static boolean isAdvanceMode() {
|
||||
return advanceMode;
|
||||
}
|
||||
|
||||
public static void setAdvanceMode(boolean advanceMode) {
|
||||
Main.advanceMode = advanceMode;
|
||||
@ -507,7 +507,7 @@ public class Main {
|
||||
private final static Options frameOptions;
|
||||
private final static Options allOptions;
|
||||
private final static Options emptyOptions;
|
||||
|
||||
|
||||
static {
|
||||
//normal and advance usage output
|
||||
normalOptions = new Options();
|
||||
|
@ -546,7 +546,9 @@ public class Androlib {
|
||||
|
||||
// check if file exists
|
||||
if (new File(appDir,entry.getKey()).isFile()) {
|
||||
// apkZipFile.
|
||||
|
||||
// @todo read ZipFile and inject file into
|
||||
// might need to use Zip4j
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
|
@ -31,17 +31,17 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
/**
|
||||
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
* @author Dmitry Skiba
|
||||
*
|
||||
*
|
||||
* Binary xml files parser.
|
||||
*
|
||||
*
|
||||
* Parser has only two states: (1) Operational state, which parser
|
||||
* obtains after first successful call to next() and retains until
|
||||
* open(), close(), or failed call to next(). (2) Closed state, which
|
||||
* parser obtains after open(), close(), or failed call to next(). In
|
||||
* this state methods return invalid values or throw exceptions.
|
||||
*
|
||||
*
|
||||
* TODO: * check all methods in closed state
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
@ -283,7 +283,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
if (m_event != START_TAG) {
|
||||
return -1;
|
||||
}
|
||||
return m_attributes.length / ATTRIBUTE_LENGHT;
|
||||
return m_attributes.length / ATTRIBUTE_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -362,13 +362,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
getAttributePrefix(index), getAttributeName(index),
|
||||
valueData), ex);
|
||||
}
|
||||
} else {
|
||||
if (valueType == TypedValue.TYPE_STRING) {
|
||||
return ResXmlEncoders.escapeXmlChars(m_strings
|
||||
.getString(valueRaw));
|
||||
}
|
||||
}
|
||||
|
||||
return TypedValue.coerceToString(valueType, valueData);
|
||||
}
|
||||
|
||||
@ -574,9 +568,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
* methods search all depth frames starting from the last namespace pair of
|
||||
* current depth frame. All functions that operate with int, use -1 as
|
||||
* 'invalid value'.
|
||||
*
|
||||
*
|
||||
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
|
||||
*
|
||||
*
|
||||
*/
|
||||
private static final class NamespaceStack {
|
||||
|
||||
@ -830,7 +824,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Current event is not START_TAG.");
|
||||
}
|
||||
int offset = index * ATTRIBUTE_LENGHT;
|
||||
int offset = index * ATTRIBUTE_LENGTH;
|
||||
if (offset >= m_attributes.length) {
|
||||
throw new IndexOutOfBoundsException("Invalid attribute index ("
|
||||
+ index + ").");
|
||||
@ -847,11 +841,11 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
return -1;
|
||||
}
|
||||
int uri = (namespace != null) ? m_strings.find(namespace) : -1;
|
||||
for (int o = 0; o != m_attributes.length; o += ATTRIBUTE_LENGHT) {
|
||||
for (int o = 0; o != m_attributes.length; o += ATTRIBUTE_LENGTH) {
|
||||
if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
|
||||
&& (uri == -1 || uri == m_attributes[o
|
||||
+ ATTRIBUTE_IX_NAMESPACE_URI])) {
|
||||
return o / ATTRIBUTE_LENGHT;
|
||||
return o / ATTRIBUTE_LENGTH;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@ -874,7 +868,8 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
m_reader.skipCheckInt(CHUNK_AXML_FILE);
|
||||
/*
|
||||
* chunkSize
|
||||
*/m_reader.skipInt();
|
||||
*/
|
||||
m_reader.skipInt();
|
||||
m_strings = StringBlock.read(m_reader);
|
||||
m_namespaces.increaseDepth();
|
||||
m_operational = true;
|
||||
@ -960,10 +955,10 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
m_styleAttribute = (m_classAttribute >>> 16) - 1;
|
||||
m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
|
||||
m_attributes = m_reader.readIntArray(attributeCount
|
||||
* ATTRIBUTE_LENGHT);
|
||||
* ATTRIBUTE_LENGTH);
|
||||
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length;) {
|
||||
m_attributes[i] = (m_attributes[i] >>> 24);
|
||||
i += ATTRIBUTE_LENGHT;
|
||||
i += ATTRIBUTE_LENGTH;
|
||||
}
|
||||
m_namespaces.increaseDepth();
|
||||
m_event = START_TAG;
|
||||
@ -988,62 +983,6 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatArray(int[] array, int min, int max) {
|
||||
if (max > array.length) {
|
||||
max = array.length;
|
||||
}
|
||||
if (min < 0) {
|
||||
min = 0;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer("[");
|
||||
int i = min;
|
||||
while (true) {
|
||||
sb.append(array[i]);
|
||||
i++;
|
||||
if (i < max) {
|
||||
sb.append(", ");
|
||||
} else {
|
||||
sb.append("]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean compareAttr(int[] attr1, int[] attr2) {
|
||||
// TODO: sort Attrs
|
||||
/*
|
||||
* ATTRIBUTE_IX_VALUE_TYPE == TYPE_STRING : ATTRIBUTE_IX_VALUE_STRING :
|
||||
* ATTRIBUTE_IX_NAMESPACE_URI ATTRIBUTE_IX_NAMESPACE_URI :
|
||||
* ATTRIBUTE_IX_NAME id
|
||||
*/
|
||||
if (attr1[ATTRIBUTE_IX_VALUE_TYPE] == TypedValue.TYPE_STRING
|
||||
&& attr1[ATTRIBUTE_IX_VALUE_TYPE] == attr2[ATTRIBUTE_IX_VALUE_TYPE]
|
||||
&& // (m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name)
|
||||
// ||
|
||||
// m_strings.touch(attr2[ATTRIBUTE_IX_VALUE_STRING],
|
||||
// m_name)) &&
|
||||
// m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name)
|
||||
// &&
|
||||
attr1[ATTRIBUTE_IX_VALUE_STRING] != attr2[ATTRIBUTE_IX_VALUE_STRING]) {
|
||||
return (attr1[ATTRIBUTE_IX_VALUE_STRING] < attr2[ATTRIBUTE_IX_VALUE_STRING]);
|
||||
} else if ((attr1[ATTRIBUTE_IX_NAMESPACE_URI] == attr2[ATTRIBUTE_IX_NAMESPACE_URI])
|
||||
&& (attr1[ATTRIBUTE_IX_NAMESPACE_URI] != -1) && // (m_strings.touch(attr1[ATTRIBUTE_IX_NAME],
|
||||
// m_name) ||
|
||||
// m_strings.touch(attr2[ATTRIBUTE_IX_NAME],
|
||||
// m_name)) &&
|
||||
// m_strings.touch(attr1[ATTRIBUTE_IX_NAME],
|
||||
// m_name) &&
|
||||
(attr1[ATTRIBUTE_IX_NAME] != attr2[ATTRIBUTE_IX_NAME])) {
|
||||
return (attr1[ATTRIBUTE_IX_NAME] < attr2[ATTRIBUTE_IX_NAME]);
|
||||
// } else if (attr1[ATTRIBUTE_IX_NAMESPACE_URI] <
|
||||
// attr2[ATTRIBUTE_IX_NAMESPACE_URI]) {
|
||||
// return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setFirstError(AndrolibException error) {
|
||||
if (mFirstError == null) {
|
||||
mFirstError = error;
|
||||
@ -1079,7 +1018,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0,
|
||||
ATTRIBUTE_IX_NAME = 1, ATTRIBUTE_IX_VALUE_STRING = 2,
|
||||
ATTRIBUTE_IX_VALUE_TYPE = 3, ATTRIBUTE_IX_VALUE_DATA = 4,
|
||||
ATTRIBUTE_LENGHT = 5;
|
||||
ATTRIBUTE_LENGTH = 5;
|
||||
|
||||
private static final int CHUNK_AXML_FILE = 0x00080003,
|
||||
CHUNK_RESOURCEIDS = 0x00080180, CHUNK_XML_FIRST = 0x00100100,
|
||||
|
@ -309,20 +309,6 @@ public class StringBlock {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
compile project(':brut.j.common'), "commons-io:commons-io:2.4"
|
||||
compile project(':brut.j.common')
|
||||
compile "commons-io:commons-io:2.4",
|
||||
"org.apache.commons:commons-compress:1.4.1"
|
||||
testCompile "junit:junit:3.8.1"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user