mirror of
https://github.com/iBotPeaches/Apktool.git
synced 2024-11-23 04:30:04 +00:00
new: featureFlags support for SDK 35 apps (#3706)
Some checks are pending
Analyze / Analyze (push) Waiting to run
CI / analyze-mac-aapt (aapt2_64) (push) Waiting to run
CI / analyze-mac-aapt (aapt_64) (push) Waiting to run
CI / analyze-linux-aapt (aapt) (push) Waiting to run
CI / analyze-linux-aapt (aapt2) (push) Waiting to run
CI / analyze-linux-aapt (aapt2_64) (push) Waiting to run
CI / analyze-linux-aapt (aapt_64) (push) Waiting to run
CI / analyze-windows-aapt (aapt.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt2.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt2_64.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt_64.exe) (push) Waiting to run
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, windows-latest) (push) Blocked by required conditions
CI / Build apktool.jar (push) Blocked by required conditions
Some checks are pending
Analyze / Analyze (push) Waiting to run
CI / analyze-mac-aapt (aapt2_64) (push) Waiting to run
CI / analyze-mac-aapt (aapt_64) (push) Waiting to run
CI / analyze-linux-aapt (aapt) (push) Waiting to run
CI / analyze-linux-aapt (aapt2) (push) Waiting to run
CI / analyze-linux-aapt (aapt2_64) (push) Waiting to run
CI / analyze-linux-aapt (aapt_64) (push) Waiting to run
CI / analyze-windows-aapt (aapt.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt2.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt2_64.exe) (push) Waiting to run
CI / analyze-windows-aapt (aapt_64.exe) (push) Waiting to run
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, windows-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, macOS-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, ubuntu-latest) (push) Blocked by required conditions
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, windows-latest) (push) Blocked by required conditions
CI / Build apktool.jar (push) Blocked by required conditions
* new: featureFlags support for SDK 35 apps This records all featureFlag attrs that were enabled when the APK was originally built. This is now required by AAPT2 to pass these flags and their enabled/disabled state if they are used in AndroidManifest.xml. The flags are recorded to apktool.yml and can be configured, if so desired. In normal usage, all flags should remain set to true (i.e. enabled). Sample APK sourced from AOSP Android 15. https://drive.google.com/file/d/1av7Ih7-YUXi73Hf0E3xlPv-V-nE_sXdt/view * test: adapt testapp for featureFlag
This commit is contained in:
parent
03a7c67082
commit
5c99919d94
@ -189,6 +189,15 @@ public class AaptInvoker {
|
|||||||
cmd.add("-x");
|
cmd.add("-x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mApkInfo.featureFlags.isEmpty()) {
|
||||||
|
List<String> featureFlags = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Boolean> entry : mApkInfo.featureFlags.entrySet()) {
|
||||||
|
featureFlags.add(entry.getKey() + "=" + entry.getValue());
|
||||||
|
}
|
||||||
|
cmd.add("--feature-flags");
|
||||||
|
cmd.add(String.join(",", featureFlags));
|
||||||
|
}
|
||||||
|
|
||||||
if (include != null) {
|
if (include != null) {
|
||||||
for (File file : include) {
|
for (File file : include) {
|
||||||
cmd.add("-I");
|
cmd.add("-I");
|
||||||
|
@ -21,6 +21,7 @@ import brut.androlib.exceptions.InFileNotFoundException;
|
|||||||
import brut.androlib.exceptions.OutDirExistsException;
|
import brut.androlib.exceptions.OutDirExistsException;
|
||||||
import brut.androlib.apk.ApkInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.res.ResourcesDecoder;
|
import brut.androlib.res.ResourcesDecoder;
|
||||||
|
import brut.androlib.res.xml.ResXmlPatcher;
|
||||||
import brut.androlib.src.SmaliDecoder;
|
import brut.androlib.src.SmaliDecoder;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
@ -321,6 +322,15 @@ public class ApkDecoder {
|
|||||||
mApkInfo.setMinSdkVersion(Integer.toString(mMinSdkVersion));
|
mApkInfo.setMinSdkVersion(Integer.toString(mMinSdkVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record feature flags
|
||||||
|
File manifest = new File(outDir, "AndroidManifest.xml");
|
||||||
|
List<String> featureFlags = ResXmlPatcher.pullManifestFeatureFlags(manifest);
|
||||||
|
if (featureFlags != null) {
|
||||||
|
for (String flag : featureFlags) {
|
||||||
|
mApkInfo.addFeatureFlag(flag, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// record uncompressed files
|
// record uncompressed files
|
||||||
try {
|
try {
|
||||||
Map<String, String> resFileMapping = mResDecoder.getResFileMapping();
|
Map<String, String> resFileMapping = mResDecoder.getResFileMapping();
|
||||||
|
@ -48,6 +48,7 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
public Map<String, String> sdkInfo = new LinkedHashMap<>();
|
public Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
public PackageInfo packageInfo = new PackageInfo();
|
public PackageInfo packageInfo = new PackageInfo();
|
||||||
public VersionInfo versionInfo = new VersionInfo();
|
public VersionInfo versionInfo = new VersionInfo();
|
||||||
|
public Map<String, Boolean> featureFlags = new LinkedHashMap<>();
|
||||||
public boolean sharedLibrary;
|
public boolean sharedLibrary;
|
||||||
public boolean sparseResources;
|
public boolean sparseResources;
|
||||||
public List<String> doNotCompress = new ArrayList<>();
|
public List<String> doNotCompress = new ArrayList<>();
|
||||||
@ -185,6 +186,10 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addFeatureFlag(String flag, boolean value) {
|
||||||
|
featureFlags.put(flag, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(File file) throws AndrolibException {
|
public void save(File file) throws AndrolibException {
|
||||||
try (YamlWriter writer = new YamlWriter(new FileOutputStream(file))) {
|
try (YamlWriter writer = new YamlWriter(new FileOutputStream(file))) {
|
||||||
write(writer);
|
write(writer);
|
||||||
@ -235,7 +240,7 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
}
|
}
|
||||||
case "sdkInfo": {
|
case "sdkInfo": {
|
||||||
sdkInfo.clear();
|
sdkInfo.clear();
|
||||||
reader.readMap(sdkInfo);
|
reader.readStringMap(sdkInfo);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "packageInfo": {
|
case "packageInfo": {
|
||||||
@ -248,6 +253,11 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
reader.readObject(versionInfo);
|
reader.readObject(versionInfo);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "featureFlags": {
|
||||||
|
featureFlags.clear();
|
||||||
|
reader.readBoolMap(featureFlags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "sharedLibrary": {
|
case "sharedLibrary": {
|
||||||
sharedLibrary = line.getValueBool();
|
sharedLibrary = line.getValueBool();
|
||||||
break;
|
break;
|
||||||
@ -270,9 +280,12 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
writer.writeString("apkFileName", apkFileName);
|
writer.writeString("apkFileName", apkFileName);
|
||||||
writer.writeBool("isFrameworkApk", isFrameworkApk);
|
writer.writeBool("isFrameworkApk", isFrameworkApk);
|
||||||
writer.writeObject("usesFramework", usesFramework);
|
writer.writeObject("usesFramework", usesFramework);
|
||||||
writer.writeStringMap("sdkInfo", sdkInfo);
|
writer.writeMap("sdkInfo", sdkInfo);
|
||||||
writer.writeObject("packageInfo", packageInfo);
|
writer.writeObject("packageInfo", packageInfo);
|
||||||
writer.writeObject("versionInfo", versionInfo);
|
writer.writeObject("versionInfo", versionInfo);
|
||||||
|
if (!featureFlags.isEmpty()) {
|
||||||
|
writer.writeMap("featureFlags", featureFlags);
|
||||||
|
}
|
||||||
writer.writeBool("sharedLibrary", sharedLibrary);
|
writer.writeBool("sharedLibrary", sharedLibrary);
|
||||||
writer.writeBool("sparseResources", sparseResources);
|
writer.writeBool("sparseResources", sparseResources);
|
||||||
if (!doNotCompress.isEmpty()) {
|
if (!doNotCompress.isEmpty()) {
|
||||||
|
@ -203,7 +203,7 @@ public class YamlReader {
|
|||||||
readList(list, (items, reader) -> items.add(reader.getLine().getValueInt()));
|
readList(list, (items, reader) -> items.add(reader.getLine().getValueInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readMap(Map<String, String> map) throws AndrolibException {
|
public void readStringMap(Map<String, String> map) throws AndrolibException {
|
||||||
readObject(map,
|
readObject(map,
|
||||||
line -> line.hasColon,
|
line -> line.hasColon,
|
||||||
(items, reader) -> {
|
(items, reader) -> {
|
||||||
@ -211,4 +211,13 @@ public class YamlReader {
|
|||||||
items.put(line.getKey(), line.getValue());
|
items.put(line.getKey(), line.getValue());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void readBoolMap(Map<String, Boolean> map) throws AndrolibException {
|
||||||
|
readObject(map,
|
||||||
|
line -> line.hasColon,
|
||||||
|
(items, reader) -> {
|
||||||
|
YamlLine line = reader.getLine();
|
||||||
|
items.put(line.getKey(), line.getValueBool());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ public class YamlWriter implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeStringMap(String key, Map<String, String> map) {
|
public <T> void writeMap(String key, Map<String, T> map) {
|
||||||
if (Objects.isNull(map)) {
|
if (Objects.isNull(map)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ public class YamlWriter implements Closeable {
|
|||||||
mWriter.println(escape(key) + ":");
|
mWriter.println(escape(key) + ":");
|
||||||
nextIndent();
|
nextIndent();
|
||||||
for (String mapKey : map.keySet()) {
|
for (String mapKey : map.keySet()) {
|
||||||
writeString(mapKey, map.get(mapKey));
|
writeString(mapKey, String.valueOf(map.get(mapKey)));
|
||||||
}
|
}
|
||||||
prevIndent();
|
prevIndent();
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import javax.xml.transform.dom.DOMSource;
|
|||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
import javax.xml.xpath.*;
|
import javax.xml.xpath.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public final class ResXmlPatcher {
|
public final class ResXmlPatcher {
|
||||||
@ -357,6 +358,42 @@ public final class ResXmlPatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all feature flags set on permissions in AndroidManifest.xml.
|
||||||
|
*
|
||||||
|
* @param file File for AndroidManifest.xml
|
||||||
|
*/
|
||||||
|
public static List<String> pullManifestFeatureFlags(File file) {
|
||||||
|
if (!file.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Document doc = loadDocument(file);
|
||||||
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||||
|
XPathExpression expression = xPath.compile("/manifest/permission");
|
||||||
|
|
||||||
|
Object result = expression.evaluate(doc, XPathConstants.NODESET);
|
||||||
|
NodeList nodes = (NodeList) result;
|
||||||
|
|
||||||
|
List<String> featureFlags = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
Node node = nodes.item(i);
|
||||||
|
NamedNodeMap attrs = node.getAttributes();
|
||||||
|
Node featureFlagAttr = attrs.getNamedItem("android:featureFlag");
|
||||||
|
|
||||||
|
if (featureFlagAttr != null) {
|
||||||
|
featureFlags.add(featureFlagAttr.getNodeValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureFlags;
|
||||||
|
|
||||||
|
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param file File to load into Document
|
* @param file File to load into Document
|
||||||
|
@ -11,4 +11,5 @@
|
|||||||
<meta-data name="test_int" value="12345" />
|
<meta-data name="test_int" value="12345" />
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||||
|
<permission android:featureFlag="brut.feature.flag" android:label="Test Permission" android:name="brut.permission.TEST" android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="signature"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -9,6 +9,8 @@ packageInfo:
|
|||||||
versionInfo:
|
versionInfo:
|
||||||
versionCode: '1'
|
versionCode: '1'
|
||||||
versionName: '1.0'
|
versionName: '1.0'
|
||||||
|
featureFlags:
|
||||||
|
brut.feature.flag: true
|
||||||
doNotCompress:
|
doNotCompress:
|
||||||
- assets/0byte_file.jpg
|
- assets/0byte_file.jpg
|
||||||
sparseResources: false
|
sparseResources: false
|
||||||
|
Loading…
Reference in New Issue
Block a user