diff --git a/.gitignore b/.gitignore index 716cab9..a1c73c7 100644 --- a/.gitignore +++ b/.gitignore @@ -121,5 +121,5 @@ node_modules/ revanced-cache/ options.toml -# Generated by an Android project (such as ReVanced Integrations) +# Generated by Android projects local.properties \ No newline at end of file diff --git a/docs/1_usage.md b/docs/1_usage.md index dfb7679..31ee72a 100644 --- a/docs/1_usage.md +++ b/docs/1_usage.md @@ -1,129 +1,100 @@ # 🛠ī¸ Using ReVanced CLI Learn how to use ReVanced CLI. +The following examples will show you how to perform basic operations. +You can list patches, patch an app, uninstall, and install an app. -## 🔨 Usage +## 🚀 Show all commands -ReVanced CLI is divided into the following fundamental commands: +```bash +java -jar revanced-cli.jar -h +``` -- ### 🚀 Show all available options for ReVanced CLI +## 📃 List patches - ```bash - java -jar revanced-cli.jar -h - ``` +```bash +java -jar revanced-cli.jar list-patches --with-descriptions --with-packages --with-versions --with-options --with-universal-patches revanced-patches.rvp +``` -- ### 📃 List patches +## 💉 Patch an app with the default list of patches - ```bash - java -jar revanced-cli.jar list-patches \ - --with-packages \ - --with-versions \ - --with-options \ - revanced-patches.jar [ ...] - ``` +```bash +java -jar revanced-cli.jar patch -b revanced-patches.rvp input.apk +``` -- ### ⚙ī¸ Generate options +You can also use multiple patch bundles: - This will generate an `options.json` file for the patches from a list of supplied patch bundles. - The file can be supplied to ReVanced CLI later on. +```bash +java -jar revanced-cli.jar patch -b revanced-patches.rvp -b another-patches.rvp input.apk +``` - ```bash - java -jar revanced-cli.jar options \ - --path options.json \ - --overwrite \ - revanced-patches.jar [ ...] - ``` +To manually include or exclude patches, use the options `-i` and `-e`. +Keep in mind the name of the patch must be an exact match. +You can also use the options `--ii` and `--ie` to include or exclude patches by their index +if two patches have the same name. +To know the indices of patches, use the option `--with-indices` when listing patches: - > **ℹī¸ Note** - > A default `options.json` file will be automatically created if it does not exist - > without any need for intervention when using the `patch` command. +```bash +java -jar revanced-cli.jar list-patches --with-indices revanced-patches.rvp +``` -- ### 💉 Patch an app +Then you can use the indices to include or exclude patches: - You can patch apps by supplying patch bundles and the app to patch. - After patching, ReVanced CLI can install the patched app on your device using two methods: +```bash +java -jar revanced-cli.jar patch -b revanced-patches.rvp --ii 123 --ie 456 input.apk +``` - > **💡 Tip** - > For ReVanced CLI to be able to install the patched app on your device, make sure ADB is working: - > - > ```bash - > adb shell exit - > ``` - > - > If you want to mount the patched app on top of the un-patched app, make sure you have root permissions: - > - > ```bash - > adb shell su -c exit - > ``` +> [!TIP] +> You can use the option `-d` to automatically install the patched app after patching. +> Make sure ADB is working: +> +> ```bash +> adb shell exit +> ``` - > **⚠ī¸ Warning** - > Some patches may require integrations - > such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations). - > Supply them with the option `--merge`. ReVanced Patcher will automatically determine if they are necessary. - - #### 👾 Patch an app and install it on your device regularly +> [!TIP] +> You can use the option `--mount` to mount the patched app on top of the un-patched app. +> Make sure you have root permissions and the same app you are patching and mounting over is installed on your device: +> +> ```bash +> adb shell su -c exit +> adb install input.apk +> ``` - ```bash - java -jar revanced-cli.jar patch \ - --patch-bundle revanced-patches.jar \ - -d \ - input.apk - ``` +## đŸ“Ļ Install an app manually - - #### 👾 Patch an app and mount it on top of the un-patched app with root permissions +```bash +java -jar revanced-cli.jar utility install -a input.apk +``` - > **❗ Caution** - > Ensure that the same app you are patching and mounting over is installed on your device: - > - > ```bash - > adb install app.apk - > ``` +> [!TIP] +> You can use the option `--mount` to mount the patched app on top of the un-patched app. +> Make sure you have root permissions and the same app you are patching and mounting over is installed on your device: +> +> ```bash +> adb shell su -c exit +> adb install input.apk +> ``` - ```bash - java -jar revanced-cli.jar patch \ - --patch-bundle revanced-patches.jar \ - --include "Some patch" \ - --ii 123 \ - --exclude "Some other patch" \ - -d \ - --mount \ - app.apk - ``` +## 🗑ī¸ Uninstall an app manually - > **💡 Tip** - > You can use the option `--ii` to include or `--ie` to exclude - > patches by their index in relation to supplied patch bundles, - > similarly to the option `--include` and `--exclude`. - > - > This is useful in case two patches have the same name, and you must include or exclude one. - > The patch index is calculated by the position of the patch in the list of patches - > from patch bundles supplied using the option `--patch-bundle`. - > - > You can list all patches with their indices using the command `list-patches`. - > - > Keep in mind that the indices can change based on the order of the patch bundles supplied, - > as well if the patch bundles are updated because patches can be added or removed. +Here `` is the package name of the app you want to uninstall: -- ### 🗑ī¸ Uninstall an app +```bash +java -jar revanced-cli.jar utility uninstall --package-name +``` - ```bash - java -jar revanced-cli.jar utility uninstall \ - --package-name \ - [] - ``` +If the app is mounted, you need to unmount it by using the option `--unmount`: - > **💡 Tip** - > You can unmount an APK file - > by adding the option `--unmount`. +```bash +java -jar revanced-cli.jar utility uninstall --package-name --unmount +``` -- ### ī¸ đŸ“Ļ Install an app - - ```bash - java -jar revanced-cli.jar utility install \ - -a input.apk \ - [] - ``` - - > **💡 Tip** - > You can mount an APK file - > by supplying the app's package name to mount the supplied APK file over the option `--mount`. +> [!TIP] +> By default, the app is installed or uninstalled to the first connected device. +> You can append one or more devices by their serial to install or uninstall an app on your selected choice of devices: +> +> ```bash +> java -jar revanced-cli.jar utility uninstall --package-name [ ...] +> ``` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d61b0dd..726f9ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,17 @@ [versions] shadow = "8.1.1" kotlin = "2.0.0" -kotlinx-coroutines-core = "1.8.0" -picocli = "4.7.5" -revanced-patcher = "19.3.1" -revanced-library = "2.3.0" +kotlinx = "1.8.1" +picocli = "4.7.6" +revanced-patcher = "20.0.0" +revanced-library = "3.0.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } -revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } +revanced-library = { module = "app.revanced:revanced-library-jvm", version.ref = "revanced-library" } [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 381baa9..68e8816 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt b/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt index ab21eee..31f5cfa 100644 --- a/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt +++ b/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt @@ -3,7 +3,7 @@ package app.revanced.cli.command import app.revanced.library.PackageName import app.revanced.library.PatchUtils import app.revanced.library.VersionMap -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine import java.io.File import java.util.logging.Logger @@ -12,7 +12,7 @@ import java.util.logging.Logger name = "list-versions", description = [ "List the most common compatible versions of apps that are compatible " + - "with the patches in the supplied patch bundles.", + "with the patches in the supplied patch bundles.", ], ) internal class ListCompatibleVersions : Runnable { @@ -22,7 +22,7 @@ internal class ListCompatibleVersions : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @CommandLine.Option( names = ["-f", "--filter-package-names"], @@ -38,8 +38,6 @@ internal class ListCompatibleVersions : Runnable { private var countUnusedPatches: Boolean = false override fun run() { - val patches = PatchBundleLoader.Jar(*patchBundles) - fun VersionMap.buildVersionsString(): String { if (isEmpty()) return "Any" @@ -58,6 +56,8 @@ internal class ListCompatibleVersions : Runnable { appendLine(versions.buildVersionsString().prependIndent("\t")) } + val patches = loadPatchesFromJar(patchBundles) + PatchUtils.getMostCommonCompatibleVersions( patches, packageNames, diff --git a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt index 1886613..d3c66a1 100644 --- a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt @@ -1,12 +1,13 @@ package app.revanced.cli.command -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.Package import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.options.PatchOption +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine.* import picocli.CommandLine.Help.Visibility.ALWAYS import java.io.File import java.util.logging.Logger +import app.revanced.patcher.patch.Option as PatchOption @Command( name = "list-patches", @@ -19,7 +20,7 @@ internal object ListPatchesCommand : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @Option( names = ["-d", "--with-descriptions"], @@ -70,16 +71,19 @@ internal object ListPatchesCommand : Runnable { private var packageName: String? = null override fun run() { - fun Patch.CompatiblePackage.buildString() = - buildString { + fun Package.buildString(): String { + val (name, versions) = this + + return buildString { if (withVersions && versions != null) { appendLine("Package name: $name") appendLine("Compatible versions:") - append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t")) + append(versions.joinToString("\n") { version -> version }.prependIndent("\t")) } else { append("Package name: $name") } } + } fun PatchOption<*>.buildString() = buildString { @@ -126,10 +130,10 @@ internal object ListPatchesCommand : Runnable { } fun Patch<*>.filterCompatiblePackages(name: String) = - compatiblePackages?.any { it.name == name } + compatiblePackages?.any { (compatiblePackageName, _) -> compatiblePackageName == name } ?: withUniversalPatches - val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList() + val patches = loadPatchesFromJar(patchBundles).withIndex().toList() val filtered = packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches diff --git a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt index 72d4240..dbc1ada 100644 --- a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt @@ -2,7 +2,7 @@ package app.revanced.cli.command import app.revanced.library.Options import app.revanced.library.Options.setOptions -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine import picocli.CommandLine.Help.Visibility.ALWAYS import java.io.File @@ -19,7 +19,7 @@ internal object OptionsCommand : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @CommandLine.Option( names = ["-p", "--path"], @@ -44,7 +44,7 @@ internal object OptionsCommand : Runnable { override fun run() = try { - PatchBundleLoader.Jar(*patchBundles).let { patches -> + loadPatchesFromJar(patchBundles).let { patches -> val exists = filePath.exists() if (!exists || overwrite) { if (exists && update) patches.setOptions(filePath) diff --git a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt index 05238c2..69f24f1 100644 --- a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt @@ -4,11 +4,11 @@ import app.revanced.library.ApkUtils import app.revanced.library.ApkUtils.applyTo import app.revanced.library.Options import app.revanced.library.Options.setOptions -import app.revanced.library.adb.AdbManager -import app.revanced.patcher.PatchBundleLoader -import app.revanced.patcher.PatchSet +import app.revanced.library.installation.installer.* import app.revanced.patcher.Patcher import app.revanced.patcher.PatcherConfig +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.loadPatchesFromJar import kotlinx.coroutines.runBlocking import picocli.CommandLine import picocli.CommandLine.Help.Visibility.ALWAYS @@ -31,8 +31,6 @@ internal object PatchCommand : Runnable { private lateinit var apk: File - private var integrations = setOf() - private var patchBundles = emptySet() @CommandLine.Option( @@ -121,16 +119,6 @@ internal object PatchCommand : Runnable { ) private var keyStorePassword: String? = null // Empty password by default - @CommandLine.Option( - names = ["--alias"], - description = ["The alias of the keystore entry to sign the patched APK file with."], - showDefaultValue = ALWAYS, - ) - private fun setKeyStoreEntryAlias(alias: String = "ReVanced Key") { - logger.warning("The --alias option is deprecated. Use --keystore-entry-alias instead.") - keyStoreEntryAlias = alias - } - @CommandLine.Option( names = ["--keystore-entry-alias"], description = ["The alias of the keystore entry to sign the patched APK file with."], @@ -193,11 +181,8 @@ internal object PatchCommand : Runnable { description = ["One or more DEX files or containers to merge into the APK."], ) @Suppress("unused") - private fun setIntegrations(integrations: Array) { - integrations.firstOrNull { !it.exists() }?.let { - throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.path} does not exist.") - } - this.integrations += integrations + private fun setIntegrations(integrations: Set) { + logger.warning("The --merge option is not used anymore.") } @CommandLine.Option( @@ -256,7 +241,7 @@ internal object PatchCommand : Runnable { logger.info("Loading patches") - val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray()) + val patches = loadPatchesFromJar(patchBundles) // Warn if a patch can not be found in the supplied patch bundles. if (warn) { @@ -271,6 +256,7 @@ internal object PatchCommand : Runnable { } // endregion + val patcherTemporaryFilesPath = temporaryFilesPath.resolve("patcher") val (packageName, patcherResult) = Patcher( PatcherConfig( @@ -292,28 +278,27 @@ internal object PatchCommand : Runnable { } } - // region Patch + patcher += filteredPatches - patcher.context.packageMetadata.packageName to patcher.apply { - acceptIntegrations(integrations) - acceptPatches(filteredPatches) + // Execute patches. + runBlocking { + patcher().collect { patchResult -> + val exception = patchResult.exception + ?: return@collect logger.info("\"${patchResult.patch}\" succeeded") - // Execute patches. - runBlocking { - apply(false).collect { patchResult -> - patchResult.exception?.let { - StringWriter().use { writer -> - it.printStackTrace(PrintWriter(writer)) - logger.severe("${patchResult.patch.name} failed:\n$writer") - } - } ?: logger.info("${patchResult.patch.name} succeeded") + StringWriter().use { writer -> + exception.printStackTrace(PrintWriter(writer)) + + logger.severe("\"${patchResult.patch}\" failed:\n$writer") } } - }.get() - // endregion + } + + patcher.context.packageMetadata.packageName to patcher.get() } // region Save + apk.copyTo(temporaryFilesPath.resolve(apk.name), overwrite = true).apply { patcherResult.applyTo(this) }.let { patchedApkFile -> @@ -340,9 +325,23 @@ internal object PatchCommand : Runnable { // region Install - deviceSerial?.let { serial -> - AdbManager.getAdbManager(deviceSerial = serial.ifEmpty { null }, mount) - }?.install(AdbManager.Apk(outputFilePath, packageName)) + deviceSerial?.let { it -> + val deviceSerial = it.ifEmpty { null } + + runBlocking { + val result = if (mount) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.install(Installer.Apk(outputFilePath, packageName)) + + when (result) { + RootInstallerResult.FAILURE -> logger.severe("Failed to mount the patched APK file") + is AdbInstallerResult.Failure -> logger.severe(result.exception.toString()) + else -> logger.info("Installed the patched APK file") + } + } + } // endregion @@ -358,7 +357,7 @@ internal object PatchCommand : Runnable { * @param patches The patches to filter. * @return The filtered patches. */ - private fun Patcher.filterPatchSelection(patches: PatchSet): PatchSet = + private fun Patcher.filterPatchSelection(patches: Set>): Set> = buildSet { val packageName = context.packageMetadata.packageName val packageVersion = context.packageMetadata.packageVersion @@ -367,33 +366,36 @@ internal object PatchCommand : Runnable { val patchName = patch.name!! val explicitlyExcluded = excludedPatches.contains(patchName) || excludedPatchesByIndex.contains(i) - if (explicitlyExcluded) return@patch logger.info("Excluding $patchName") + if (explicitlyExcluded) return@patch logger.info("\"$patchName\" excluded manually") // Make sure the patch is compatible with the supplied APK files package name and version. patch.compatiblePackages?.let { packages -> - packages.singleOrNull { it.name == packageName }?.let { `package` -> - val matchesVersion = - force || `package`.versions?.let { - it.any { version -> version == packageVersion } - } ?: true + packages.singleOrNull { (name, _) -> name == packageName }?.let { (_, versions) -> + if (versions?.isEmpty() == true) { + return@patch logger.warning("\"$patchName\" incompatible with \"$packageName\"") + } + + val matchesVersion = force || + versions?.let { it.any { version -> version == packageVersion } } + ?: true if (!matchesVersion) { return@patch logger.warning( - "$patchName is incompatible with version $packageVersion. " + - "This patch is only compatible with version " + - packages.joinToString(";") { pkg -> - pkg.versions!!.joinToString(", ") + "\"$patchName\" incompatible with $packageName $packageVersion " + + "but compatible with " + + packages.joinToString("; ") { (packageName, versions) -> + packageName + " " + versions!!.joinToString(", ") }, ) } } ?: return@patch logger.fine( - "$patchName is incompatible with $packageName. " + - "This patch is only compatible with " + - packages.joinToString(", ") { `package` -> `package`.name }, + "\"$patchName\" incompatible with $packageName. " + + "It is only compatible with " + + packages.joinToString(", ") { (name, _) -> name }, ) return@let - } ?: logger.fine("$patchName has no constraint on packages.") + } ?: logger.fine("\"$patchName\" has no package constraints") // If the patch is implicitly used, it will be only included if [exclusive] is false. val implicitlyIncluded = !exclusive && patch.use @@ -401,11 +403,11 @@ internal object PatchCommand : Runnable { val explicitlyIncluded = includedPatches.contains(patchName) || includedPatchesByIndex.contains(i) val included = implicitlyIncluded || explicitlyIncluded - if (!included) return@patch logger.info("$patchName excluded") // Case 1. - - logger.fine("Adding $patchName") + if (!included) return@patch logger.info("\"$patchName\" excluded") // Case 1. add(patch) + + logger.fine("\"$patchName\" added") } } diff --git a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt index 508bf1c..573205e 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt @@ -1,6 +1,9 @@ package app.revanced.cli.command.utility -import app.revanced.library.adb.AdbManager +import app.revanced.library.installation.installer.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import picocli.CommandLine.* import java.io.File import java.util.logging.Logger @@ -32,13 +35,29 @@ internal object InstallCommand : Runnable { private var packageName: String? = null override fun run() { - fun install(deviceSerial: String? = null) = - try { - AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName)) - } catch (e: AdbManager.DeviceNotFoundException) { + suspend fun install(deviceSerial: String? = null) { + val result = try { + if (packageName != null) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.install(Installer.Apk(apk, packageName)) + } catch (e: Exception) { logger.severe(e.toString()) } - deviceSerials?.forEach(::install) ?: install() + when (result) { + RootInstallerResult.FAILURE -> + logger.severe("Failed to mount the APK file") + is AdbInstallerResult.Failure -> + logger.severe(result.exception.toString()) + else -> + logger.info("Installed the APK file") + } + } + + runBlocking { + deviceSerials?.map { async { install(it) } }?.awaitAll() ?: install() + } } } diff --git a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt index 49a36f2..e446e00 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt @@ -1,6 +1,9 @@ package app.revanced.cli.command.utility -import app.revanced.library.adb.AdbManager +import app.revanced.library.installation.installer.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import picocli.CommandLine.* import picocli.CommandLine.Help.Visibility.ALWAYS import java.util.logging.Logger @@ -33,13 +36,28 @@ internal object UninstallCommand : Runnable { private var unmount: Boolean = false override fun run() { - fun uninstall(deviceSerial: String? = null) = - try { - AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName) - } catch (e: AdbManager.DeviceNotFoundException) { + suspend fun uninstall(deviceSerial: String? = null) { + val result = try { + if (unmount) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.uninstall(packageName) + } catch (e: Exception) { logger.severe(e.toString()) } - deviceSerials?.forEach { uninstall(it) } ?: uninstall() + when (result) { + RootInstallerResult.FAILURE -> + logger.severe("Failed to unmount the patched APK file") + is AdbInstallerResult.Failure -> + logger.severe(result.exception.toString()) + else -> logger.info("Uninstalled the patched APK file") + } + } + + runBlocking { + deviceSerials?.map { async { uninstall(it) } }?.awaitAll() ?: uninstall() + } } }