2019-10-16 07:49:58 +02:00
|
|
|
/*
|
2020-04-11 12:33:05 +02:00
|
|
|
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
|
|
|
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
2012-09-20 03:27:35 +02:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
2021-08-24 15:31:41 +02:00
|
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
2012-09-20 03:27:35 +02:00
|
|
|
*
|
|
|
|
* 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;
|
2021-09-05 12:45:03 +02:00
|
|
|
import brut.androlib.options.BuildOptions;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.androlib.err.CantFindFrameworkResException;
|
2017-09-28 14:23:02 +02:00
|
|
|
import brut.androlib.meta.MetaInfo;
|
2016-01-07 08:35:56 +01:00
|
|
|
import brut.androlib.meta.PackageInfo;
|
|
|
|
import brut.androlib.meta.VersionInfo;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.androlib.res.data.*;
|
|
|
|
import brut.androlib.res.decoder.*;
|
|
|
|
import brut.androlib.res.decoder.ARSCDecoder.ARSCData;
|
|
|
|
import brut.androlib.res.decoder.ARSCDecoder.FlagsOffset;
|
2016-01-07 08:35:56 +01:00
|
|
|
import brut.androlib.res.util.ExtMXSerializer;
|
|
|
|
import brut.androlib.res.util.ExtXmlSerializer;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
2015-04-16 15:02:24 +02:00
|
|
|
import brut.androlib.res.xml.ResXmlPatcher;
|
2012-09-20 03:27:35 +02:00
|
|
|
import brut.common.BrutException;
|
2021-07-03 22:24:42 +02:00
|
|
|
import brut.directory.*;
|
2018-02-16 00:10:22 +01:00
|
|
|
import brut.util.*;
|
2016-01-07 08:35:56 +01:00
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
import org.xmlpull.v1.XmlSerializer;
|
|
|
|
|
2012-09-20 03:27:35 +02:00
|
|
|
import java.io.*;
|
2022-06-11 19:17:09 +02:00
|
|
|
import java.nio.file.Files;
|
2022-06-20 16:41:45 +02:00
|
|
|
import java.nio.file.Path;
|
2012-09-20 03:27:35 +02:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.logging.Logger;
|
2016-01-07 08:35:56 +01:00
|
|
|
import java.util.zip.CRC32;
|
|
|
|
import java.util.zip.ZipEntry;
|
|
|
|
import java.util.zip.ZipFile;
|
|
|
|
import java.util.zip.ZipOutputStream;
|
2012-09-20 03:27:35 +02:00
|
|
|
|
|
|
|
final public class AndrolibResources {
|
2013-06-20 15:03:20 +02:00
|
|
|
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 {
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Loading resource table...");
|
2014-09-23 15:24:45 +02:00
|
|
|
ResPackage[] pkgs = getResPackagesFromApk(apkFile, resTable, sKeepBroken);
|
2021-08-26 19:00:37 +02:00
|
|
|
ResPackage pkg;
|
2013-06-20 15:03:20 +02:00
|
|
|
|
|
|
|
switch (pkgs.length) {
|
2022-07-10 13:42:29 +02:00
|
|
|
case 0:
|
|
|
|
pkg = null;
|
|
|
|
break;
|
2013-06-20 15:03:20 +02:00
|
|
|
case 1:
|
|
|
|
pkg = pkgs[0];
|
|
|
|
break;
|
|
|
|
case 2:
|
2021-08-31 02:59:38 +02:00
|
|
|
LOGGER.warning("Skipping package group: " + pkgs[0].getName());
|
2021-08-26 19:00:37 +02:00
|
|
|
pkg = pkgs[1];
|
|
|
|
break;
|
2014-10-03 18:31:43 +02:00
|
|
|
default:
|
|
|
|
pkg = selectPkgWithMostResSpecs(pkgs);
|
2013-06-20 15:03:20 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pkg == null) {
|
2014-10-03 18:31:43 +02:00
|
|
|
throw new AndrolibException("arsc files with zero packages or no arsc file found.");
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
resTable.addPackage(pkg, true);
|
|
|
|
return pkg;
|
|
|
|
}
|
|
|
|
|
2021-03-12 13:47:19 +01:00
|
|
|
public ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs) {
|
2014-10-03 18:31:43 +02:00
|
|
|
int id = 0;
|
|
|
|
int value = 0;
|
2019-09-23 12:39:18 +02:00
|
|
|
int index = 0;
|
2014-10-03 18:31:43 +02:00
|
|
|
|
2019-09-23 12:39:18 +02:00
|
|
|
for (int i = 0; i < pkgs.length; i++) {
|
|
|
|
ResPackage resPackage = pkgs[i];
|
2014-10-03 18:31:43 +02:00
|
|
|
if (resPackage.getResSpecCount() > value && ! resPackage.getName().equalsIgnoreCase("android")) {
|
|
|
|
value = resPackage.getResSpecCount();
|
|
|
|
id = resPackage.getId();
|
2019-09-23 12:39:18 +02:00
|
|
|
index = i;
|
2014-10-03 18:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if id is still 0, we only have one pkgId which is "android" -> 1
|
2019-09-23 12:39:18 +02:00
|
|
|
return (id == 0) ? pkgs[0] : pkgs[index];
|
2014-10-03 18:31:43 +02:00
|
|
|
}
|
|
|
|
|
2014-09-23 15:24:45 +02:00
|
|
|
public ResPackage loadFrameworkPkg(ResTable resTable, int id, String frameTag)
|
|
|
|
throws AndrolibException {
|
2013-06-20 15:03:20 +02:00
|
|
|
File apk = getFrameworkApk(id, frameTag);
|
|
|
|
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Loading resource table from file: " + apk);
|
2017-05-02 08:21:09 +02:00
|
|
|
mFramework = new ExtFile(apk);
|
|
|
|
ResPackage[] pkgs = getResPackagesFromApk(mFramework, resTable, true);
|
2013-06-20 15:03:20 +02:00
|
|
|
|
2014-10-03 18:31:43 +02:00
|
|
|
ResPackage pkg;
|
|
|
|
if (pkgs.length > 1) {
|
|
|
|
pkg = selectPkgWithMostResSpecs(pkgs);
|
|
|
|
} else if (pkgs.length == 0) {
|
2014-09-23 15:24:45 +02:00
|
|
|
throw new AndrolibException("Arsc files with zero or multiple packages");
|
2014-10-03 18:31:43 +02:00
|
|
|
} else {
|
|
|
|
pkg = pkgs[0];
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pkg.getId() != id) {
|
2021-08-26 19:00:37 +02:00
|
|
|
throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId());
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
resTable.addPackage(pkg, false);
|
|
|
|
return pkg;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
|
2021-08-17 13:01:08 +02:00
|
|
|
Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder(false);
|
2013-06-20 15:03:20 +02:00
|
|
|
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);
|
|
|
|
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Decoding AndroidManifest.xml with only framework resources...");
|
2014-09-23 15:24:45 +02:00
|
|
|
fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");
|
2013-06-20 15:03:20 +02:00
|
|
|
|
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
public void adjustPackageManifest(ResTable resTable, String filePath)
|
2013-06-20 15:03:20 +02:00
|
|
|
throws AndrolibException {
|
2012-12-19 02:40:42 +01:00
|
|
|
|
2014-01-07 17:59:37 +01:00
|
|
|
// compare resources.arsc package name to the one present in AndroidManifest
|
2014-03-19 22:23:45 +01:00
|
|
|
ResPackage resPackage = resTable.getCurrentResPackage();
|
2019-07-14 12:56:37 +02:00
|
|
|
String pkgOriginal = resPackage.getName();
|
2014-01-07 17:59:37 +01:00
|
|
|
mPackageRenamed = resTable.getPackageRenamed();
|
2013-02-13 04:12:17 +01:00
|
|
|
|
2014-01-07 17:59:37 +01:00
|
|
|
resTable.setPackageId(resPackage.getId());
|
2019-07-14 12:56:37 +02:00
|
|
|
resTable.setPackageOriginal(pkgOriginal);
|
|
|
|
|
|
|
|
// 1) Check if pkgOriginal === mPackageRenamed
|
|
|
|
// 2) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
|
|
|
if (pkgOriginal.equalsIgnoreCase(mPackageRenamed) || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Regular manifest package...");
|
2013-06-20 15:03:20 +02:00
|
|
|
} else {
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Renamed manifest package found! Replacing " + mPackageRenamed + " with " + pkgOriginal);
|
2019-07-14 12:56:37 +02:00
|
|
|
ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal);
|
2015-04-16 14:24:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-26 15:08:50 +01:00
|
|
|
public void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir)
|
2013-06-20 15:03:20 +02:00
|
|
|
throws AndrolibException {
|
2014-12-26 15:08:50 +01:00
|
|
|
|
2021-08-17 13:01:08 +02:00
|
|
|
Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder(true);
|
2013-06-20 15:03:20 +02:00
|
|
|
ResFileDecoder fileDecoder = duo.m1;
|
|
|
|
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2014-09-23 15:24:45 +02:00
|
|
|
attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
Directory inApk, in = null, out;
|
|
|
|
try {
|
|
|
|
inApk = apkFile.getDirectory();
|
|
|
|
out = new FileDirectory(outDir);
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Decoding AndroidManifest.xml with resources...");
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2014-09-23 15:24:45 +02:00
|
|
|
fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");
|
2013-02-13 04:12:17 +01:00
|
|
|
|
2013-04-01 00:13:10 +02:00
|
|
|
// Remove versionName / versionCode (aapt API 16)
|
2013-11-15 20:07:07 +01:00
|
|
|
if (!resTable.getAnalysisMode()) {
|
|
|
|
|
|
|
|
// check for a mismatch between resources.arsc package and the package listed in AndroidManifest
|
|
|
|
// also remove the android::versionCode / versionName from manifest for rebuild
|
|
|
|
// this is a required change to prevent aapt warning about conflicting versions
|
|
|
|
// it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml
|
2015-04-16 15:02:24 +02:00
|
|
|
adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml");
|
|
|
|
|
|
|
|
ResXmlPatcher.removeManifestVersions(new File(
|
|
|
|
outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"));
|
|
|
|
|
2014-01-07 17:59:37 +01:00
|
|
|
mPackageId = String.valueOf(resTable.getPackageId());
|
2013-06-12 17:04:28 +02:00
|
|
|
}
|
2014-12-26 15:08:50 +01:00
|
|
|
} 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());
|
2022-11-14 00:06:25 +01:00
|
|
|
Directory in, out;
|
2014-12-26 15:08:50 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
out = new FileDirectory(outDir);
|
2022-11-14 00:06:25 +01:00
|
|
|
in = apkFile.getDirectory();
|
2016-03-10 15:29:49 +01:00
|
|
|
out = out.createDir("res");
|
2013-06-20 15:03:20 +02:00
|
|
|
} catch (DirectoryException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExtMXSerializer xmlSerializer = getResXmlSerializer();
|
|
|
|
for (ResPackage pkg : resTable.listMainPackages()) {
|
|
|
|
attrDecoder.setCurrentPackage(pkg);
|
|
|
|
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Decoding file-resources...");
|
2013-06-20 15:03:20 +02:00
|
|
|
for (ResResource res : pkg.listFiles()) {
|
2022-11-08 12:24:41 +01:00
|
|
|
fileDecoder.decode(res, in, out, mResFileMapping);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Decoding values */* XMLs...");
|
2013-06-20 15:03:20 +02:00
|
|
|
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
|
|
|
|
generateValuesFile(valuesFile, out, xmlSerializer);
|
|
|
|
}
|
|
|
|
generatePublicXml(pkg, out, xmlSerializer);
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 08:35:56 +01:00
|
|
|
public void setVersionInfo(VersionInfo versionInfo) {
|
|
|
|
if (versionInfo != null) {
|
|
|
|
mVersionCode = versionInfo.versionCode;
|
|
|
|
mVersionName = versionInfo.versionName;
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 08:35:56 +01:00
|
|
|
public void setPackageRenamed(PackageInfo packageInfo) {
|
|
|
|
if (packageInfo != null) {
|
|
|
|
mPackageRenamed = packageInfo.renameManifestPackage;
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2016-01-07 08:35:56 +01:00
|
|
|
public void setPackageId(PackageInfo packageInfo) {
|
|
|
|
if (packageInfo != null) {
|
|
|
|
mPackageId = packageInfo.forcedPackageId;
|
2013-05-12 17:15:36 +02:00
|
|
|
}
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
2013-03-17 15:37:46 +01:00
|
|
|
|
2015-03-08 20:31:21 +01:00
|
|
|
public void setSharedLibrary(boolean flag) {
|
|
|
|
mSharedLibrary = flag;
|
|
|
|
}
|
|
|
|
|
2018-02-28 17:05:07 +01:00
|
|
|
public void setSparseResources(boolean flag) {
|
|
|
|
mSparseResources = flag;
|
|
|
|
}
|
|
|
|
|
2017-09-19 21:09:25 +02:00
|
|
|
public String checkTargetSdkVersionBounds() {
|
2017-09-28 14:23:02 +02:00
|
|
|
int target = mapSdkShorthandToVersion(mTargetSdkVersion);
|
|
|
|
|
|
|
|
int min = (mMinSdkVersion != null) ? mapSdkShorthandToVersion(mMinSdkVersion) : 0;
|
|
|
|
int max = (mMaxSdkVersion != null) ? mapSdkShorthandToVersion(mMaxSdkVersion) : target;
|
2017-09-15 16:24:11 +02:00
|
|
|
|
|
|
|
target = Math.min(max, target);
|
|
|
|
target = Math.max(min, target);
|
|
|
|
return Integer.toString(target);
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
private File createDoNotCompressExtensionsFile(BuildOptions buildOptions) throws AndrolibException {
|
|
|
|
if (buildOptions.doNotCompress == null || buildOptions.doNotCompress.isEmpty()) {
|
2019-10-06 12:59:54 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
File doNotCompressFile;
|
|
|
|
try {
|
|
|
|
doNotCompressFile = File.createTempFile("APKTOOL", null);
|
|
|
|
doNotCompressFile.deleteOnExit();
|
|
|
|
|
|
|
|
BufferedWriter fileWriter = new BufferedWriter(new FileWriter(doNotCompressFile));
|
2021-09-05 12:45:03 +02:00
|
|
|
for (String extension : buildOptions.doNotCompress) {
|
2019-10-06 12:59:54 +02:00
|
|
|
fileWriter.write(extension);
|
|
|
|
fileWriter.newLine();
|
|
|
|
}
|
|
|
|
fileWriter.close();
|
|
|
|
|
|
|
|
return doNotCompressFile;
|
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-16 00:10:22 +01:00
|
|
|
private void aapt2Package(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
2022-06-20 16:41:45 +02:00
|
|
|
List<String> cmd, boolean customAapt) throws AndrolibException {
|
2018-02-16 00:10:22 +01:00
|
|
|
List<String> compileCommand = new ArrayList<>(cmd);
|
2018-03-01 00:40:13 +01:00
|
|
|
File resourcesZip = null;
|
|
|
|
|
2018-02-16 00:10:22 +01:00
|
|
|
if (resDir != null) {
|
2018-03-01 00:40:13 +01:00
|
|
|
File buildDir = new File(resDir.getParent(), "build");
|
2022-06-20 16:41:45 +02:00
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
|
buildDir.mkdirs();
|
2018-03-01 00:40:13 +01:00
|
|
|
resourcesZip = new File(buildDir, "resources.zip");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resDir != null && !resourcesZip.exists()) {
|
2018-02-16 00:10:22 +01:00
|
|
|
// Compile the files into flat arsc files
|
|
|
|
cmd.add("compile");
|
|
|
|
|
|
|
|
cmd.add("--dir");
|
|
|
|
cmd.add(resDir.getAbsolutePath());
|
|
|
|
|
2018-02-28 16:58:38 +01:00
|
|
|
// Treats error that used to be valid in aapt1 as warnings in aapt2
|
|
|
|
cmd.add("--legacy");
|
2018-02-16 00:10:22 +01:00
|
|
|
|
2018-03-01 00:40:13 +01:00
|
|
|
cmd.add("-o");
|
|
|
|
cmd.add(resourcesZip.getAbsolutePath());
|
2013-03-29 20:49:04 +01:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.verbose) {
|
2018-03-01 00:40:13 +01:00
|
|
|
cmd.add("-v");
|
|
|
|
}
|
2013-03-29 20:49:04 +01:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.noCrunch) {
|
2018-07-24 05:54:37 +02:00
|
|
|
cmd.add("--no-crunch");
|
|
|
|
}
|
|
|
|
|
2018-03-01 00:40:13 +01:00
|
|
|
try {
|
|
|
|
OS.exec(cmd.toArray(new String[0]));
|
|
|
|
LOGGER.fine("aapt2 compile command ran: ");
|
|
|
|
LOGGER.fine(cmd.toString());
|
|
|
|
} catch (BrutException ex) {
|
2018-02-16 00:10:22 +01:00
|
|
|
throw new AndrolibException(ex);
|
2013-03-29 20:49:04 +01:00
|
|
|
}
|
2018-02-16 00:10:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (manifest == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Link them into the final apk, reusing our old command after clearing for the aapt2 binary
|
|
|
|
cmd = new ArrayList<>(compileCommand);
|
|
|
|
cmd.add("link");
|
|
|
|
|
|
|
|
cmd.add("-o");
|
|
|
|
cmd.add(apkFile.getAbsolutePath());
|
|
|
|
|
|
|
|
if (mPackageId != null && ! mSharedLibrary) {
|
|
|
|
cmd.add("--package-id");
|
|
|
|
cmd.add(mPackageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mSharedLibrary) {
|
|
|
|
cmd.add("--shared-lib");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mMinSdkVersion != null) {
|
|
|
|
cmd.add("--min-sdk-version");
|
|
|
|
cmd.add(mMinSdkVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTargetSdkVersion != null) {
|
|
|
|
cmd.add("--target-sdk-version");
|
|
|
|
cmd.add(checkTargetSdkVersionBounds());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPackageRenamed != null) {
|
|
|
|
cmd.add("--rename-manifest-package");
|
|
|
|
cmd.add(mPackageRenamed);
|
|
|
|
|
|
|
|
cmd.add("--rename-instrumentation-target-package");
|
|
|
|
cmd.add(mPackageRenamed);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mVersionCode != null) {
|
|
|
|
cmd.add("--version-code");
|
|
|
|
cmd.add(mVersionCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mVersionName != null) {
|
|
|
|
cmd.add("--version-name");
|
|
|
|
cmd.add(mVersionName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disable automatic changes
|
|
|
|
cmd.add("--no-auto-version");
|
|
|
|
cmd.add("--no-version-vectors");
|
|
|
|
cmd.add("--no-version-transitions");
|
|
|
|
cmd.add("--no-resource-deduping");
|
|
|
|
|
2022-03-28 12:53:42 +02:00
|
|
|
cmd.add("--allow-reserved-package-id");
|
|
|
|
|
2018-02-28 17:05:07 +01:00
|
|
|
if (mSparseResources) {
|
|
|
|
cmd.add("--enable-sparse-encoding");
|
|
|
|
}
|
2018-02-16 00:10:22 +01:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.isFramework) {
|
2018-02-16 00:10:22 +01:00
|
|
|
cmd.add("-x");
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.doNotCompress != null && !customAapt) {
|
2019-11-20 12:37:02 +01:00
|
|
|
// Use custom -e option to avoid limits on commandline length.
|
|
|
|
// Can only be used when custom aapt binary is not used.
|
2021-09-05 12:45:03 +02:00
|
|
|
String extensionsFilePath = createDoNotCompressExtensionsFile(buildOptions).getAbsolutePath();
|
2019-10-06 12:59:54 +02:00
|
|
|
cmd.add("-e");
|
|
|
|
cmd.add(extensionsFilePath);
|
2021-09-05 12:45:03 +02:00
|
|
|
} else if (buildOptions.doNotCompress != null) {
|
|
|
|
for (String file : buildOptions.doNotCompress) {
|
2019-11-20 12:37:02 +01:00
|
|
|
cmd.add("-0");
|
|
|
|
cmd.add(file);
|
|
|
|
}
|
2013-03-29 20:49:04 +01:00
|
|
|
}
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (!buildOptions.resourcesAreCompressed) {
|
2018-02-16 00:10:22 +01:00
|
|
|
cmd.add("-0");
|
|
|
|
cmd.add("arsc");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (include != null) {
|
|
|
|
for (File file : include) {
|
|
|
|
cmd.add("-I");
|
|
|
|
cmd.add(file.getPath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.add("--manifest");
|
|
|
|
cmd.add(manifest.getAbsolutePath());
|
|
|
|
|
|
|
|
if (assetDir != null) {
|
|
|
|
cmd.add("-A");
|
|
|
|
cmd.add(assetDir.getAbsolutePath());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rawDir != null) {
|
|
|
|
cmd.add("-R");
|
|
|
|
cmd.add(rawDir.getAbsolutePath());
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.verbose) {
|
2018-02-16 00:10:22 +01:00
|
|
|
cmd.add("-v");
|
|
|
|
}
|
|
|
|
|
2018-03-01 00:40:13 +01:00
|
|
|
if (resourcesZip != null) {
|
|
|
|
cmd.add(resourcesZip.getAbsolutePath());
|
2018-02-16 00:10:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
OS.exec(cmd.toArray(new String[0]));
|
2018-02-28 16:58:38 +01:00
|
|
|
LOGGER.fine("aapt2 link command ran: ");
|
|
|
|
LOGGER.fine(cmd.toString());
|
2018-02-16 00:10:22 +01:00
|
|
|
} catch (BrutException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void aapt1Package(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
|
|
|
List<String> cmd, boolean customAapt)
|
|
|
|
throws AndrolibException {
|
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("p");
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.verbose) { // output aapt verbose
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("-v");
|
|
|
|
}
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.updateFiles) {
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("-u");
|
|
|
|
}
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.debugMode) { // inject debuggable="true" into manifest
|
2016-04-18 00:05:39 +02:00
|
|
|
cmd.add("--debug-mode");
|
|
|
|
}
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.noCrunch) {
|
2018-07-24 05:54:37 +02:00
|
|
|
cmd.add("--no-crunch");
|
|
|
|
}
|
2013-03-29 20:49:04 +01:00
|
|
|
// force package id so that some frameworks build with correct id
|
|
|
|
// disable if user adds own aapt (can't know if they have this feature)
|
2015-03-08 20:31:21 +01:00
|
|
|
if (mPackageId != null && ! customAapt && ! mSharedLibrary) {
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("--forced-package-id");
|
|
|
|
cmd.add(mPackageId);
|
|
|
|
}
|
2015-03-08 20:31:21 +01:00
|
|
|
if (mSharedLibrary) {
|
|
|
|
cmd.add("--shared-lib");
|
|
|
|
}
|
2013-06-20 15:03:20 +02:00
|
|
|
if (mMinSdkVersion != null) {
|
|
|
|
cmd.add("--min-sdk-version");
|
|
|
|
cmd.add(mMinSdkVersion);
|
|
|
|
}
|
|
|
|
if (mTargetSdkVersion != null) {
|
|
|
|
cmd.add("--target-sdk-version");
|
2017-09-19 20:42:14 +02:00
|
|
|
|
|
|
|
// Ensure that targetSdkVersion is between minSdkVersion/maxSdkVersion if
|
|
|
|
// they are specified.
|
2017-09-15 16:24:11 +02:00
|
|
|
cmd.add(checkTargetSdkVersionBounds());
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
if (mMaxSdkVersion != null) {
|
|
|
|
cmd.add("--max-sdk-version");
|
|
|
|
cmd.add(mMaxSdkVersion);
|
|
|
|
|
|
|
|
// if we have max sdk version, set --max-res-version
|
|
|
|
// so we can ignore anything over that during build.
|
|
|
|
cmd.add("--max-res-version");
|
|
|
|
cmd.add(mMaxSdkVersion);
|
|
|
|
}
|
|
|
|
if (mPackageRenamed != null) {
|
|
|
|
cmd.add("--rename-manifest-package");
|
|
|
|
cmd.add(mPackageRenamed);
|
|
|
|
}
|
|
|
|
if (mVersionCode != null) {
|
|
|
|
cmd.add("--version-code");
|
|
|
|
cmd.add(mVersionCode);
|
|
|
|
}
|
|
|
|
if (mVersionName != null) {
|
|
|
|
cmd.add("--version-name");
|
|
|
|
cmd.add(mVersionName);
|
|
|
|
}
|
2016-12-23 15:11:43 +01:00
|
|
|
cmd.add("--no-version-vectors");
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("-F");
|
|
|
|
cmd.add(apkFile.getAbsolutePath());
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.isFramework) {
|
2013-06-20 15:03:20 +02:00
|
|
|
cmd.add("-x");
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.doNotCompress != null && !customAapt) {
|
2019-10-18 10:57:52 +02:00
|
|
|
// Use custom -e option to avoid limits on commandline length.
|
|
|
|
// Can only be used when custom aapt binary is not used.
|
2021-09-05 12:45:03 +02:00
|
|
|
String extensionsFilePath = createDoNotCompressExtensionsFile(buildOptions).getAbsolutePath();
|
2019-10-06 12:59:54 +02:00
|
|
|
cmd.add("-e");
|
|
|
|
cmd.add(extensionsFilePath);
|
2021-09-05 12:45:03 +02:00
|
|
|
} else if (buildOptions.doNotCompress != null) {
|
|
|
|
for (String file : buildOptions.doNotCompress) {
|
2019-10-18 10:57:52 +02:00
|
|
|
cmd.add("-0");
|
|
|
|
cmd.add(file);
|
|
|
|
}
|
2015-08-14 17:52:33 +02:00
|
|
|
}
|
2019-10-06 12:59:54 +02:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (!buildOptions.resourcesAreCompressed) {
|
2013-06-20 15:03:20 +02:00
|
|
|
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]));
|
2018-02-28 16:58:38 +01:00
|
|
|
LOGGER.fine("command ran: ");
|
|
|
|
LOGGER.fine(cmd.toString());
|
2013-06-20 15:03:20 +02:00
|
|
|
} catch (BrutException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-16 00:10:22 +01:00
|
|
|
public void aaptPackage(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
|
|
|
|
throws AndrolibException {
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
String aaptPath = buildOptions.aaptPath;
|
2022-06-20 16:41:45 +02:00
|
|
|
boolean customAapt = false; // ReVanced - we always use a custom aapt binary.
|
2021-08-26 19:00:37 +02:00
|
|
|
List<String> cmd = new ArrayList<>();
|
2018-02-16 00:10:22 +01:00
|
|
|
|
2018-02-28 17:25:56 +01:00
|
|
|
try {
|
2022-06-11 19:17:09 +02:00
|
|
|
String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptVersion());
|
2018-02-28 17:25:56 +01:00
|
|
|
cmd.add(aaptCommand);
|
|
|
|
} catch (BrutException ex) {
|
|
|
|
LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)");
|
|
|
|
cmd.add(AaptManager.getAaptBinaryName(getAaptVersion()));
|
2018-02-16 00:10:22 +01:00
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.isAapt2()) {
|
2019-11-20 12:37:02 +01:00
|
|
|
aapt2Package(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
|
2018-02-16 00:10:22 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
aapt1Package(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void zipPackage(File apkFile, File rawDir, File assetDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
|
|
|
|
try {
|
2021-09-05 12:45:03 +02:00
|
|
|
ZipUtils.zipFolders(rawDir, apkFile, assetDir, buildOptions.doNotCompress);
|
2018-02-16 00:10:22 +01:00
|
|
|
} catch (IOException | BrutException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-28 14:23:02 +02:00
|
|
|
public int getMinSdkVersionFromAndroidCodename(MetaInfo meta, String sdkVersion) {
|
|
|
|
int sdkNumber = mapSdkShorthandToVersion(sdkVersion);
|
|
|
|
|
|
|
|
if (sdkNumber == ResConfigFlags.SDK_BASE) {
|
|
|
|
return Integer.parseInt(meta.sdkInfo.get("minSdkVersion"));
|
|
|
|
}
|
|
|
|
return sdkNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int mapSdkShorthandToVersion(String sdkVersion) {
|
2018-03-12 20:34:32 +01:00
|
|
|
switch (sdkVersion.toUpperCase()) {
|
2017-09-28 14:23:02 +02:00
|
|
|
case "M":
|
|
|
|
return ResConfigFlags.SDK_MNC;
|
|
|
|
case "N":
|
|
|
|
return ResConfigFlags.SDK_NOUGAT;
|
|
|
|
case "O":
|
|
|
|
return ResConfigFlags.SDK_OREO;
|
2018-03-12 20:34:32 +01:00
|
|
|
case "P":
|
|
|
|
return ResConfigFlags.SDK_P;
|
2019-04-25 00:04:24 +02:00
|
|
|
case "Q":
|
|
|
|
return ResConfigFlags.SDK_Q;
|
2020-09-13 14:52:15 +02:00
|
|
|
case "R":
|
|
|
|
return ResConfigFlags.SDK_R;
|
|
|
|
case "S":
|
|
|
|
return ResConfigFlags.SDK_S;
|
2022-03-13 12:39:54 +01:00
|
|
|
case "SV2":
|
|
|
|
return ResConfigFlags.SDK_S_V2;
|
2021-08-29 12:43:46 +02:00
|
|
|
case "T":
|
2022-03-13 12:39:54 +01:00
|
|
|
case "TIRAMISU":
|
2021-08-29 12:43:46 +02:00
|
|
|
return ResConfigFlags.SDK_DEVELOPMENT;
|
2017-09-28 14:23:02 +02:00
|
|
|
default:
|
|
|
|
return Integer.parseInt(sdkVersion);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
public boolean detectWhetherAppIsFramework(File appDir)
|
|
|
|
throws AndrolibException {
|
|
|
|
File publicXml = new File(appDir, "res/values/public.xml");
|
2014-09-23 15:24:45 +02:00
|
|
|
if (! publicXml.exists()) {
|
2013-06-20 15:03:20 +02:00
|
|
|
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 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());
|
2014-09-23 15:24:45 +02:00
|
|
|
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
|
2013-06-20 15:03:20 +02:00
|
|
|
|
2021-08-26 19:00:37 +02:00
|
|
|
return new Duo<>(new ResFileDecoder(decoders), axmlParser);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
2021-08-17 13:01:08 +02:00
|
|
|
public Duo<ResFileDecoder, AXmlResourceParser> getManifestFileDecoder(boolean withResources) {
|
2013-06-20 15:03:20 +02:00
|
|
|
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
|
|
|
|
|
2021-08-17 13:01:08 +02:00
|
|
|
AXmlResourceParser axmlParser = new AndroidManifestResourceParser();
|
|
|
|
if (withResources) {
|
|
|
|
axmlParser.setAttrDecoder(new ResAttrDecoder());
|
|
|
|
}
|
2014-09-23 15:24:45 +02:00
|
|
|
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser,getResXmlSerializer()));
|
2013-06-20 15:03:20 +02:00
|
|
|
|
2021-08-26 19:00:37 +02:00
|
|
|
return new Duo<>(new ResFileDecoder(decoders), axmlParser);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public ExtMXSerializer getResXmlSerializer() {
|
|
|
|
ExtMXSerializer serial = new ExtMXSerializer();
|
2014-09-23 15:24:45 +02:00
|
|
|
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " ");
|
|
|
|
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator"));
|
2013-06-20 15:03:20 +02:00
|
|
|
serial.setProperty(ExtXmlSerializer.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;
|
|
|
|
}
|
2014-09-23 15:24:45 +02:00
|
|
|
((ResValuesXmlSerializable) res.getValue()).serializeToResValuesXml(serial, res);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
serial.endTag(null, "resources");
|
|
|
|
serial.newLine();
|
|
|
|
serial.endDocument();
|
|
|
|
serial.flush();
|
|
|
|
outStream.close();
|
2014-09-23 15:24:45 +02:00
|
|
|
} catch (IOException | DirectoryException ex) {
|
|
|
|
throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
2014-09-23 15:24:45 +02:00
|
|
|
serial.attribute(null, "id", String.format("0x%08x", spec.getId().id));
|
2013-06-20 15:03:20 +02:00
|
|
|
serial.endTag(null, "public");
|
|
|
|
}
|
|
|
|
|
|
|
|
serial.endTag(null, "resources");
|
|
|
|
serial.endDocument();
|
|
|
|
serial.flush();
|
|
|
|
outStream.close();
|
2014-09-23 15:24:45 +02:00
|
|
|
} catch (IOException | DirectoryException ex) {
|
|
|
|
throw new AndrolibException("Could not generate public.xml file", ex);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-23 15:24:45 +02:00
|
|
|
private ResPackage[] getResPackagesFromApk(ExtFile apkFile,ResTable resTable, boolean keepBroken)
|
|
|
|
throws AndrolibException {
|
2013-06-20 15:03:20 +02:00
|
|
|
try {
|
2017-05-02 08:21:09 +02:00
|
|
|
Directory dir = apkFile.getDirectory();
|
2021-08-26 19:00:37 +02:00
|
|
|
try (BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
|
2017-05-02 08:21:09 +02:00
|
|
|
return ARSCDecoder.decode(bfi, false, keepBroken, resTable).getPackages();
|
|
|
|
}
|
2021-08-26 19:00:37 +02:00
|
|
|
} catch (DirectoryException | IOException ex) {
|
2014-09-23 15:24:45 +02:00
|
|
|
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public File getFrameworkApk(int id, String frameTag)
|
2017-05-02 14:16:07 +02:00
|
|
|
throws AndrolibException {
|
2013-06-20 15:03:20 +02:00
|
|
|
File dir = getFrameworkDir();
|
|
|
|
File apk;
|
|
|
|
|
|
|
|
if (frameTag != null) {
|
|
|
|
apk = new File(dir, String.valueOf(id) + '-' + frameTag + ".apk");
|
|
|
|
if (apk.exists()) {
|
|
|
|
return apk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:00:37 +02:00
|
|
|
apk = new File(dir, id + ".apk");
|
2013-06-20 15:03:20 +02:00
|
|
|
if (apk.exists()) {
|
|
|
|
return apk;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id == 1) {
|
2021-03-12 13:37:47 +01:00
|
|
|
try (InputStream in = getAndroidFrameworkResourcesAsStream();
|
2022-06-11 19:17:09 +02:00
|
|
|
OutputStream out = Files.newOutputStream(apk.toPath())) {
|
2013-06-20 15:03:20 +02:00
|
|
|
IOUtils.copy(in, out);
|
|
|
|
return apk;
|
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new CantFindFrameworkResException(id);
|
|
|
|
}
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2016-10-06 15:18:16 +02:00
|
|
|
public void emptyFrameworkDirectory() throws AndrolibException {
|
2015-04-19 17:29:28 +02:00
|
|
|
File dir = getFrameworkDir();
|
|
|
|
File apk;
|
|
|
|
|
|
|
|
apk = new File(dir, "1.apk");
|
|
|
|
|
|
|
|
if (! apk.exists()) {
|
2016-10-06 15:18:16 +02:00
|
|
|
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
|
2015-04-19 17:29:28 +02:00
|
|
|
} else {
|
2016-10-06 15:18:16 +02:00
|
|
|
try {
|
2021-09-05 12:45:03 +02:00
|
|
|
if (apk.exists() && dir.listFiles().length > 1 && ! buildOptions.forceDeleteFramework) {
|
2016-10-06 15:18:16 +02:00
|
|
|
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
|
|
|
|
} else {
|
|
|
|
for (File file : dir.listFiles()) {
|
|
|
|
if (file.isFile() && file.getName().endsWith(".apk")) {
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Removing " + file.getName() + " framework file...");
|
2016-10-06 15:18:16 +02:00
|
|
|
file.delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (NullPointerException e) {
|
|
|
|
throw new AndrolibException(e);
|
|
|
|
}
|
2015-04-19 17:29:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-30 12:35:27 +01:00
|
|
|
public void listFrameworkDirectory() throws AndrolibException {
|
|
|
|
File dir = getFrameworkDir();
|
|
|
|
if (dir == null) {
|
|
|
|
LOGGER.severe("No framework directory found. Nothing to list.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (File file : Objects.requireNonNull(dir.listFiles())) {
|
|
|
|
if (file.isFile() && file.getName().endsWith(".apk")) {
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine(file.getName());
|
2020-11-30 12:35:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
public void installFramework(File frameFile) throws AndrolibException {
|
2021-09-05 12:45:03 +02:00
|
|
|
installFramework(frameFile, buildOptions.frameworkTag);
|
2014-11-02 03:07:02 +01:00
|
|
|
}
|
|
|
|
|
2013-04-30 14:55:33 +02:00
|
|
|
public void installFramework(File frameFile, String tag)
|
|
|
|
throws AndrolibException {
|
|
|
|
InputStream in = null;
|
2015-01-21 14:12:26 +01:00
|
|
|
ZipOutputStream out = null;
|
2013-04-30 14:55:33 +02:00
|
|
|
try {
|
2015-01-21 14:12:26 +01:00
|
|
|
ZipFile zip = new ZipFile(frameFile);
|
|
|
|
ZipEntry entry = zip.getEntry("resources.arsc");
|
2013-04-30 14:55:33 +02:00
|
|
|
|
|
|
|
if (entry == null) {
|
|
|
|
throw new AndrolibException("Can't find resources.arsc file");
|
|
|
|
}
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2013-04-30 14:55:33 +02:00
|
|
|
in = zip.getInputStream(entry);
|
|
|
|
byte[] data = IOUtils.toByteArray(in);
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2014-09-23 15:24:45 +02:00
|
|
|
ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
|
2013-04-30 14:55:33 +02:00
|
|
|
publicizeResources(data, arsc.getFlagsOffsets());
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2021-08-26 19:00:37 +02:00
|
|
|
File outFile = new File(getFrameworkDir(), arsc
|
|
|
|
.getOnePackage().getId()
|
2013-04-30 14:55:33 +02:00
|
|
|
+ (tag == null ? "" : '-' + tag)
|
|
|
|
+ ".apk");
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2022-06-20 16:41:45 +02:00
|
|
|
out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()));
|
2013-04-30 14:55:33 +02:00
|
|
|
out.setMethod(ZipOutputStream.STORED);
|
|
|
|
CRC32 crc = new CRC32();
|
2022-06-20 16:41:45 +02:00
|
|
|
crc.update(data, 0, data.length);
|
2015-01-21 14:12:26 +01:00
|
|
|
entry = new ZipEntry("resources.arsc");
|
2013-04-30 14:55:33 +02:00
|
|
|
entry.setSize(data.length);
|
2020-04-12 13:20:48 +02:00
|
|
|
entry.setMethod(ZipOutputStream.STORED);
|
2013-04-30 14:55:33 +02:00
|
|
|
entry.setCrc(crc.getValue());
|
2015-01-21 14:12:26 +01:00
|
|
|
out.putNextEntry(entry);
|
2013-04-30 14:55:33 +02:00
|
|
|
out.write(data);
|
2016-04-17 13:48:51 +02:00
|
|
|
out.closeEntry();
|
2021-08-24 15:31:41 +02:00
|
|
|
|
2016-04-17 13:48:51 +02:00
|
|
|
//Write fake AndroidManifest.xml file to support original aapt
|
2016-04-22 07:32:42 +02:00
|
|
|
entry = zip.getEntry("AndroidManifest.xml");
|
|
|
|
if (entry != null) {
|
|
|
|
in = zip.getInputStream(entry);
|
|
|
|
byte[] manifest = IOUtils.toByteArray(in);
|
|
|
|
CRC32 manifestCrc = new CRC32();
|
2022-06-20 16:41:45 +02:00
|
|
|
manifestCrc.update(manifest, 0, manifest.length);
|
2016-04-22 07:32:42 +02:00
|
|
|
entry.setSize(manifest.length);
|
|
|
|
entry.setCompressedSize(-1);
|
|
|
|
entry.setCrc(manifestCrc.getValue());
|
|
|
|
out.putNextEntry(entry);
|
|
|
|
out.write(manifest);
|
|
|
|
out.closeEntry();
|
|
|
|
}
|
2016-04-17 13:48:51 +02:00
|
|
|
|
2013-04-30 14:53:17 +02:00
|
|
|
zip.close();
|
2022-06-11 19:24:41 +02:00
|
|
|
LOGGER.fine("Framework installed to: " + outFile);
|
2013-04-30 14:55:33 +02:00
|
|
|
} catch (IOException ex) {
|
|
|
|
throw new AndrolibException(ex);
|
|
|
|
} finally {
|
2015-04-16 15:20:18 +02:00
|
|
|
IOUtils.closeQuietly(in);
|
|
|
|
IOUtils.closeQuietly(out);
|
2013-04-30 14:55:33 +02:00
|
|
|
}
|
|
|
|
}
|
2012-12-14 04:14:41 +01:00
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
public void publicizeResources(File arscFile) throws AndrolibException {
|
|
|
|
byte[] data = new byte[(int) arscFile.length()];
|
|
|
|
|
2022-06-20 16:41:45 +02:00
|
|
|
Path path = arscFile.toPath();
|
|
|
|
try(InputStream in = Files.newInputStream(path);
|
|
|
|
OutputStream out = Files.newOutputStream(path)) {
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
2013-06-20 15:03:20 +02:00
|
|
|
in.read(data);
|
|
|
|
publicizeResources(data);
|
|
|
|
out.write(data);
|
2014-09-23 15:24:45 +02:00
|
|
|
} catch (IOException ex){
|
2013-06-20 15:03:20 +02:00
|
|
|
throw new AndrolibException(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void publicizeResources(byte[] arsc) throws AndrolibException {
|
2014-09-23 15:24:45 +02:00
|
|
|
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
2021-03-12 13:37:47 +01:00
|
|
|
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets) {
|
2013-06-20 15:03:20 +02:00
|
|
|
for (FlagsOffset flags : flagsOffsets) {
|
|
|
|
int offset = flags.offset + 3;
|
|
|
|
int end = offset + 4 * flags.count;
|
|
|
|
while (offset < end) {
|
|
|
|
arsc[offset] |= (byte) 0x40;
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-27 13:37:14 +02:00
|
|
|
public File getFrameworkDir() throws AndrolibException {
|
2015-02-12 15:18:53 +01:00
|
|
|
if (mFrameworkDirectory != null) {
|
|
|
|
return mFrameworkDirectory;
|
|
|
|
}
|
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
String path;
|
|
|
|
|
|
|
|
// if a framework path was specified on the command line, use it
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.frameworkFolderLocation != null) {
|
|
|
|
path = buildOptions.frameworkFolderLocation;
|
2013-06-20 15:03:20 +02:00
|
|
|
} else {
|
2015-02-12 15:18:53 +01:00
|
|
|
File parentPath = new File(System.getProperty("user.home"));
|
|
|
|
|
|
|
|
if (OSDetection.isMacOSX()) {
|
|
|
|
path = parentPath.getAbsolutePath() + String.format("%1$sLibrary%1$sapktool%1$sframework", File.separatorChar);
|
2016-06-21 14:02:23 +02:00
|
|
|
} else if (OSDetection.isWindows()) {
|
|
|
|
path = parentPath.getAbsolutePath() + String.format("%1$sAppData%1$sLocal%1$sapktool%1$sframework", File.separatorChar);
|
2015-02-12 15:18:53 +01:00
|
|
|
} else {
|
2022-11-08 11:54:36 +01:00
|
|
|
String xdgDataFolder = System.getenv("XDG_DATA_HOME");
|
|
|
|
if (xdgDataFolder != null) {
|
|
|
|
path = xdgDataFolder + String.format("%1$sapktool%1$sframework", File.separatorChar);
|
|
|
|
} else {
|
|
|
|
path = parentPath.getAbsolutePath() + String.format("%1$s.local%1$sshare%1$sapktool%1$sframework", File.separatorChar);
|
|
|
|
}
|
2015-02-12 15:18:53 +01:00
|
|
|
}
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
File dir = new File(path);
|
2014-09-23 15:32:04 +02:00
|
|
|
|
2017-10-14 18:36:54 +02:00
|
|
|
if (!dir.isDirectory() && dir.isFile()) {
|
2017-10-14 18:36:07 +02:00
|
|
|
throw new AndrolibException("--frame-path is set to a file, not a directory.");
|
2017-08-15 21:52:46 +02:00
|
|
|
}
|
|
|
|
|
2014-11-02 03:07:02 +01:00
|
|
|
if (dir.getParentFile() != null && dir.getParentFile().isFile()) {
|
2017-10-14 18:36:07 +02:00
|
|
|
throw new AndrolibException("Please remove file at " + dir.getParentFile());
|
2014-09-23 15:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (! dir.exists()) {
|
|
|
|
if (! dir.mkdirs()) {
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.frameworkFolderLocation != null) {
|
2015-02-12 15:18:53 +01:00
|
|
|
LOGGER.severe("Can't create Framework directory: " + dir);
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
2020-11-28 22:04:08 +01:00
|
|
|
throw new AndrolibException(String.format(
|
|
|
|
"Can't create directory: (%s). Pass a writable path with --frame-path {DIR}. ", dir
|
|
|
|
));
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
}
|
2014-09-23 15:32:04 +02:00
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
if (buildOptions.frameworkFolderLocation == null) {
|
2019-04-25 00:45:57 +02:00
|
|
|
if (! dir.canWrite()) {
|
|
|
|
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
|
|
|
|
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
|
|
|
|
LOGGER.severe("Please be aware this is a volatile directory and frameworks could go missing, " +
|
|
|
|
"please utilize --frame-path if the default storage directory is unavailable");
|
|
|
|
|
|
|
|
dir = new File(System.getProperty("java.io.tmpdir"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-12 15:18:53 +01:00
|
|
|
mFrameworkDirectory = dir;
|
2013-06-20 15:03:20 +02:00
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
2018-02-28 17:25:56 +01:00
|
|
|
private int getAaptVersion() {
|
2021-09-05 12:45:03 +02:00
|
|
|
return buildOptions.isAapt2() ? 2 : 1;
|
2018-02-28 17:25:56 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 13:37:47 +01:00
|
|
|
public InputStream getAndroidFrameworkResourcesAsStream() {
|
|
|
|
return Jar.class.getResourceAsStream("/brut/androlib/android-framework.jar");
|
2013-06-20 15:03:20 +02:00
|
|
|
}
|
|
|
|
|
2017-05-02 08:21:09 +02:00
|
|
|
public void close() throws IOException {
|
2017-05-04 23:53:54 +02:00
|
|
|
if (mFramework != null) {
|
|
|
|
mFramework.close();
|
|
|
|
}
|
2017-05-02 08:21:09 +02:00
|
|
|
}
|
|
|
|
|
2021-09-05 12:45:03 +02:00
|
|
|
public BuildOptions buildOptions;
|
2013-06-20 15:03:20 +02:00
|
|
|
|
2022-11-08 12:24:41 +01:00
|
|
|
public Map<String, String> mResFileMapping = new HashMap();
|
2022-11-07 14:06:22 +01:00
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
// TODO: dirty static hack. I have to refactor decoding mechanisms.
|
|
|
|
public static boolean sKeepBroken = false;
|
|
|
|
|
2014-08-16 17:17:15 +02:00
|
|
|
private final static Logger LOGGER = Logger.getLogger(AndrolibResources.class.getName());
|
2013-06-20 15:03:20 +02:00
|
|
|
|
2015-02-12 15:18:53 +01:00
|
|
|
private File mFrameworkDirectory = null;
|
|
|
|
|
2017-05-02 08:21:09 +02:00
|
|
|
private ExtFile mFramework = null;
|
|
|
|
|
2013-06-20 15:03:20 +02:00
|
|
|
private String mMinSdkVersion = null;
|
|
|
|
private String mMaxSdkVersion = null;
|
|
|
|
private String mTargetSdkVersion = null;
|
|
|
|
private String mVersionCode = null;
|
|
|
|
private String mVersionName = null;
|
|
|
|
private String mPackageRenamed = null;
|
2014-01-07 17:59:37 +01:00
|
|
|
private String mPackageId = null;
|
|
|
|
|
2015-03-08 20:31:21 +01:00
|
|
|
private boolean mSharedLibrary = false;
|
2018-02-28 17:05:07 +01:00
|
|
|
private boolean mSparseResources = false;
|
2015-03-08 20:31:21 +01:00
|
|
|
|
2014-03-09 23:47:43 +01:00
|
|
|
private final static String[] IGNORED_PACKAGES = new String[] {
|
2019-03-03 14:14:07 +01:00
|
|
|
"android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry",
|
|
|
|
"FFFFFFFFFFFFFFFFFFFFFF" };
|
2012-10-11 17:26:00 +02:00
|
|
|
}
|