Apktool/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java

894 lines
32 KiB
Java
Raw Normal View History

2012-09-20 03:27:35 +02:00
/**
2018-02-16 14:26:53 +01:00
* Copyright (C) 2018 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.res;
import brut.androlib.AndrolibException;
import brut.androlib.ApkOptions;
2012-09-20 03:27:35 +02:00
import brut.androlib.err.CantFindFrameworkResException;
import brut.androlib.meta.MetaInfo;
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;
import brut.directory.ExtFile;
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;
import brut.androlib.res.xml.ResXmlPatcher;
2012-09-20 03:27:35 +02:00
import brut.common.BrutException;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import brut.util.Duo;
import brut.util.Jar;
import brut.util.OS;
import brut.util.OSDetection;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlSerializer;
2012-09-20 03:27:35 +02:00
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
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
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
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 {
LOGGER.info("Loading resource table...");
2014-09-23 15:24:45 +02:00
ResPackage[] pkgs = getResPackagesFromApk(apkFile, resTable, sKeepBroken);
2013-06-20 15:03:20 +02:00
ResPackage pkg = null;
switch (pkgs.length) {
case 1:
pkg = pkgs[0];
break;
case 2:
if (pkgs[0].getName().equals("android")) {
LOGGER.warning("Skipping \"android\" package group");
pkg = pkgs[1];
break;
2013-06-20 15:03:20 +02:00
} else if (pkgs[0].getName().equals("com.htc")) {
LOGGER.warning("Skipping \"htc\" package group");
pkg = pkgs[1];
break;
2013-06-20 15:03:20 +02:00
}
default:
pkg = selectPkgWithMostResSpecs(pkgs);
2013-06-20 15:03:20 +02:00
break;
}
if (pkg == null) {
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;
}
public ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs)
throws AndrolibException {
int id = 0;
int value = 0;
for (ResPackage resPackage : pkgs) {
if (resPackage.getResSpecCount() > value && ! resPackage.getName().equalsIgnoreCase("android")) {
value = resPackage.getResSpecCount();
id = resPackage.getId();
}
}
// if id is still 0, we only have one pkgId which is "android" -> 1
return (id == 0) ? pkgs[0] : pkgs[1];
}
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);
LOGGER.info("Loading resource table from file: " + apk);
mFramework = new ExtFile(apk);
ResPackage[] pkgs = getResPackagesFromApk(mFramework, resTable, true);
2013-06-20 15:03:20 +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");
} else {
pkg = pkgs[0];
2013-06-20 15:03:20 +02:00
}
if (pkg.getId() != id) {
2014-09-23 15:24:45 +02:00
throw new AndrolibException("Expected pkg of id: " + String.valueOf(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 {
Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
// Set ResAttrDecoder
duo.m2.setAttrDecoder(new ResAttrDecoder());
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
// Fake ResPackage
attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null));
Directory inApk, out;
try {
inApk = apkFile.getDirectory();
out = new FileDirectory(outDir);
LOGGER.info("Decoding AndroidManifest.xml with only framework resources...");
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);
}
}
public void adjustPackageManifest(ResTable resTable, String filePath)
2013-06-20 15:03:20 +02:00
throws AndrolibException {
// compare resources.arsc package name to the one present in AndroidManifest
ResPackage resPackage = resTable.getCurrentResPackage();
String packageOriginal = resPackage.getName();
mPackageRenamed = resTable.getPackageRenamed();
resTable.setPackageId(resPackage.getId());
resTable.setPackageOriginal(packageOriginal);
// 1) Check if packageOriginal === mPackageRenamed
// 2) Check if packageOriginal is ignored via IGNORED_PACKAGES
// 2a) If its ignored, make sure the mPackageRenamed isn't explicitly allowed
if (packageOriginal.equalsIgnoreCase(mPackageRenamed) ||
(Arrays.asList(IGNORED_PACKAGES).contains(packageOriginal) &&
! Arrays.asList(ALLOWED_PACKAGES).contains(mPackageRenamed))) {
2013-06-20 15:03:20 +02:00
LOGGER.info("Regular manifest package...");
} else {
LOGGER.info("Renamed manifest package found! Replacing " + mPackageRenamed + " with " + packageOriginal);
ResXmlPatcher.renameManifestPackage(new File(filePath), packageOriginal);
}
}
public void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir)
2013-06-20 15:03:20 +02:00
throws AndrolibException {
2013-06-20 15:03:20 +02:00
Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
2014-09-23 15:24:45 +02:00
attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
2013-06-20 15:03:20 +02:00
Directory inApk, in = null, out;
try {
inApk = apkFile.getDirectory();
out = new FileDirectory(outDir);
LOGGER.info("Decoding AndroidManifest.xml with resources...");
2014-09-23 15:24:45 +02:00
fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");
// Remove versionName / versionCode (aapt API 16)
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
adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml");
ResXmlPatcher.removeManifestVersions(new File(
outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"));
mPackageId = String.valueOf(resTable.getPackageId());
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void decode(ResTable resTable, ExtFile apkFile, File outDir)
throws AndrolibException {
Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();
attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
Directory inApk, in = null, out;
try {
out = new FileDirectory(outDir);
inApk = apkFile.getDirectory();
out = out.createDir("res");
2013-06-20 15:03:20 +02:00
if (inApk.containsDir("res")) {
in = inApk.getDir("res");
}
if (in == null && inApk.containsDir("r")) {
in = inApk.getDir("r");
}
2016-08-06 13:56:17 +02:00
if (in == null && inApk.containsDir("R")) {
in = inApk.getDir("R");
}
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);
LOGGER.info("Decoding file-resources...");
for (ResResource res : pkg.listFiles()) {
fileDecoder.decode(res, in, out);
}
LOGGER.info("Decoding values */* XMLs...");
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
generateValuesFile(valuesFile, out, xmlSerializer);
}
generatePublicXml(pkg, out, xmlSerializer);
}
AndrolibException decodeError = duo.m2.getFirstError();
if (decodeError != null) {
throw decodeError;
}
}
public void setSdkInfo(Map<String, String> map) {
if (map != null) {
mMinSdkVersion = map.get("minSdkVersion");
mTargetSdkVersion = map.get("targetSdkVersion");
mMaxSdkVersion = map.get("maxSdkVersion");
}
}
public void setVersionInfo(VersionInfo versionInfo) {
if (versionInfo != null) {
mVersionCode = versionInfo.versionCode;
mVersionName = versionInfo.versionName;
2013-06-20 15:03:20 +02:00
}
}
public void setPackageRenamed(PackageInfo packageInfo) {
if (packageInfo != null) {
mPackageRenamed = packageInfo.renameManifestPackage;
2013-06-20 15:03:20 +02:00
}
}
public void setPackageId(PackageInfo packageInfo) {
if (packageInfo != null) {
mPackageId = packageInfo.forcedPackageId;
}
2013-06-20 15:03:20 +02:00
}
public void setSharedLibrary(boolean flag) {
mSharedLibrary = flag;
}
2017-09-19 21:09:25 +02:00
public String checkTargetSdkVersionBounds() {
int target = mapSdkShorthandToVersion(mTargetSdkVersion);
int min = (mMinSdkVersion != null) ? mapSdkShorthandToVersion(mMinSdkVersion) : 0;
int max = (mMaxSdkVersion != null) ? mapSdkShorthandToVersion(mMaxSdkVersion) : target;
target = Math.min(max, target);
target = Math.max(min, target);
return Integer.toString(target);
}
public void aaptPackage(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
throws AndrolibException {
boolean customAapt = false;
String aaptPath = apkOptions.aaptPath;
List<String> cmd = new ArrayList<String>();
// path for aapt binary
2014-09-23 15:24:45 +02:00
if (! aaptPath.isEmpty()) {
File aaptFile = new File(aaptPath);
if (aaptFile.canRead() && aaptFile.exists()) {
aaptFile.setExecutable(true);
cmd.add(aaptFile.getPath());
customAapt = true;
if (apkOptions.verbose) {
LOGGER.info(aaptFile.getPath() + " being used as aapt location.");
}
} else {
LOGGER.warning("aapt location could not be found. Defaulting back to default");
try {
cmd.add(getAaptBinaryFile().getAbsolutePath());
} catch (BrutException ignored) {
cmd.add("aapt");
}
}
} else {
try {
cmd.add(getAaptBinaryFile().getAbsolutePath());
} catch (BrutException ignored) {
cmd.add("aapt");
}
}
2013-06-20 15:03:20 +02:00
cmd.add("p");
if (apkOptions.verbose) { // output aapt verbose
2013-06-20 15:03:20 +02:00
cmd.add("-v");
}
if (apkOptions.updateFiles) {
2013-06-20 15:03:20 +02:00
cmd.add("-u");
}
if (apkOptions.debugMode) { // inject debuggable="true" into manifest
cmd.add("--debug-mode");
}
// 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)
if (mPackageId != null && ! customAapt && ! mSharedLibrary) {
2013-06-20 15:03:20 +02:00
cmd.add("--forced-package-id");
cmd.add(mPackageId);
}
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.
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);
}
cmd.add("--no-version-vectors");
2013-06-20 15:03:20 +02:00
cmd.add("-F");
cmd.add(apkFile.getAbsolutePath());
if (apkOptions.isFramework) {
2013-06-20 15:03:20 +02:00
cmd.add("-x");
}
if (apkOptions.doNotCompress != null) {
for (String file : apkOptions.doNotCompress) {
cmd.add("-0");
cmd.add(file);
}
}
if (!apkOptions.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]));
if (apkOptions.verbose) {
LOGGER.info("command ran: ");
LOGGER.info(cmd.toString());
}
2013-06-20 15:03:20 +02:00
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
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) {
switch (sdkVersion) {
case "M":
return ResConfigFlags.SDK_MNC;
case "N":
return ResConfigFlags.SDK_NOUGAT;
case "O":
return ResConfigFlags.SDK_OREO;
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 void tagSmaliResIDs(ResTable resTable, File smaliDir)
throws AndrolibException {
new ResSmaliUpdater().tagResIDs(resTable, smaliDir);
}
public void updateSmaliResIDs(ResTable resTable, File smaliDir)
throws AndrolibException {
new ResSmaliUpdater().updateResIDs(resTable, smaliDir);
}
public Duo<ResFileDecoder, AXmlResourceParser> getResFileDecoder() {
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
decoders.setDecoder("raw", new ResRawStreamDecoder());
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
AXmlResourceParser axmlParser = new AXmlResourceParser();
axmlParser.setAttrDecoder(new ResAttrDecoder());
2014-09-23 15:24:45 +02:00
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
2013-06-20 15:03:20 +02:00
2014-09-23 15:24:45 +02:00
return new Duo<ResFileDecoder, AXmlResourceParser>(new ResFileDecoder(decoders), axmlParser);
2013-06-20 15:03:20 +02:00
}
public Duo<ResFileDecoder, AXmlResourceParser> getManifestFileDecoder() {
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
AXmlResourceParser axmlParser = new AXmlResourceParser();
2014-09-23 15:24:45 +02:00
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser,getResXmlSerializer()));
2013-06-20 15:03:20 +02:00
2014-09-23 15:24:45 +02:00
return new Duo<ResFileDecoder, AXmlResourceParser>(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 {
Directory dir = apkFile.getDirectory();
BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"));
try {
return ARSCDecoder.decode(bfi, false, keepBroken, resTable).getPackages();
} finally {
try {
bfi.close();
2017-05-02 14:16:07 +02:00
} catch (IOException ignored) {}
}
2013-06-20 15:03:20 +02:00
} catch (DirectoryException 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;
}
}
apk = new File(dir, String.valueOf(id) + ".apk");
if (apk.exists()) {
return apk;
}
if (id == 1) {
2014-09-23 15:24:45 +02:00
try (InputStream in = AndrolibResources.class.getResourceAsStream("/brut/androlib/android-framework.jar");
OutputStream out = new FileOutputStream(apk)) {
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);
}
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()) {
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
2015-04-19 17:29:28 +02:00
} else {
try {
if (apk.exists() && dir.listFiles().length > 1 && ! apkOptions.forceDeleteFramework) {
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")) {
LOGGER.info("Removing " + file.getName() + " framework file...");
file.delete();
}
}
}
} catch (NullPointerException e) {
throw new AndrolibException(e);
}
2015-04-19 17:29:28 +02:00
}
}
public void installFramework(File frameFile) throws AndrolibException {
installFramework(frameFile, apkOptions.frameworkTag);
}
2013-04-30 14:55:33 +02:00
public void installFramework(File frameFile, String tag)
throws AndrolibException {
InputStream in = null;
ZipOutputStream out = null;
2013-04-30 14:55:33 +02:00
try {
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");
}
2013-04-30 14:55:33 +02:00
in = zip.getInputStream(entry);
byte[] data = IOUtils.toByteArray(in);
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());
2013-04-30 14:55:33 +02:00
File outFile = new File(getFrameworkDir(), String.valueOf(arsc
.getOnePackage().getId())
+ (tag == null ? "" : '-' + tag)
+ ".apk");
out = new ZipOutputStream(new FileOutputStream(outFile));
2013-04-30 14:55:33 +02:00
out.setMethod(ZipOutputStream.STORED);
CRC32 crc = new CRC32();
crc.update(data);
entry = new ZipEntry("resources.arsc");
2013-04-30 14:55:33 +02:00
entry.setSize(data.length);
entry.setCrc(crc.getValue());
out.putNextEntry(entry);
2013-04-30 14:55:33 +02:00
out.write(data);
out.closeEntry();
//Write fake AndroidManifest.xml file to support original aapt
entry = zip.getEntry("AndroidManifest.xml");
if (entry != null) {
in = zip.getInputStream(entry);
byte[] manifest = IOUtils.toByteArray(in);
CRC32 manifestCrc = new CRC32();
manifestCrc.update(manifest);
entry.setSize(manifest.length);
entry.setCompressedSize(-1);
entry.setCrc(manifestCrc.getValue());
out.putNextEntry(entry);
out.write(manifest);
out.closeEntry();
}
zip.close();
2013-04-30 14:55:33 +02:00
LOGGER.info("Framework installed to: " + outFile);
} 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
}
}
2013-06-20 15:03:20 +02:00
public void publicizeResources(File arscFile) throws AndrolibException {
byte[] data = new byte[(int) arscFile.length()];
2014-09-23 15:24:45 +02:00
try(InputStream in = new FileInputStream(arscFile);
OutputStream out = new FileOutputStream(arscFile)) {
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
}
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets)
throws AndrolibException {
for (FlagsOffset flags : flagsOffsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while (offset < end) {
arsc[offset] |= (byte) 0x40;
offset += 4;
}
}
}
public File getFrameworkDir() throws AndrolibException {
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
if (apkOptions.frameworkFolderLocation != null) {
path = apkOptions.frameworkFolderLocation;
2013-06-20 15:03:20 +02:00
} else {
File parentPath = new File(System.getProperty("user.home"));
if (OSDetection.isMacOSX()) {
path = parentPath.getAbsolutePath() + String.format("%1$sLibrary%1$sapktool%1$sframework", File.separatorChar);
} else if (OSDetection.isWindows()) {
path = parentPath.getAbsolutePath() + String.format("%1$sAppData%1$sLocal%1$sapktool%1$sframework", File.separatorChar);
} else {
path = parentPath.getAbsolutePath() + String.format("%1$s.local%1$sshare%1$sapktool%1$sframework", File.separatorChar);
}
File fullPath = new File(path);
if (! fullPath.canWrite()) {
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
fullPath.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");
path = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
}
2013-06-20 15:03:20 +02:00
}
File dir = new File(path);
if (!dir.isDirectory() && dir.isFile()) {
throw new AndrolibException("--frame-path is set to a file, not a directory.");
}
if (dir.getParentFile() != null && dir.getParentFile().isFile()) {
throw new AndrolibException("Please remove file at " + dir.getParentFile());
}
if (! dir.exists()) {
if (! dir.mkdirs()) {
if (apkOptions.frameworkFolderLocation != null) {
LOGGER.severe("Can't create Framework directory: " + dir);
2013-06-20 15:03:20 +02:00
}
throw new AndrolibException("Can't create directory: " + dir);
}
}
mFrameworkDirectory = dir;
2013-06-20 15:03:20 +02:00
return dir;
}
/**
* Using a prebuilt aapt and forcing its use, allows us to prevent bugs from older aapt's
* along with having a finer control over the build procedure.
*
2013-03-29 20:45:54 +01:00
* Aapt can still be overridden via --aapt/-a on build, but specific features will be disabled
*
2013-06-20 15:03:20 +02:00
* @url https://github.com/iBotPeaches/platform_frameworks_base
* @throws AndrolibException
*/
public File getAaptBinaryFile() throws AndrolibException {
File aaptBinary;
2017-08-27 15:54:36 +02:00
if (! OSDetection.is64Bit() && ! OSDetection.isWindows()) {
throw new AndrolibException("32 bit OS detected. No 32 bit binaries available.");
}
2013-06-20 15:03:20 +02:00
try {
if (OSDetection.isMacOSX()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/macosx/aapt", AndrolibResources.class);
2013-06-20 15:03:20 +02:00
} else if (OSDetection.isUnix()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/linux/aapt", AndrolibResources.class);
2013-06-20 15:03:20 +02:00
} else if (OSDetection.isWindows()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/aapt/windows/aapt.exe", AndrolibResources.class);
2013-06-20 15:03:20 +02:00
} else {
LOGGER.warning("Unknown Operating System: " + OSDetection.returnOS());
2013-06-20 15:03:20 +02:00
return null;
}
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
if (aaptBinary.setExecutable(true)) {
return aaptBinary;
}
System.err.println("Can't set aapt binary as executable");
throw new AndrolibException("Can't set aapt binary as executable");
2013-06-20 15:03:20 +02:00
}
public File getAndroidResourcesFile() throws AndrolibException {
try {
2014-09-23 15:24:45 +02:00
return Jar.getResourceAsFile("/brut/androlib/android-framework.jar");
2013-06-20 15:03:20 +02:00
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
public void close() throws IOException {
if (mFramework != null) {
mFramework.close();
}
}
public ApkOptions apkOptions;
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
private File mFrameworkDirectory = null;
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;
private String mPackageId = null;
private boolean mSharedLibrary = false;
private final static String[] IGNORED_PACKAGES = new String[] {
"android", "com.htc", "miui", "com.lge", "com.lge.internal", "yi", "com.miui.core", "flyme",
"air.com.adobe.appentry" };
private final static String[] ALLOWED_PACKAGES = new String[] {
"com.miui" };
}