diff --git a/INTERNAL.md b/INTERNAL.md
index 24e79772..0e2893b4 100644
--- a/INTERNAL.md
+++ b/INTERNAL.md
@@ -239,3 +239,22 @@ we lose the ability to quickly build just the aapt binary. So the Windows proced
1. `source build/envsetup.sh`
2. `lunch sdk-eng`
3. `make OUT_DIR=out-x64 LOCAL_MULTILIB=64 USE_NINJA=false aapt`
+
+# Gradle Tips n Tricks
+
+ ./gradlew build shadowJar proguard -x test
+
+This skips the testing suite (which currently takes 2-4 minutes). Use this when making quick builds and save the testing
+suite before pushing to GitHub.
+
+ ./gradlew build shadowJar proguard -Dtest.debug
+
+This enables debugging on the test suite. This starts the debugger on port 5005 which you can connect with IntelliJ.
+
+ ./gradlew :brut.apktool:apktool-lib:test ---tests "*BuildAndDecodeTest"
+
+This runs the library project of Apktool, selecting a specific test to run. Comes in handy when writing a new test and
+only wanting to run that one. The asterisk is used to the full path to the test can be ignored. You can additionally
+match this with the debugging parameter to debug a specific test. This command can be found below.
+
+ ./gradlew :brut.apktool:apktool-lib:test --tests "*BuildAndDecodeTest" -Dtest.debug
diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java
index d51cb8d4..9c61d964 100644
--- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java
+++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java
@@ -19,6 +19,7 @@ package brut.androlib;
import brut.androlib.meta.MetaInfo;
import brut.androlib.meta.UsesFramework;
import brut.androlib.res.AndrolibResources;
+import brut.androlib.res.data.ResConfigFlags;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.data.ResUnknownFiles;
@@ -279,7 +280,22 @@ public class Androlib {
mAndRes.setSharedLibrary(meta.sharedLibrary);
if (meta.sdkInfo != null && meta.sdkInfo.get("minSdkVersion") != null) {
- mMinSdkVersion = Integer.parseInt(meta.sdkInfo.get("minSdkVersion"));
+ String minSdkVersion = meta.sdkInfo.get("minSdkVersion");
+
+ // Preview builds use short letter for API versions
+ switch (minSdkVersion) {
+ case "M":
+ mMinSdkVersion = ResConfigFlags.SDK_MNC;
+ break;
+ case "N":
+ mMinSdkVersion = ResConfigFlags.SDK_NOUGAT;
+ break;
+ case "O":
+ mMinSdkVersion = ResConfigFlags.SDK_O;
+ break;
+ default:
+ mMinSdkVersion = Integer.parseInt(meta.sdkInfo.get("minSdkVersion"));
+ }
}
if (outFile == null) {
diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java
index 0c4aaf5f..333c3aa7 100644
--- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java
+++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java
@@ -507,6 +507,9 @@ public class ResConfigFlags {
public final static byte SDK_LOLLIPOP = 21;
public final static byte SDK_LOLLIPOP_MR1 = 22;
public final static byte SDK_MNC = 23;
+ public final static byte SDK_NOUGAT = 24;
+ public final static byte SDK_NOUGAT_MR1 = 25;
+ public final static byte SDK_O = 26;
public final static byte ORIENTATION_ANY = 0;
public final static byte ORIENTATION_PORT = 1;
diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java
index aa0741d1..875142b0 100644
--- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java
+++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java
@@ -90,7 +90,8 @@ public class ResValueFactory {
if (key == ResAttr.BAG_KEY_ATTR_TYPE) {
return ResAttr.factory(parentVal, items, this, mPackage);
}
- if (key == ResArrayValue.BAG_KEY_ARRAY_START) {
+ // Android O Preview added an unknown enum for ResTable_map. This is hardcoded as 0 for now.
+ if (key == ResArrayValue.BAG_KEY_ARRAY_START || key == 0) {
return new ResArrayValue(parentVal, items);
}
if (key >= ResPluralsValue.BAG_KEY_PLURALS_START && key <= ResPluralsValue.BAG_KEY_PLURALS_END) {
diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java
index 680f4712..381ed748 100644
--- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java
+++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java
@@ -20,6 +20,8 @@ import brut.androlib.AndrolibException;
import brut.androlib.err.CantFind9PatchChunk;
import brut.util.ExtDataInput;
import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
import java.io.*;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
@@ -39,8 +41,24 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
BufferedImage im = ImageIO.read(new ByteArrayInputStream(data));
int w = im.getWidth(), h = im.getHeight();
- BufferedImage im2 = new BufferedImage(w+2, h+2, BufferedImage.TYPE_INT_ARGB);
- im2.createGraphics().drawImage(im, 1, 1, w, h, null);
+ BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB);
+ if (im.getType() == BufferedImage.TYPE_CUSTOM) {
+ //TODO: Ensure this is gray + alpha case?
+ Raster srcRaster = im.getRaster();
+ WritableRaster dstRaster = im2.getRaster();
+ int[] gray = null, alpha = null;
+ for (int y = 0; y < im.getHeight(); y++) {
+ gray = srcRaster.getSamples(0, y, w, 1, 0, gray);
+ alpha = srcRaster.getSamples(0, y, w, 1, 1, alpha);
+
+ dstRaster.setSamples(1, y + 1, w, 1, 0, gray);
+ dstRaster.setSamples(1, y + 1, w, 1, 1, gray);
+ dstRaster.setSamples(1, y + 1, w, 1, 2, gray);
+ dstRaster.setSamples(1, y + 1, w, 1, 3, alpha);
+ }
+ } else {
+ im2.createGraphics().drawImage(im, 1, 1, w, h, null);
+ }
NinePatch np = getNinePatch(data);
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java
index d6f03c8a..dd692f41 100644
--- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java
+++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java
@@ -131,8 +131,14 @@ public class StringBlock {
if (style == null) {
return ResXmlEncoders.escapeXmlChars(raw);
}
+
+ // If the returned style is further in string, than string length. Lets skip it.
+ if (style[1] > raw.length()) {
+ return ResXmlEncoders.escapeXmlChars(raw);
+ }
StringBuilder html = new StringBuilder(raw.length() + 32);
int[] opened = new int[style.length / 3];
+ boolean[] unclosed = new boolean[style.length / 3];
int offset = 0, depth = 0;
while (true) {
int i = -1, j;
@@ -149,6 +155,9 @@ public class StringBlock {
int last = opened[j];
int end = style[last + 2];
if (end >= start) {
+ if (style[last + 1] == -1 && end != -1) {
+ unclosed[j] = true;
+ }
break;
}
if (offset <= end) {
@@ -160,6 +169,11 @@ public class StringBlock {
depth = j + 1;
if (offset < start) {
html.append(ResXmlEncoders.escapeXmlChars(raw.substring(offset, start)));
+ if (j >= 0 && unclosed.length >= j && unclosed[j]) {
+ if (unclosed.length > (j + 1) && unclosed[j + 1] || unclosed.length == 1) {
+ outputStyleTag(getString(style[opened[j]]), html, true);
+ }
+ }
offset = start;
}
if (i == -1) {
diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/32/aapt b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/32/aapt
index 72ad574b..ea1d16db 100755
Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/32/aapt and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/32/aapt differ
diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/64/aapt b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/64/aapt
index d09b6145..ba0a130e 100755
Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/64/aapt and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/linux/64/aapt differ
diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/32/aapt b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/32/aapt
index e0fc2525..80190671 100644
Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/32/aapt and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/32/aapt differ
diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/64/aapt b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/64/aapt
index e31b2821..327d5963 100644
Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/64/aapt and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/macosx/64/aapt differ
diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/windows/aapt.exe b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/windows/aapt.exe
index 10ca6130..65ffe0a6 100755
Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/windows/aapt.exe and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/aapt/windows/aapt.exe differ
diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java
index da251858..de49686c 100644
--- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java
+++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java
@@ -253,6 +253,11 @@ public class BuildAndDecodeTest {
compareValuesFiles("values-ast/strings.xml");
}
+ @Test
+ public void androidOStringTest() throws BrutException, IOException {
+ compareValuesFiles("values-ast/strings.xml");
+ }
+
@Test
public void twoLetterNotHandledAsBcpTest() throws BrutException, IOException {
checkFolderExists("res/values-fr");
@@ -345,6 +350,27 @@ public class BuildAndDecodeTest {
assertEquals(controlImage.getRGB(30, 30), testImage.getRGB(30, 30));
}
+ @Test
+ public void issue1508Test() throws BrutException, IOException {
+ char slash = File.separatorChar;
+ String location = slash + "res" + slash + "drawable-xhdpi" + slash;
+
+ File control = new File((sTestOrigDir + location), "btn_zoom_up_normal.9.png");
+ File test = new File((sTestNewDir + location), "btn_zoom_up_normal.9.png");
+
+ BufferedImage controlImage = ImageIO.read(control);
+ BufferedImage testImage = ImageIO.read(test);
+
+ // 0, 0 = clear
+ assertEquals(controlImage.getRGB(0, 0), testImage.getRGB(0, 0));
+
+ // 30, 0 = black line
+ assertEquals(controlImage.getRGB(0, 30), testImage.getRGB(0, 30));
+
+ // 30, 30 = greyish button
+ assertEquals(controlImage.getRGB(30, 30), testImage.getRGB(30, 30));
+ }
+
@Test
public void drawableXxhdpiTest() throws BrutException, IOException {
compareResFolder("drawable-xxhdpi");
diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/drawable-xhdpi/btn_zoom_up_normal.9.png b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/drawable-xhdpi/btn_zoom_up_normal.9.png
new file mode 100644
index 00000000..7f73df09
Binary files /dev/null and b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/drawable-xhdpi/btn_zoom_up_normal.9.png differ
diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-ar-rXB/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-ar-rXB/strings.xml
new file mode 100644
index 00000000..687db601
--- /dev/null
+++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-ar-rXB/strings.xml
@@ -0,0 +1,9 @@
+
+
+ "Forgot your username or password?\nVisit google.com/accounts/recovery."
+ Forgot your username or password?\n.Visit google.com/accounts/recovery
+ (string8) "Forgot your username or password?\nVisit google.com/accounts/recovery."
+ Forgot your username or password?\nVisit google.com/accounts/recovery.
+ Forgot your username or password?
+ Visit google.com/accounts/recovery.
+
\ No newline at end of file
diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml
index a9adad9a..65ccbe69 100644
--- a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml
+++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml
@@ -31,4 +31,14 @@ bar"
category=temp%temp%foo
res/foo/
res/foo
+ [TEST STRING]
+ [TEST STRING]
+ [TEST STRING]
+ [TEST STRING]
+ TEST STRING
+ [Ţåþ ţö ţýþé þåššŵöŕð one two three]
+ [Ţåþ ţö ţýþé þåššŵöŕð one two three]
+ [Ţåþ ţö ţýþé þåššŵöŕð one two three]
+ []Ţåþ ţö ţýþé þåššŵöŕð one two three
+ [Ţåþ ţö ţýþé þåššŵöŕð one two three]
diff --git a/build.gradle b/build.gradle
index ba3f344f..868dc2f1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -82,7 +82,7 @@ subprojects {
ext {
depends = [
- baksmali: 'org.smali:baksmali:2.2.0',
+ baksmali: 'org.smali:baksmali:2.2.1',
commons_cli: 'commons-cli:commons-cli:1.4',
commons_io: 'commons-io:commons-io:2.4',
commons_lang: 'org.apache.commons:commons-lang3:3.1',
@@ -91,7 +91,7 @@ subprojects {
junit: 'junit:junit:4.12',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
snakeyaml: 'org.yaml:snakeyaml:1.17',
- smali: 'org.smali:smali:2.2.0',
+ smali: 'org.smali:smali:2.2.1',
xmlpull: 'xpp3:xpp3:1.1.4c',
xmlunit: 'xmlunit:xmlunit:1.3',
]