From 6a1303f4d64df190eeecd2ccfb6865eaf9ed3f9c Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sat, 19 Jun 2021 11:57:09 +0800 Subject: [PATCH] Use tmpdir --- .../main/java/org/lsposed/patch/LSPatch.java | 366 ++++++++++-------- .../patch/task/BuildAndSignApkTask.java | 98 ----- .../patch/util/ApkSignatureHelper.java | 1 - .../lsposed/patch/util/ManifestParser.java | 37 +- 4 files changed, 228 insertions(+), 274 deletions(-) delete mode 100644 patch/src/main/java/org/lsposed/patch/task/BuildAndSignApkTask.java diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index add7582..cf22704 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -1,47 +1,49 @@ package org.lsposed.patch; -import static org.apache.commons.io.FileUtils.copyDirectory; -import static org.apache.commons.io.FileUtils.copyFile; - +import com.android.apksigner.ApkSignerTool; import com.android.tools.build.apkzlib.zip.StoredEntry; import com.android.tools.build.apkzlib.zip.ZFile; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; -import com.wind.meditor.base.BaseCommand; -import com.wind.meditor.core.FileProcesser; +import com.wind.meditor.core.ManifestEditor; import com.wind.meditor.property.AttributeItem; import com.wind.meditor.property.ModificationProperty; import com.wind.meditor.utils.NodeValue; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.lsposed.lspatch.share.Constants; -import org.lsposed.patch.task.BuildAndSignApkTask; import org.lsposed.patch.util.ApkSignatureHelper; import org.lsposed.patch.util.ManifestParser; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FilenameFilter; import java.io.IOException; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; -import java.util.Date; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.regex.Pattern; public class LSPatch { - @Parameter(description = "apk path") - private String apkPath; + static class PatchError extends Error { + PatchError(String message) { + super(message); + } + } - private String unzipApkFilePath; + @Parameter(description = "apk") + private String apkPath = null; - @Parameter(names = "--help", help = true, order = 0) + @Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message") private boolean help = false; @Parameter(names = {"-o", "--output"}, description = "Output apk file") @@ -53,17 +55,30 @@ public class LSPatch { @Parameter(names = {"-p", "--proxyname"}, description = "Special proxy app name with full dot path") private String proxyName = "org.lsposed.lspatch.appstub.LSPApplicationStub"; - @Parameter(names = {"-d", "--debuggable"}, description = "Set true to make the app debuggable, otherwise set 0 (default) to make the app non-debuggable") + @Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable") private boolean debuggableFlag = false; @Parameter(names = {"-l", "--sigbypasslv"}, description = "Signature bypass level. 0 (disable), 1 (pm), 2 (pm+openat). default 0") private int sigbypassLevel = 0; + @Parameter(names = {"--v1"}, description = "Sign with v1 signature") + private boolean v1 = true; + + @Parameter(names = {"--v2"}, description = "Sign with v2 signature") + private boolean v2 = true; + + @Parameter(names = {"--v3"}, description = "Sign with v3 signature") + private boolean v3 = true; + + @Parameter(names = {"-v", "--verbose"}, description = "Verbose output") + private boolean verbose = false; + private int dexFileCount = 0; - private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files"; private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini"; - private final static String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini"; + private static final String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini"; + private static final String ORIGIN_APK_ASSET_PATH = "assets/origin_apk.bin"; + private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; private static final String[] APK_LIB_PATH_ARRAY = { "lib/armeabi-v7a", "lib/armeabi", @@ -80,191 +95,238 @@ public class LSPatch { .addObject(lsPatch) .build(); jCommander.parse(args); - lsPatch.doCommandLine(); + try { + lsPatch.doCommandLine(); + } catch (PatchError e) { + System.err.println(e.getMessage()); + } } - public void doCommandLine() throws IOException { + public void doCommandLine() throws PatchError, IOException { + if (help) { + jCommander.usage(); + return; + } if (apkPath == null || apkPath.isEmpty()) { jCommander.usage(); return; } - File srcApkFile = new File(apkPath); + File srcApkFile = new File(apkPath).getAbsoluteFile(); - if (!srcApkFile.exists()) { - System.out.println("The source apk file not exsit, please choose another one."); - return; - } + if (!srcApkFile.exists()) + throw new PatchError("The source apk file does not exit. Please provide a correct path."); - File finalApk = new File(String.format("%s-%s-unsigned.apk", srcApkFile.getName(), - Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel)); - FileUtils.copyFile(srcApkFile, finalApk); + var workingDir = Files.createTempDirectory("LSPatch").toFile(); + try { + File tmpApk = new File(workingDir, String.format("%s-%s-unsigned.apk", srcApkFile.getName(), + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel)); + if (verbose) { + System.out.println("work dir: " + workingDir); + System.out.println("apk path: " + srcApkFile); + } - ZFile zFile = ZFile.openReadWrite(finalApk); + String apkFileName = srcApkFile.getName(); - String currentDir = new File(".").getAbsolutePath(); - System.out.println("work dir: " + currentDir); - System.out.println("apk path: " + apkPath); + if (outputPath == null || outputPath.length() == 0) { + outputPath = String.format("%s-lv%s-xposed-signed.apk", FilenameUtils.getBaseName(apkFileName), sigbypassLevel); + } - if (outputPath == null || outputPath.length() == 0) { - String sig = Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel; - outputPath = String.format("%s-%s-xposed-signed.apk", new File(apkPath).getName(), sig); - } + File outputFile = new File(outputPath); + if (outputFile.exists() && !forceOverwrite) + throw new PatchError(outputPath + " exists. Use --force to overwrite"); - File outputFile = new File(outputPath); - if (outputFile.exists() && !forceOverwrite) { - System.err.println(outputPath + " exists, use --force to overwrite"); - return; - } + System.out.println("Copying to tmp apk..."); - String outputApkFileParentPath = outputFile.getParent(); - if (outputApkFileParentPath == null) { - String absPath = outputFile.getAbsolutePath(); - int index = absPath.lastIndexOf(File.separatorChar); - outputApkFileParentPath = absPath.substring(0, index); - } + FileUtils.copyFile(srcApkFile, tmpApk); - String apkFileName = srcApkFile.getName(); + System.out.println("Parsing original apk..."); + ZFile zFile = ZFile.openReadWrite(tmpApk); - String tempFilePath = outputApkFileParentPath + File.separator + - currentTimeStr() + "-tmp" + File.separator; + // save the apk original signature info, to support crach signature. + String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath); + if (originalSignature == null || originalSignature.isEmpty()) { + throw new PatchError("get original signature failed"); + } + if (verbose) + System.out.println("Original signature\n" + originalSignature); + try (var is = new ByteArrayInputStream(originalSignature.getBytes(StandardCharsets.UTF_8))) { + zFile.add(SIGNATURE_INFO_ASSET_PATH, is); + } catch (IOException e) { + throw new PatchError("Error when saving signature: " + e); + } - unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator; + // get the dex count in the apk zip file + dexFileCount = findDexFileCount(zFile); - // save the apk original signature info, to support crach signature. - String originalSignature = ApkSignatureHelper.getApkSignInfo(apkPath); - if (originalSignature == null || originalSignature.isEmpty()) { - throw new IllegalStateException("get original signature failed"); - } - File osi = new File((unzipApkFilePath + SIGNATURE_INFO_ASSET_PATH).replace("/", File.separator)); - FileUtils.write(osi, originalSignature, Charset.defaultCharset()); - zFile.add(SIGNATURE_INFO_ASSET_PATH, new FileInputStream(osi)); + if (verbose) + System.out.println("dexFileCount: " + dexFileCount); - // get the dex count in the apk zip file - dexFileCount = findDexFileCount(zFile); + // copy out manifest file from zlib + var manifestEntry = zFile.get(ANDROID_MANIFEST_XML); + if (manifestEntry == null) + throw new PatchError("Provided file is not a valid apk"); - System.out.println("dexFileCount: " + dexFileCount); + // parse the app main application full name from the manifest file + ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestEntry.open()); + if (pair == null) + throw new PatchError("Failed to parse AndroidManifest.xml"); + String applicationName = pair.applicationName == null ? "" : pair.applicationName; - // copy out manifest file from zlib - int copySize = IOUtils.copy(zFile.get("AndroidManifest.xml").open(), new FileOutputStream(unzipApkFilePath + "AndroidManifest.xml.bak")); - if (copySize <= 0) { - throw new IllegalStateException("wtf"); - } - String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml.bak"; + if (verbose) + System.out.println("original application name: " + applicationName); - // parse the app main application full name from the manifest file - ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath); - String applicationName = null; - if (pair != null && pair.applicationName != null) { - applicationName = pair.applicationName; - } + System.out.println("Patching apk..."); + // modify manifest + try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open()))) { + zFile.add(APPLICATION_NAME_ASSET_PATH, is); + } catch (IOException e) { + throw new PatchError("Error when modifying manifest: " + e); + } - System.out.println("original application name: " + applicationName); + // save original main application name to asset file even its empty + try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) { + zFile.add(APPLICATION_NAME_ASSET_PATH, is); + } catch (IOException e) { + throw new PatchError("Error when saving signature: " + e); + } - // modify manifest - modifyManifestFile(manifestFilePath, new File(unzipApkFilePath, "AndroidManifest.xml").getPath()); + // copy so and dex files into the unzipped apk + Set apkArchs = new HashSet<>(); - // save original main application name to asset file even its empty - File oan = new File((unzipApkFilePath + APPLICATION_NAME_ASSET_PATH).replace("/", File.separator)); - FileUtils.write(oan, applicationName, Charset.defaultCharset()); - zFile.add(APPLICATION_NAME_ASSET_PATH, new FileInputStream(oan)); - - // copy so and dex files into the unzipped apk - Set apkArchs = new HashSet<>(); - - System.out.println("search target apk library arch.."); - for (StoredEntry storedEntry : zFile.entries()) { - for (String arch : APK_LIB_PATH_ARRAY) { - if (storedEntry.getCentralDirectoryHeader().getName().startsWith(arch)) { - apkArchs.add(arch); + if (verbose) + System.out.println("search target apk library arch.."); + for (StoredEntry storedEntry : zFile.entries()) { + for (String arch : APK_LIB_PATH_ARRAY) { + if (storedEntry.getCentralDirectoryHeader().getName().startsWith(arch)) { + apkArchs.add(arch); + } } } - } - if (apkArchs.isEmpty()) { - apkArchs.add(APK_LIB_PATH_ARRAY[0]); - } + if (apkArchs.isEmpty()) { + apkArchs.add(APK_LIB_PATH_ARRAY[0]); + } - for (String arch : apkArchs) { - // lib/armeabi-v7a -> armeabi-v7a - String justArch = arch.substring(arch.indexOf('/')); - File sod = new File("list-so", justArch); - File[] files = sod.listFiles(); - if (files == null) { - System.out.println("Warning: Nothing so file has been copied in " + sod.getPath()); - continue; + for (String arch : apkArchs) { + // lib/armeabi-v7a -> armeabi-v7a + String justArch = arch.substring(arch.indexOf('/')); + File sod = new File("list-so", justArch); + File[] files = sod.listFiles(); + if (files == null) { + System.err.println("Warning: No so file has been copied in " + sod.getPath()); + continue; + } + for (File file : files) { + zFile.add(arch + "/" + file.getName(), new FileInputStream(file)); + if (verbose) + System.out.println("add " + file.getPath()); + } + } + + // copy all dex files in list-dex + File[] files = new File("list-dex").listFiles(); + if (files == null || files.length == 0) { + System.err.println("Warning: No dex file has been copied"); + return; } for (File file : files) { - zFile.add(arch + "/" + file.getName(), new FileInputStream(file)); - System.out.println("add " + file.getPath()); + String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex"; + zFile.add(copiedDexFileName, new FileInputStream(file)); + dexFileCount++; } - } - // copy all dex files in list-dex - File[] files = new File("list-dex").listFiles(); - if (files == null || files.length == 0) { - System.out.println("Warning: Nothing dex file has been copied"); - return; - } - for (File file : files) { - String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex"; - zFile.add(copiedDexFileName, new FileInputStream(file)); - dexFileCount++; - } + // copy origin apk to assets + // convenient to bypass some check like CRC + if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) { + zFile.add(ORIGIN_APK_ASSET_PATH, new FileInputStream(srcApkFile)); + } - // copy origin apk to assets - // convenient to bypass some check like CRC - if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) { - zFile.add("assets/origin_apk.bin", new FileInputStream(srcApkFile)); - } - - File[] listAssets = new File("list-assets").listFiles(); - if (listAssets == null || listAssets.length == 0) { - System.out.println("Warning: No assets file copyied"); - } - else { - for (File f : listAssets) { - if (f.isDirectory()) { - throw new IllegalStateException("unsupport directory in assets"); + File[] listAssets = new File("list-assets").listFiles(); + if (listAssets == null || listAssets.length == 0) { + System.err.println("Warning: No assets file copyied"); + } else { + for (File f : listAssets) { + if (f.isDirectory()) { + throw new PatchError("unsupported directory in assets"); + } + zFile.add("assets/" + f.getName(), new FileInputStream(f)); } - zFile.add("assets/" + f.getName(), new FileInputStream(f)); } + + // save lspatch config to asset.. + try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) { + zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is); + } catch (IOException e) { + throw new PatchError("Error when saving signature: " + e); + } + + zFile.update(); + zFile.close(); + + System.out.println("Signing apk..."); + signApkUsingAndroidApksigner(workingDir, tmpApk, outputFile); + + System.out.println("Done. Output APK: " + outputFile.getAbsolutePath()); + } finally { + FileUtils.deleteDirectory(workingDir); } - - // save lspatch config to asset.. - File sl = new File(unzipApkFilePath, "tmp"); - FileUtils.write(sl, "42", Charset.defaultCharset()); - zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, new FileInputStream(sl)); - - zFile.update(); - zFile.close(); - - new BuildAndSignApkTask(true, unzipApkFilePath, finalApk.getAbsolutePath(), outputPath).run(); - System.out.println("Output APK: " + outputPath); } - private void modifyManifestFile(String filePath, String dstFilePath) { + private byte[] modifyManifestFile(InputStream is) { ModificationProperty property = new ModificationProperty(); property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag)); property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true)); property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, proxyName)); - FileProcesser.processManifestFile(filePath, dstFilePath, property); + var os = new ByteArrayOutputStream(); + (new ManifestEditor(is, os, property)).processManifest(); + return os.toByteArray(); } private int findDexFileCount(ZFile zFile) { - for (int i = 2; i < 30; i++) { + for (int i = 2; ; i++) { if (zFile.get("classes" + i + ".dex") == null) return i - 1; } - throw new IllegalStateException("wtf"); } - // Use the current timestamp as the name of the build file - @SuppressWarnings("SimpleDateFormat") - private String currentTimeStr() { - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - return df.format(new Date()); + private void signApkUsingAndroidApksigner(File workingDir, File apkPath, File outputPath) throws PatchError, IOException { + ArrayList commandList = new ArrayList<>(); + + var keyStoreFile = new File(workingDir, "keystore"); + + try (InputStream is = getClass().getClassLoader().getResourceAsStream("assets/keystore"); + FileOutputStream os = new FileOutputStream(keyStoreFile)) { + if (is == null) + throw new PatchError("Fail to save keystore file"); + IOUtils.copy(is, os); + } + + commandList.add("sign"); + commandList.add("--ks"); + commandList.add(keyStoreFile.getAbsolutePath()); + commandList.add("--ks-key-alias"); + commandList.add("key0"); + commandList.add("--ks-pass"); + commandList.add("pass:" + 123456); + commandList.add("--key-pass"); + commandList.add("pass:" + 123456); + commandList.add("--out"); + commandList.add(outputPath.getAbsolutePath()); + commandList.add("--v1-signing-enabled"); + commandList.add(Boolean.toString(v1)); + commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6 + commandList.add(Boolean.toString(v2)); + commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6 + commandList.add(Boolean.toString(v3)); + commandList.add(apkPath.getAbsolutePath()); + + try { + ApkSignerTool.main(commandList.toArray(new String[0])); + } catch (Exception e) { + throw new PatchError("Failed to sign apk: " + e.getMessage()); + } } } diff --git a/patch/src/main/java/org/lsposed/patch/task/BuildAndSignApkTask.java b/patch/src/main/java/org/lsposed/patch/task/BuildAndSignApkTask.java deleted file mode 100644 index 32290ac..0000000 --- a/patch/src/main/java/org/lsposed/patch/task/BuildAndSignApkTask.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.lsposed.patch.task; - -import com.android.apksigner.ApkSignerTool; - -import com.android.tools.build.apkzlib.zip.ZFile; - -import org.apache.commons.io.IOUtils; -import org.lsposed.patch.LSPatch; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.ArrayList; - -/** - * Created by Wind - */ -public class BuildAndSignApkTask implements Runnable { - - private final boolean keepUnsignedApkFile; - private final String signedApkPath; - private final String unzipApkFilePath; - private final String unsignedApkPath; - - public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String unsignedApkPath, String signedApkPath) { - this.keepUnsignedApkFile = keepUnsignedApkFile; - this.unsignedApkPath = unsignedApkPath; - this.unzipApkFilePath = unzipApkFilePath; - this.signedApkPath = signedApkPath; - } - - @Override - public void run() { - - try { - File unzipApkPathFile = new File(unzipApkFilePath); - File keyStoreFile = new File(unzipApkPathFile, "keystore"); - String keyStoreAssetPath; - keyStoreAssetPath = "assets/keystore"; - - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(keyStoreAssetPath); - FileOutputStream out = new FileOutputStream(keyStoreFile)) { - IOUtils.copy(inputStream, out); - } - - boolean signResult = signApk(unsignedApkPath, keyStoreFile.getAbsolutePath(), signedApkPath); - - File unsignedApkFile = new File(unsignedApkPath); - File signedApkFile = new File(signedApkPath); - - // delete unsigned apk file - if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists() && signResult) { - if (!unsignedApkFile.delete()) { - throw new IllegalStateException("wtf"); - } - } - } - catch (Exception err) { - throw new IllegalStateException("wtf", err); - } - } - - private boolean signApk(String apkPath, String keyStorePath, String signedApkPath) { - return signApkUsingAndroidApksigner(apkPath, keyStorePath, signedApkPath, "123456"); - } - - private boolean signApkUsingAndroidApksigner(String apkPath, String keyStorePath, String signedApkPath, String keyStorePassword) { - ArrayList commandList = new ArrayList<>(); - - commandList.add("sign"); - commandList.add("--ks"); - commandList.add(keyStorePath); - commandList.add("--ks-key-alias"); - commandList.add("key0"); - commandList.add("--ks-pass"); - commandList.add("pass:" + keyStorePassword); - commandList.add("--key-pass"); - commandList.add("pass:" + keyStorePassword); - commandList.add("--out"); - commandList.add(signedApkPath); - commandList.add("--v1-signing-enabled"); - commandList.add("true"); - commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6 - commandList.add("false"); - commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6 - commandList.add("false"); - commandList.add(apkPath); - - try { - ApkSignerTool.main(commandList.toArray(new String[0])); - } - catch (Exception e) { - e.printStackTrace(); - return false; - } - return true; - } -} diff --git a/patch/src/main/java/org/lsposed/patch/util/ApkSignatureHelper.java b/patch/src/main/java/org/lsposed/patch/util/ApkSignatureHelper.java index 369302e..ec2f476 100644 --- a/patch/src/main/java/org/lsposed/patch/util/ApkSignatureHelper.java +++ b/patch/src/main/java/org/lsposed/patch/util/ApkSignatureHelper.java @@ -72,7 +72,6 @@ public class ApkSignatureHelper { } } jarFile.close(); - System.out.println("getApkSignInfo result: " + certs[0]); return new String(toChars(certs[0].getEncoded())); } catch (Exception e) { e.printStackTrace(); diff --git a/patch/src/main/java/org/lsposed/patch/util/ManifestParser.java b/patch/src/main/java/org/lsposed/patch/util/ManifestParser.java index 77d40e6..ae5b191 100644 --- a/patch/src/main/java/org/lsposed/patch/util/ManifestParser.java +++ b/patch/src/main/java/org/lsposed/patch/util/ManifestParser.java @@ -3,6 +3,7 @@ package org.lsposed.patch.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import wind.android.content.res.AXmlResourceParser; import wind.v1.XmlPullParser; @@ -13,23 +14,12 @@ import wind.v1.XmlPullParserException; */ public class ManifestParser { - /** - * Get the package name and the main application name from the manifest file - * */ - public static Pair parseManifestFile(String filePath) { + public static Pair parseManifestFile(InputStream is) throws IOException { AXmlResourceParser parser = new AXmlResourceParser(); - File file = new File(filePath); String packageName = null; String applicationName = null; - if (!file.exists()) { - System.out.println(" manifest file not exist!!! filePath -> " + filePath); - return null; - } - FileInputStream inputStream = null; try { - inputStream = new FileInputStream(file); - - parser.open(inputStream); + parser.open(is); while (true) { int type = parser.next(); @@ -64,20 +54,21 @@ public class ManifestParser { } } } catch (XmlPullParserException | IOException e) { - e.printStackTrace(); - System.out.println("parseManifestFile failed, reason --> " + e.getMessage()); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } + return null; } return new Pair(packageName, applicationName); } + /** + * Get the package name and the main application name from the manifest file + */ + public static Pair parseManifestFile(String filePath) throws IOException { + File file = new File(filePath); + try (var is = new FileInputStream(file)) { + return parseManifestFile(is); + } + } + public static class Pair { public String packageName; public String applicationName;