Skip to content

Commit 2b2efb1

Browse files
authored
refactor: migrate tests to aapt2 + related fixes (#3865)
* refactor: migrate tests to aapt2 + related fixes All tests have been migrated to aapt2, no tests left for aapt1, as we phase out support. Fixed shared library support using a custom option '-l' or '-lib', that can be specified many times for multiple libs. On decoding, the dynamic_ref_table is used to determine which shared libraries were used, and when done they are stored in ascending order in apktool.yml as a usesLibrary list. The order within usesLibrary determines the package IDs that will be dynamically assigned to shared resources on recompile. Works perfectly with aapt2, doesn't seem to work with aapt1 since it's garbage and never worked properly. SharedLibraryTest was updated accordingly. "isFrameworkApk" and "sharedLibrary" flags are obsolete. We decide aapt options to use by package ID. values-godzilla dir omitted for now, currently aapt2 doesn't support it properly (needs more binary patching). drawableXhdpiTest commented out for now until we decide what to do with those old Samsung and HTC 9patch drawables. aapt2 stores them with ".9" or ".r" appended to resource name, and the decoded files are named ".9.9.qmg" or ".r.r.9.png". Some variable name tweaks for consistency. * organize tests logically to minimize imports * Use camelCase for test methods * remove old proprietary drawables and restore drawableXhdpiTest
1 parent d137d16 commit 2b2efb1

File tree

273 files changed

+813
-1190
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

273 files changed

+813
-1190
lines changed

brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ private static void initConfig(CommandLine cli, Config config) {
148148
if (cli.hasOption("t") || cli.hasOption("tag")) {
149149
config.setFrameworkTag(cli.getOptionValue("t"));
150150
}
151+
if (cli.hasOption("l") || cli.hasOption("lib")) {
152+
config.setLibraryFiles(cli.getOptionValues("l"));
153+
}
151154
if (cli.hasOption("api") || cli.hasOption("api-level")) {
152155
config.setApiLevel(Integer.parseInt(cli.getOptionValue("api")));
153156
}
@@ -365,9 +368,9 @@ private static void _options() {
365368

366369
Option jobsOption = Option.builder("j")
367370
.longOpt("jobs")
371+
.desc("Sets the number of threads to use.")
368372
.hasArg()
369373
.type(Integer.class)
370-
.desc("Sets the number of threads to use.")
371374
.build();
372375

373376
Option noSrcOption = Option.builder("s")
@@ -408,7 +411,7 @@ private static void _options() {
408411
Option apiLevelOption = Option.builder("api")
409412
.longOpt("api-level")
410413
.desc("The numeric api-level of the file to generate, e.g. 14 for ICS.")
411-
.hasArg(true)
414+
.hasArg()
412415
.argName("API")
413416
.build();
414417

@@ -435,24 +438,32 @@ private static void _options() {
435438
Option frameTagOption = Option.builder("t")
436439
.longOpt("frame-tag")
437440
.desc("Use framework files tagged by <tag>.")
438-
.hasArg(true)
441+
.hasArg()
439442
.argName("tag")
440443
.build();
441444

442445
Option frameDirOption = Option.builder("p")
443446
.longOpt("frame-path")
444447
.desc("Use framework files located in <dir>.")
445-
.hasArg(true)
448+
.hasArg()
446449
.argName("dir")
447450
.build();
448451

449452
Option frameIfDirOption = Option.builder("p")
450453
.longOpt("frame-path")
451454
.desc("Store framework files into <dir>.")
452-
.hasArg(true)
455+
.hasArg()
453456
.argName("dir")
454457
.build();
455458

459+
Option libOption = Option.builder("l")
460+
.longOpt("lib")
461+
.desc("Use dynamic library from specified location. Format is: <package>:<loc>\n"
462+
+ " Can be specified multiple times.")
463+
.hasArg()
464+
.argName("loc")
465+
.build();
466+
456467
Option keepResOption = Option.builder("k")
457468
.longOpt("keep-broken-res")
458469
.desc("Use if there was an error and some resources were dropped, e.g.\n"
@@ -469,15 +480,15 @@ private static void _options() {
469480
Option resolveResModeOption = Option.builder("resm")
470481
.longOpt("resource-mode")
471482
.desc("Sets the resolve resources mode. Possible values are: 'remove' (default), 'dummy' or 'keep'.")
472-
.hasArg(true)
483+
.hasArg()
473484
.argName("mode")
474485
.build();
475486

476487
Option aaptOption = Option.builder("a")
477488
.longOpt("aapt")
478-
.hasArg(true)
479-
.argName("loc")
480489
.desc("Load aapt from specified location.")
490+
.hasArg()
491+
.argName("loc")
481492
.build();
482493

483494
Option aapt1Option = Option.builder()
@@ -508,21 +519,21 @@ private static void _options() {
508519
Option tagOption = Option.builder("t")
509520
.longOpt("tag")
510521
.desc("Tag frameworks using <tag>.")
511-
.hasArg(true)
522+
.hasArg()
512523
.argName("tag")
513524
.build();
514525

515526
Option outputBuiOption = Option.builder("o")
516527
.longOpt("output")
517528
.desc("The name of apk that gets written. (default: dist/name.apk)")
518-
.hasArg(true)
529+
.hasArg()
519530
.argName("file")
520531
.build();
521532

522533
Option outputDecOption = Option.builder("o")
523534
.longOpt("output")
524535
.desc("The name of folder that gets written. (default: apk.out)")
525-
.hasArg(true)
536+
.hasArg()
526537
.argName("dir")
527538
.build();
528539

@@ -565,13 +576,15 @@ private static void _options() {
565576
decodeOptions.addOption(frameTagOption);
566577
decodeOptions.addOption(outputDecOption);
567578
decodeOptions.addOption(frameDirOption);
579+
decodeOptions.addOption(libOption);
568580
decodeOptions.addOption(forceDecOption);
569581
decodeOptions.addOption(noSrcOption);
570582
decodeOptions.addOption(noResOption);
571583

572584
// add basic build options
573585
buildOptions.addOption(outputBuiOption);
574586
buildOptions.addOption(frameDirOption);
587+
buildOptions.addOption(libOption);
575588
buildOptions.addOption(forceBuiOption);
576589

577590
// add basic framework options
@@ -634,12 +647,12 @@ private static void usage() {
634647
formatter.setWidth(120);
635648

636649
// print out license info prior to formatter.
637-
System.out.println(
638-
"Apktool " + ApktoolProperties.getVersion() + " - a tool for reengineering Android apk files\n" +
639-
"with smali " + ApktoolProperties.getSmaliVersion() +
640-
" and baksmali " + ApktoolProperties.getBaksmaliVersion() + "\n" +
641-
"Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>\n" +
642-
"Copyright 2010 Connor Tumbleson <connor.tumbleson@gmail.com>");
650+
System.out.println("Apktool " + ApktoolProperties.getVersion()
651+
+ " - a tool for reengineering Android apk files\n"
652+
+ "with smali " + ApktoolProperties.getSmaliVersion()
653+
+ " and baksmali " + ApktoolProperties.getBaksmaliVersion() + "\n"
654+
+ "Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>\n"
655+
+ "Copyright 2010 Connor Tumbleson <connor.tumbleson@gmail.com>");
643656
if (isAdvanceMode()) {
644657
System.out.println("Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)\n");
645658
}else {

brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,17 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir,
120120
cmd.add("-o");
121121
cmd.add(apkFile.getAbsolutePath());
122122

123-
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.packageInfo.forcedPackageId.equals("1")
124-
&& !mApkInfo.sharedLibrary) {
125-
cmd.add("--allow-reserved-package-id");
126-
cmd.add("--package-id");
127-
cmd.add(mApkInfo.packageInfo.forcedPackageId);
128-
}
129-
if (mApkInfo.sharedLibrary) {
130-
cmd.add("--shared-lib");
123+
if (mApkInfo.packageInfo.forcedPackageId != null) {
124+
int pkgId = Integer.parseInt(mApkInfo.packageInfo.forcedPackageId);
125+
if (pkgId == 0) {
126+
cmd.add("--shared-lib");
127+
} else if (pkgId > 1) {
128+
cmd.add("--package-id");
129+
cmd.add(mApkInfo.packageInfo.forcedPackageId);
130+
if (pkgId < 0x7f) {
131+
cmd.add("--allow-reserved-package-id");
132+
}
133+
}
131134
}
132135
if (mApkInfo.getMinSdkVersion() != null) {
133136
cmd.add("--min-sdk-version");
@@ -169,9 +172,6 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir,
169172
if (mApkInfo.compactEntries) {
170173
cmd.add("--enable-compact-entries");
171174
}
172-
if (mApkInfo.isFrameworkApk) {
173-
cmd.add("-x");
174-
}
175175
if (!mApkInfo.featureFlags.isEmpty()) {
176176
List<String> featureFlags = new ArrayList<>();
177177
for (Map.Entry<String, Boolean> entry : mApkInfo.featureFlags.entrySet()) {
@@ -232,14 +232,19 @@ private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir,
232232
if (mConfig.isNoCrunch()) {
233233
cmd.add("--no-crunch");
234234
}
235-
// force package id so that some frameworks build with correct id
236-
// disable if user adds own aapt (can't know if they have this feature)
237-
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.sharedLibrary && !customAapt) {
238-
cmd.add("--forced-package-id");
239-
cmd.add(mApkInfo.packageInfo.forcedPackageId);
240-
}
241-
if (mApkInfo.sharedLibrary) {
242-
cmd.add("--shared-lib");
235+
if (mApkInfo.packageInfo.forcedPackageId != null) {
236+
int pkgId = Integer.parseInt(mApkInfo.packageInfo.forcedPackageId);
237+
if (pkgId == 0) {
238+
cmd.add("--shared-lib");
239+
} else if (pkgId > 0) {
240+
if (!customAapt) { // custom aapt might not have this option
241+
cmd.add("--forced-package-id");
242+
cmd.add(mApkInfo.packageInfo.forcedPackageId);
243+
}
244+
if (pkgId < 0x7f) {
245+
cmd.add("-x");
246+
}
247+
}
243248
}
244249
if (mApkInfo.getMinSdkVersion() != null) {
245250
cmd.add("--min-sdk-version");
@@ -277,9 +282,6 @@ private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir,
277282
cmd.add("-F");
278283
cmd.add(apkFile.getAbsolutePath());
279284

280-
if (mApkInfo.isFrameworkApk) {
281-
cmd.add("-x");
282-
}
283285
if (include != null) {
284286
for (File file : include) {
285287
cmd.add("-I");

brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -467,24 +467,43 @@ private void importUnknownFiles(ZipOutputStream out) throws AndrolibException {
467467
}
468468

469469
private File[] getIncludeFiles() throws AndrolibException {
470+
List<File> files = new ArrayList<>();
471+
470472
UsesFramework usesFramework = mApkInfo.usesFramework;
471-
if (usesFramework == null) {
472-
return null;
473+
if (usesFramework != null) {
474+
List<Integer> ids = usesFramework.ids;
475+
if (ids != null) {
476+
Framework framework = new Framework(mConfig);
477+
String tag = usesFramework.tag;
478+
for (Integer id : ids) {
479+
files.add(framework.getApkFile(id, tag));
480+
}
481+
}
473482
}
474483

475-
List<Integer> ids = usesFramework.ids;
476-
if (ids == null || ids.isEmpty()) {
477-
return null;
484+
List<String> usesLibrary = mApkInfo.usesLibrary;
485+
if (usesLibrary != null) {
486+
String[] libFiles = mConfig.getLibraryFiles();
487+
for (String name : usesLibrary) {
488+
File apkFile = null;
489+
if (libFiles != null) {
490+
for (String libName : libFiles) {
491+
String[] parts = libName.split(":", 2);
492+
if (parts.length == 2 && name.equals(parts[0])) {
493+
apkFile = new File(parts[1]);
494+
break;
495+
}
496+
}
497+
}
498+
if (apkFile == null) {
499+
LOGGER.warning("Shared library was not provided: " + name);
500+
} else {
501+
files.add(apkFile);
502+
}
503+
}
478504
}
479505

480-
Framework framework = new Framework(mConfig);
481-
String tag = usesFramework.tag;
482-
File[] files = new File[ids.size()];
483-
int i = 0;
484-
for (int id : ids) {
485-
files[i++] = framework.getApkFile(id, tag);
486-
}
487-
return files;
506+
return files.toArray(new File[0]);
488507
}
489508

490509
private boolean isModified(File working, File stored) {

brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public class ApkDecoder {
4141

4242
// extensions of files that are often packed uncompressed
4343
private static final Pattern NO_COMPRESS_EXT_PATTERN = Pattern.compile(
44-
"dex|arsc|so|jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|" +
45-
"rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
44+
"dex|arsc|so|jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|"
45+
+ "rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
4646

4747
private final ExtFile mApkFile;
4848
private final Config mConfig;

brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public final class Config {
9393
private int mJobs;
9494
private String mFrameworkDirectory;
9595
private String mFrameworkTag;
96+
private String[] mLibraryFiles;
9697
private File mAaptBinary;
9798
private int mAaptVersion;
9899

@@ -121,6 +122,7 @@ public Config() {
121122
mJobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
122123
mFrameworkDirectory = DEFAULT_FRAMEWORK_DIRECTORY;
123124
mFrameworkTag = null;
125+
mLibraryFiles = null;
124126
mAaptBinary = null;
125127
mAaptVersion = 2;
126128
}
@@ -345,6 +347,14 @@ public void setFrameworkTag(String frameworkTag) {
345347
mFrameworkTag = frameworkTag;
346348
}
347349

350+
public String[] getLibraryFiles() {
351+
return mLibraryFiles;
352+
}
353+
354+
public void setLibraryFiles(String[] libraryFiles) {
355+
mLibraryFiles = libraryFiles;
356+
}
357+
348358
public File getAaptBinary() {
349359
return mAaptBinary;
350360
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,20 @@ public class ApkInfo implements YamlSerializable {
3535
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
3636

3737
public static final Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
38-
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|" +
39-
String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
38+
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|"
39+
+ String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
4040

4141
// only set when loaded from a file (not a stream)
4242
private transient ExtFile mApkFile;
4343

4444
public String version;
4545
public String apkFileName;
46-
public boolean isFrameworkApk;
4746
public UsesFramework usesFramework;
47+
public List<String> usesLibrary = new ArrayList<>();
4848
public Map<String, String> sdkInfo = new LinkedHashMap<>();
4949
public PackageInfo packageInfo = new PackageInfo();
5050
public VersionInfo versionInfo = new VersionInfo();
5151
public Map<String, Boolean> featureFlags = new LinkedHashMap<>();
52-
public boolean sharedLibrary;
5352
public boolean sparseResources;
5453
public boolean compactEntries;
5554
public List<String> doNotCompress = new ArrayList<>();
@@ -227,15 +226,16 @@ public void readItem(YamlReader reader) throws AndrolibException {
227226
apkFileName = line.getValue();
228227
break;
229228
}
230-
case "isFrameworkApk": {
231-
isFrameworkApk = line.getValueBool();
232-
break;
233-
}
234229
case "usesFramework": {
235230
usesFramework = new UsesFramework();
236231
reader.readObject(usesFramework);
237232
break;
238233
}
234+
case "usesLibrary": {
235+
usesLibrary.clear();
236+
reader.readStringList(usesLibrary);
237+
break;
238+
}
239239
case "sdkInfo": {
240240
sdkInfo.clear();
241241
reader.readStringMap(sdkInfo);
@@ -256,10 +256,6 @@ public void readItem(YamlReader reader) throws AndrolibException {
256256
reader.readBoolMap(featureFlags);
257257
break;
258258
}
259-
case "sharedLibrary": {
260-
sharedLibrary = line.getValueBool();
261-
break;
262-
}
263259
case "sparseResources": {
264260
sparseResources = line.getValueBool();
265261
break;
@@ -280,15 +276,16 @@ public void readItem(YamlReader reader) throws AndrolibException {
280276
public void write(YamlWriter writer) {
281277
writer.writeString("version", version);
282278
writer.writeString("apkFileName", apkFileName);
283-
writer.writeBool("isFrameworkApk", isFrameworkApk);
284279
writer.writeObject("usesFramework", usesFramework);
280+
if (!usesLibrary.isEmpty()) {
281+
writer.writeList("usesLibrary", usesLibrary);
282+
}
285283
writer.writeMap("sdkInfo", sdkInfo);
286284
writer.writeObject("packageInfo", packageInfo);
287285
writer.writeObject("versionInfo", versionInfo);
288286
if (!featureFlags.isEmpty()) {
289287
writer.writeMap("featureFlags", featureFlags);
290288
}
291-
writer.writeBool("sharedLibrary", sharedLibrary);
292289
writer.writeBool("sparseResources", sparseResources);
293290
writer.writeBool("compactEntries", compactEntries);
294291
if (!doNotCompress.isEmpty()) {

0 commit comments

Comments
 (0)