From 43d655aea5d86288ae9916630e0f30de219d5cfb Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 7 Apr 2024 18:30:43 +0200 Subject: [PATCH] feat: Add local Android installer (#25) --- .github/workflows/build_pull_request.yml | 3 + .github/workflows/release.yml | 3 + .gitignore | 123 +------ api/android/revanced-library.api | 337 ++++++++++++++++++ api/jvm/revanced-library.api | 283 +++++++++++++++ build.gradle.kts | 87 +++-- gradle.properties | 16 +- gradle/libs.versions.toml | 19 +- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 59203 bytes gradlew | 281 ++++++--------- gradlew.bat | 15 +- settings.gradle.kts | 10 +- .../ILocalShellCommandRunnerRootService.aidl | 5 + .../command/LocalShellCommandRunner.kt | 88 +++++ .../LocalShellCommandRunnerRootService.kt | 15 + .../installation/installer/LocalInstaller.kt | 104 ++++++ .../installer/LocalInstallerResult.kt | 15 + .../installer/LocalInstallerService.kt | 57 +++ .../installer/LocalRootInstaller.kt | 34 ++ .../kotlin/app/revanced/library/ApkSigner.kt | 0 .../kotlin/app/revanced/library/ApkUtils.kt | 0 .../kotlin/app/revanced/library/Commands.kt | 43 +++ .../kotlin/app/revanced/library/Options.kt | 0 .../kotlin/app/revanced/library/PatchUtils.kt | 0 .../kotlin/app/revanced/library/Utils.kt | 18 + .../app/revanced/library/adb/AdbManager.kt | 144 ++++++++ .../command/AdbShellCommandRunner.kt | 59 +++ .../library/installation/command/RunResult.kt | 26 ++ .../command/ShellCommandRunner.kt | 59 +++ .../installation/installer/AdbInstaller.kt | 51 +++ .../installer/AdbInstallerResult.kt | 23 ++ .../installer/AdbRootInstaller.kt | 23 ++ .../installation/installer/Constants.kt | 75 ++++ .../installation/installer/Installation.kt | 11 + .../installation/installer/Installer.kt | 53 +++ .../installer/RootInstallation.kt | 15 + .../installation/installer/RootInstaller.kt | 135 +++++++ .../installer/RootInstallerResult.kt | 20 ++ .../library/installation/installer/Utils.kt | 40 +++ .../app/revanced/library/logging/Logger.kt | 8 +- .../app/revanced/library/PatchOptionsTest.kt | 33 +- .../app/revanced/library/PatchUtilsTest.kt | 4 +- .../app/revanced/library/adb/AdbManager.kt | 183 ---------- .../app/revanced/library/adb/Commands.kt | 35 -- .../app/revanced/library/adb/Constants.kt | 54 --- 45 files changed, 1976 insertions(+), 631 deletions(-) create mode 100644 api/android/revanced-library.api create mode 100644 api/jvm/revanced-library.api create mode 100644 src/androidMain/aidl/app/revanced/library/installation/command/ILocalShellCommandRunnerRootService.aidl create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunner.kt create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunnerRootService.kt create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerResult.kt create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerService.kt create mode 100644 src/androidMain/kotlin/app/revanced/library/installation/installer/LocalRootInstaller.kt rename src/{main => commonMain}/kotlin/app/revanced/library/ApkSigner.kt (100%) rename src/{main => commonMain}/kotlin/app/revanced/library/ApkUtils.kt (100%) create mode 100644 src/commonMain/kotlin/app/revanced/library/Commands.kt rename src/{main => commonMain}/kotlin/app/revanced/library/Options.kt (100%) rename src/{main => commonMain}/kotlin/app/revanced/library/PatchUtils.kt (100%) create mode 100644 src/commonMain/kotlin/app/revanced/library/Utils.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/adb/AdbManager.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/command/AdbShellCommandRunner.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/command/RunResult.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/command/ShellCommandRunner.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstallerResult.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/AdbRootInstaller.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/Installation.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallation.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallerResult.kt create mode 100644 src/commonMain/kotlin/app/revanced/library/installation/installer/Utils.kt rename src/{main => commonMain}/kotlin/app/revanced/library/logging/Logger.kt (91%) rename src/{test => commonTest}/kotlin/app/revanced/library/PatchOptionsTest.kt (61%) rename src/{test => commonTest}/kotlin/app/revanced/library/PatchUtilsTest.kt (99%) delete mode 100644 src/main/kotlin/app/revanced/library/adb/AdbManager.kt delete mode 100644 src/main/kotlin/app/revanced/library/adb/Commands.kt delete mode 100644 src/main/kotlin/app/revanced/library/adb/Constants.kt diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 250871b..cab9162 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -19,6 +19,9 @@ jobs: - name: Cache Gradle uses: burrunan/gradle-cache-action@v1 + - name: Setup Java + run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2cb2b20..edb9d1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,9 @@ jobs: - name: Cache Gradle uses: burrunan/gradle-cache-action@v1 + - name: Setup Java + run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 8e77bd2..83f36ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,118 +1,11 @@ -### Java template -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -.idea/artifacts -.idea/compiler.xml -.idea/jarRepositories.xml -.idea/modules.xml -.idea/*.iml -.idea/modules *.iml -*.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ -.idea - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Gradle template .gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties - -# Dependency directories +.idea +.DS_Store +build +captures +.externalNativeBuild +.cxx +local.properties +xcuserdata node_modules/ \ No newline at end of file diff --git a/api/android/revanced-library.api b/api/android/revanced-library.api new file mode 100644 index 0000000..9f89984 --- /dev/null +++ b/api/android/revanced-library.api @@ -0,0 +1,337 @@ +public final class app/revanced/library/ApkSigner { + public static final field INSTANCE Lapp/revanced/library/ApkSigner; + public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V + public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore; + public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore; + public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; +} + +public final class app/revanced/library/ApkSigner$KeyStoreEntry { + public fun (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; +} + +public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair { + public fun (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V + public final fun getCertificate ()Ljava/security/cert/X509Certificate; + public final fun getPrivateKey ()Ljava/security/PrivateKey; +} + +public final class app/revanced/library/ApkSigner$Signer { + public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V + public final fun signApk (Ljava/io/File;)V + public final fun signApk (Ljava/io/File;Ljava/io/File;)V +} + +public final class app/revanced/library/ApkUtils { + public static final field INSTANCE Lapp/revanced/library/ApkUtils; + public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V + public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V + public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V + public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V + public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V +} + +public final class app/revanced/library/ApkUtils$KeyStoreDetails { + public fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getKeyStore ()Ljava/io/File; + public final fun getKeyStorePassword ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; +} + +public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails { + public fun ()V + public fun (Ljava/lang/String;Ljava/util/Date;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCommonName ()Ljava/lang/String; + public final fun getValidUntil ()Ljava/util/Date; +} + +public final class app/revanced/library/ApkUtils$SigningOptions { + public fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getKeyStore ()Ljava/io/File; + public final fun getKeyStorePassword ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getSigner ()Ljava/lang/String; +} + +public final class app/revanced/library/Options { + public static final field INSTANCE Lapp/revanced/library/Options; + public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch; + public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String; + public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String; + public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V + public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V +} + +public final class app/revanced/library/Options$Patch { + public final fun getOptions ()Ljava/util/List; + public final fun getPatchName ()Ljava/lang/String; +} + +public final class app/revanced/library/Options$Patch$Option { + public final fun getKey ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/Object; +} + +public final class app/revanced/library/PatchUtils { + public static final field INSTANCE Lapp/revanced/library/PatchUtils; + public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; + public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map; +} + +public final class app/revanced/library/PatchUtils$Json { + public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json; + public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set; + public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V + public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch { + public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion; + public final fun getCompatiblePackages ()Ljava/util/Set; + public final fun getDependencies ()Ljava/util/Set; + public final fun getDescription ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getOptions ()Ljava/util/Map; + public final fun getRequiresIntegrations ()Z + public final fun getUse ()Z + public final fun setRequiresIntegrations (Z)V +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion { + public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch; +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption { + public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion; + public final fun getDefault ()Ljava/lang/Object; + public final fun getDescription ()Ljava/lang/String; + public final fun getKey ()Ljava/lang/String; + public final fun getRequired ()Z + public final fun getTitle ()Ljava/lang/String; + public final fun getValueType ()Ljava/lang/String; + public final fun getValues ()Ljava/util/Map; +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion { + public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption; +} + +public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch { +} + +public final class app/revanced/library/Utils { + public static final field INSTANCE Lapp/revanced/library/Utils; + public final fun isAndroidEnvironment ()Z +} + +public abstract class app/revanced/library/adb/AdbManager { + public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/adb/AdbManager$Apk { + public fun (Ljava/io/File;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFile ()Ljava/io/File; + public final fun getPackageName ()Ljava/lang/String; +} + +public final class app/revanced/library/adb/AdbManager$Companion { + public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager; + public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager; +} + +public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception { + public fun ()V +} + +public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception { +} + +public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception { +} + +public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager { + public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils; + public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils { +} + +public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager { + public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner { +} + +public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface { + public static final field DESCRIPTOR Ljava/lang/String; + public abstract fun getFileSystemService ()Landroid/os/IBinder; +} + +public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService { + public fun ()V + public fun asBinder ()Landroid/os/IBinder; + public fun getFileSystemService ()Landroid/os/IBinder; +} + +public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService { + public fun ()V + public fun asBinder ()Landroid/os/IBinder; + public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService; + public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z +} + +public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable { + public fun close ()V + public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V + public fun onServiceDisconnected (Landroid/content/ComponentName;)V +} + +public abstract interface class app/revanced/library/installation/command/RunResult { + public abstract fun getError ()Ljava/lang/String; + public abstract fun getExitCode ()I + public abstract fun getOutput ()Ljava/lang/String; + public abstract fun waitFor ()V +} + +public final class app/revanced/library/installation/command/RunResult$DefaultImpls { + public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V +} + +public abstract class app/revanced/library/installation/command/ShellCommandRunner { + protected final fun getLogger ()Ljava/util/logging/Logger; + protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult; +} + +public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult { +} + +public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult { + public final fun getException ()Ljava/lang/Exception; +} + +public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult { + public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success; +} + +public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public class app/revanced/library/installation/installer/Installation { + public final fun getApkFilePath ()Ljava/lang/String; +} + +public abstract class app/revanced/library/installation/installer/Installer { + public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getLogger ()Ljava/util/logging/Logger; + public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class app/revanced/library/installation/installer/Installer$Apk { + public fun (Ljava/io/File;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFile ()Ljava/io/File; + public final fun getPackageName ()Ljava/lang/String; +} + +public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable { + public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion; + public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V + public fun close ()V + public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class app/revanced/library/installation/installer/LocalInstaller$Companion { +} + +public final class app/revanced/library/installation/installer/LocalInstallerResult { + public final fun getExtra ()Ljava/lang/String; + public final fun getPackageName ()Ljava/lang/String; + public final fun getPmStatus ()I +} + +public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service { + public fun ()V + public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder; + public fun onStartCommand (Landroid/content/Intent;II)I +} + +public final class app/revanced/library/installation/installer/LocalRootInstaller : app/revanced/library/installation/installer/RootInstaller, java/io/Closeable { + public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V +} + +public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation { + public final fun getInstalledApkFilePath ()Ljava/lang/String; + public final fun getMounted ()Z +} + +public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer { + public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner; + public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult; + protected final fun move (Ljava/io/File;Ljava/lang/String;)V + public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun write (Ljava/lang/String;Ljava/lang/String;)V +} + +public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum { + public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult; + public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult; + public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult; +} + +public final class app/revanced/library/logging/Logger { + public static final field INSTANCE Lapp/revanced/library/logging/Logger; + public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V + public final fun removeAllHandlers ()V + public final fun setDefault ()V + public final fun setFormat (Ljava/lang/String;)V + public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V +} + diff --git a/api/jvm/revanced-library.api b/api/jvm/revanced-library.api new file mode 100644 index 0000000..5a525d9 --- /dev/null +++ b/api/jvm/revanced-library.api @@ -0,0 +1,283 @@ +public final class app/revanced/library/ApkSigner { + public static final field INSTANCE Lapp/revanced/library/ApkSigner; + public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer; + public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V + public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore; + public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore; + public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; +} + +public final class app/revanced/library/ApkSigner$KeyStoreEntry { + public fun (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; +} + +public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair { + public fun (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V + public final fun getCertificate ()Ljava/security/cert/X509Certificate; + public final fun getPrivateKey ()Ljava/security/PrivateKey; +} + +public final class app/revanced/library/ApkSigner$Signer { + public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V + public final fun signApk (Ljava/io/File;)V + public final fun signApk (Ljava/io/File;Ljava/io/File;)V +} + +public final class app/revanced/library/ApkUtils { + public static final field INSTANCE Lapp/revanced/library/ApkUtils; + public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V + public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; + public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V + public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V + public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V + public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V +} + +public final class app/revanced/library/ApkUtils$KeyStoreDetails { + public fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getKeyStore ()Ljava/io/File; + public final fun getKeyStorePassword ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; +} + +public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails { + public fun ()V + public fun (Ljava/lang/String;Ljava/util/Date;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCommonName ()Ljava/lang/String; + public final fun getValidUntil ()Ljava/util/Date; +} + +public final class app/revanced/library/ApkUtils$SigningOptions { + public fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAlias ()Ljava/lang/String; + public final fun getKeyStore ()Ljava/io/File; + public final fun getKeyStorePassword ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getSigner ()Ljava/lang/String; +} + +public final class app/revanced/library/Options { + public static final field INSTANCE Lapp/revanced/library/Options; + public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch; + public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String; + public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String; + public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V + public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V +} + +public final class app/revanced/library/Options$Patch { + public final fun getOptions ()Ljava/util/List; + public final fun getPatchName ()Ljava/lang/String; +} + +public final class app/revanced/library/Options$Patch$Option { + public final fun getKey ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/Object; +} + +public final class app/revanced/library/PatchUtils { + public static final field INSTANCE Lapp/revanced/library/PatchUtils; + public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; + public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map; +} + +public final class app/revanced/library/PatchUtils$Json { + public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json; + public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set; + public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V + public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch { + public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion; + public final fun getCompatiblePackages ()Ljava/util/Set; + public final fun getDependencies ()Ljava/util/Set; + public final fun getDescription ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getOptions ()Ljava/util/Map; + public final fun getRequiresIntegrations ()Z + public final fun getUse ()Z + public final fun setRequiresIntegrations (Z)V +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion { + public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch; +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption { + public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion; + public final fun getDefault ()Ljava/lang/Object; + public final fun getDescription ()Ljava/lang/String; + public final fun getKey ()Ljava/lang/String; + public final fun getRequired ()Z + public final fun getTitle ()Ljava/lang/String; + public final fun getValueType ()Ljava/lang/String; + public final fun getValues ()Ljava/util/Map; +} + +public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion { + public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption; +} + +public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch { +} + +public final class app/revanced/library/Utils { + public static final field INSTANCE Lapp/revanced/library/Utils; + public final fun isAndroidEnvironment ()Z +} + +public abstract class app/revanced/library/adb/AdbManager { + public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/adb/AdbManager$Apk { + public fun (Ljava/io/File;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFile ()Ljava/io/File; + public final fun getPackageName ()Ljava/lang/String; +} + +public final class app/revanced/library/adb/AdbManager$Companion { + public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager; + public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager; +} + +public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception { + public fun ()V +} + +public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception { +} + +public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception { +} + +public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager { + public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils; + public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils { +} + +public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager { + public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer; + public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1; + public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + +public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner { +} + +public abstract interface class app/revanced/library/installation/command/RunResult { + public abstract fun getError ()Ljava/lang/String; + public abstract fun getExitCode ()I + public abstract fun getOutput ()Ljava/lang/String; + public abstract fun waitFor ()V +} + +public final class app/revanced/library/installation/command/RunResult$DefaultImpls { + public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V +} + +public abstract class app/revanced/library/installation/command/ShellCommandRunner { + protected final fun getLogger ()Ljava/util/logging/Logger; + protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult; +} + +public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult { +} + +public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult { + public final fun getException ()Ljava/lang/Exception; +} + +public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult { + public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success; +} + +public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public class app/revanced/library/installation/installer/Installation { + public final fun getApkFilePath ()Ljava/lang/String; +} + +public abstract class app/revanced/library/installation/installer/Installer { + public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getLogger ()Ljava/util/logging/Logger; + public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class app/revanced/library/installation/installer/Installer$Apk { + public fun (Ljava/io/File;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFile ()Ljava/io/File; + public final fun getPackageName ()Ljava/lang/String; +} + +public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation { + public final fun getInstalledApkFilePath ()Ljava/lang/String; + public final fun getMounted ()Z +} + +public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer { + public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner; + public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult; + protected final fun move (Ljava/io/File;Ljava/lang/String;)V + public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun write (Ljava/lang/String;Ljava/lang/String;)V +} + +public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum { + public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult; + public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult; + public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult; +} + +public final class app/revanced/library/logging/Logger { + public static final field INSTANCE Lapp/revanced/library/logging/Logger; + public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V + public final fun removeAllHandlers ()V + public final fun setDefault ()V + public final fun setFormat (Ljava/lang/String;)V + public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V +} + diff --git a/build.gradle.kts b/build.gradle.kts index 0de3754..643dd26 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,6 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) alias(libs.plugins.binary.compatibility.validator) `maven-publish` signing @@ -9,53 +8,87 @@ plugins { group = "app.revanced" +// Because access to the project is necessary to authenticate with GitHub, +// the following block must be placed in the root build.gradle.kts file +// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block. repositories { mavenCentral() mavenLocal() google() maven { - // A repository must be speficied for some reason. "registry" is a dummy. + // A repository must be specified for some reason. "registry" is a dummy. url = uri("https://maven.pkg.github.com/revanced/registry") credentials { username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") } } + maven { url = uri("https://jitpack.io") } } -dependencies { - implementation(libs.revanced.patcher) - implementation(libs.kotlin.reflect) - implementation(libs.jadb) // Fork with Shell v2 support. - implementation(libs.jackson.module.kotlin) - implementation(libs.apkzlib) - implementation(libs.apksig) - implementation(libs.bcpkix.jdk15on) - implementation(libs.guava) +kotlin { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + } + } - testImplementation(libs.revanced.patcher) - testImplementation(libs.kotlin.test) -} + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + } -tasks { - test { - useJUnitPlatform() - testLogging { - events("PASSED", "SKIPPED", "FAILED") + publishLibraryVariants("release") + } + + sourceSets { + androidMain.dependencies { + implementation(libs.libsu.nio) + implementation(libs.libsu.service) + implementation(libs.core.ktx) + } + + commonMain.dependencies { + implementation(libs.revanced.patcher) + implementation(libs.kotlin.reflect) + implementation(libs.jadb) // Fork with Shell v2 support. + implementation(libs.bcpkix.jdk15on) + implementation(libs.jackson.module.kotlin) + implementation(libs.apkzlib) + implementation(libs.apksig) + implementation(libs.guava) + } + + commonTest.dependencies { + implementation(libs.revanced.patcher) + implementation(libs.kotlin.test.junit) } } } -kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) +android { + namespace = "app.revanced.library" + compileSdk = 34 + defaultConfig { + minSdk = 26 + } + + buildFeatures { + aidl = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } } java { targetCompatibility = JavaVersion.VERSION_11 - - withSourcesJar() } publishing { @@ -72,8 +105,6 @@ publishing { publications { create("revanced-library-publication") { - from(components["java"]) - version = project.version.toString() pom { diff --git a/gradle.properties b/gradle.properties index abd574e..1e54a84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,14 @@ -org.gradle.parallel = true -org.gradle.caching = true -kotlin.code.style = official version = 2.3.0 + +#Gradle +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.parallel = true + +#Kotlin +kotlin.code.style=official + +#Android +android.useAndroidX=true +android.nonTransitiveRClass=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04a06fc..03a7a0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,22 +4,29 @@ jadb = "1.2.1" kotlin = "1.9.22" revanced-patcher = "19.3.1" binary-compatibility-validator = "0.14.0" -apkzlib = "8.3.0" +android = "8.3.0" bcpkix-jdk15on = "1.70" guava = "33.0.0-jre" -apksig = "8.3.0" +libsu = "5.2.2" +core-ktx = "1.12.0" [libraries] jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" } jadb = { module = "app.revanced:jadb", version.ref = "jadb" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } -kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } -apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "apkzlib" } +apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" } +apksig = { module = "com.android.tools.build:apksig", version.ref = "android" } bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" } guava = { module = "com.google.guava:guava", version.ref = "guava" } -apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" } +libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } +libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" } +libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" } +core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } [plugins] binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } -kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +android-library = { id = "com.android.library", version.ref = "android" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4cdf41af1ab109bc7f253b2b887023340..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 39466 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 delta 43784 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 diff --git a/gradlew b/gradlew index fcb6fca..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/bin/sh +#!/usr/bin/env sh # -# Copyright © 2015-2021 the original authors. +# Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,98 +17,67 @@ # ############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# +## +## Gradle start up script for UN*X +## ############################################################################## # Attempt to set APP_HOME - # Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum +MAX_FD="maximum" warn () { echo "$*" -} >&2 +} die () { echo echo "$*" echo exit 1 -} >&2 +} # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -118,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java + JAVACMD="$JAVA_HOME/jre/sh/java" else - JAVACMD=$JAVA_HOME/bin/java + JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -129,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi fi # Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi # For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=$( cygpath --unix "$JAVACMD" ) + JAVACMD=`cygpath --unix "$JAVACMD"` - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac fi +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..107acd3 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%"=="" @echo off +@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,8 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused +if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd +if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle.kts b/settings.gradle.kts index 1df3126..e164b40 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,15 @@ +// TODO: Figure out why this causes problems. rootProject.name = "revanced-library" buildCache { local { - isEnabled = !System.getenv().containsKey("CI") + isEnabled = "CI" !in System.getenv() + } +} + +pluginManagement { + repositories { + google() + mavenCentral() } } diff --git a/src/androidMain/aidl/app/revanced/library/installation/command/ILocalShellCommandRunnerRootService.aidl b/src/androidMain/aidl/app/revanced/library/installation/command/ILocalShellCommandRunnerRootService.aidl new file mode 100644 index 0000000..39861ec --- /dev/null +++ b/src/androidMain/aidl/app/revanced/library/installation/command/ILocalShellCommandRunnerRootService.aidl @@ -0,0 +1,5 @@ +package app.revanced.library.installation.command; + +interface ILocalShellCommandRunnerRootService { + IBinder getFileSystemService(); +} diff --git a/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunner.kt b/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunner.kt new file mode 100644 index 0000000..f757ba3 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunner.kt @@ -0,0 +1,88 @@ +package app.revanced.library.installation.command + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.internal.BuilderImpl +import com.topjohnwu.superuser.ipc.RootService +import com.topjohnwu.superuser.nio.FileSystemManager +import java.io.Closeable +import java.io.File +import java.io.InputStream + +/** + * The [LocalShellCommandRunner] for running commands locally on the device. + * + * @param context The [Context] to use for binding to the [RootService]. + * @param onReady A callback to be invoked when [LocalShellCommandRunner] is ready to be used. + * @throws IllegalStateException If the main shell was already created + * + * @see ShellCommandRunner + */ +class LocalShellCommandRunner internal constructor( + private val context: Context, + private val onReady: () -> Unit, +) : ShellCommandRunner(), ServiceConnection, Closeable { + private var fileSystemManager: FileSystemManager? = null + + init { + logger.info("Binding to RootService") + val intent = Intent(context, LocalShellCommandRunnerRootService::class.java) + RootService.bind(intent, this) + } + + override fun runCommand(command: String) = shell.newJob().add(command).exec().let { + object : RunResult { + override val exitCode = it.code + override val output by lazy { it.out.joinToString("\n") } + override val error by lazy { it.err.joinToString("\n") } + } + } + + override fun hasRootPermission() = shell.isRoot + + /** + * Writes the given [content] to the given [targetFilePath]. + * + * @param content The [InputStream] to write. + * @param targetFilePath The path to write to. + * @throws NotReadyException If the [LocalShellCommandRunner] is not ready yet. + */ + override fun write(content: InputStream, targetFilePath: String) { + fileSystemManager?.let { + it.getFile(targetFilePath).newOutputStream().use { outputStream -> + content.copyTo(outputStream) + } + } ?: throw NotReadyException("FileSystemManager service is not ready yet") + } + + override fun move(file: File, targetFilePath: String) { + invoke("mv ${file.absolutePath} $targetFilePath") + } + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val ipc = ILocalShellCommandRunnerRootService.Stub.asInterface(service) + fileSystemManager = FileSystemManager.getRemote(ipc.fileSystemService) + + logger.info("LocalShellCommandRunner service is ready") + + onReady() + } + + override fun onServiceDisconnected(name: ComponentName?) { + fileSystemManager = null + + logger.info("LocalShellCommandRunner service is disconnected") + } + + override fun close() = RootService.unbind(this) + + private companion object { + private val shell = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER).build() + } + + internal class NotReadyException internal constructor(message: String) : Exception(message) +} diff --git a/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunnerRootService.kt b/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunnerRootService.kt new file mode 100644 index 0000000..e221257 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/command/LocalShellCommandRunnerRootService.kt @@ -0,0 +1,15 @@ +package app.revanced.library.installation.command + +import android.content.Intent +import com.topjohnwu.superuser.ipc.RootService +import com.topjohnwu.superuser.nio.FileSystemManager + +/** + * The [RootService] for the [LocalShellCommandRunner]. + */ +internal class LocalShellCommandRunnerRootService : RootService() { + override fun onBind(intent: Intent) = object : ILocalShellCommandRunnerRootService.Stub() { + override fun getFileSystemService() = + FileSystemManager.getService() + } +} diff --git a/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt new file mode 100644 index 0000000..cb3bf48 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt @@ -0,0 +1,104 @@ +package app.revanced.library.installation.installer + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageInstaller +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat +import app.revanced.library.installation.installer.Installer.Apk +import java.io.Closeable +import java.io.File + +/** + * [LocalInstaller] for installing and uninstalling [Apk] files locally. + * + * @param context The [Context] to use for installing and uninstalling. + * @param onResult The callback to be invoked when the [Apk] is installed or uninstalled. + * + * @see Installer + */ +@Suppress("unused") +class LocalInstaller( + private val context: Context, + onResult: (result: LocalInstallerResult) -> Unit, +) : Installer(), Closeable { + private val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val pmStatus = intent.getIntExtra(LocalInstallerService.EXTRA_STATUS, -999) + val extra = intent.getStringExtra(LocalInstallerService.EXTRA_STATUS_MESSAGE)!! + val packageName = intent.getStringExtra(LocalInstallerService.EXTRA_PACKAGE_NAME)!! + + onResult.invoke(LocalInstallerResult(pmStatus, extra, packageName)) + } + } + + private val intentSender + get() = PendingIntent.getService( + context, + 0, + Intent(context, LocalInstallerService::class.java), + PendingIntent.FLAG_UPDATE_CURRENT, + ).intentSender + + init { + ContextCompat.registerReceiver( + context, + broadcastReceiver, + IntentFilter().apply { + addAction(LocalInstallerService.ACTION) + }, + ContextCompat.RECEIVER_NOT_EXPORTED, + ) + } + + override suspend fun install(apk: Apk) { + logger.info("Installing ${apk.file.name}") + + val packageInstaller = context.packageManager.packageInstaller + + packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session -> + session.writeApk(apk.file) + session.commit(intentSender) + } + } + + @SuppressLint("MissingPermission") + override suspend fun uninstall(packageName: String) { + logger.info("Uninstalling $packageName") + + val packageInstaller = context.packageManager.packageInstaller + + packageInstaller.uninstall(packageName, intentSender) + } + + override suspend fun getInstallation(packageName: String) = try { + val packageInfo = context.packageManager.getPackageInfo(packageName, 0) + + Installation(packageInfo.applicationInfo.sourceDir) + } catch (e: PackageManager.NameNotFoundException) { + null + } + + override fun close() = context.unregisterReceiver(broadcastReceiver) + + companion object { + private val sessionParams = PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL, + ).apply { + setInstallReason(PackageManager.INSTALL_REASON_USER) + } + + private fun PackageInstaller.Session.writeApk(apk: File) { + apk.inputStream().use { inputStream -> + openWrite(apk.name, 0, apk.length()).use { outputStream -> + inputStream.copyTo(outputStream, 1024 * 1024) + fsync(outputStream) + } + } + } + } +} diff --git a/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerResult.kt b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerResult.kt new file mode 100644 index 0000000..a326181 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerResult.kt @@ -0,0 +1,15 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.installer.Installer.Apk + +/** + * The result of installing or uninstalling an [Apk] locally using [LocalInstaller]. + * + * @param pmStatus The status code returned by the package manager. + * @param extra The extra information returned by the package manager. + * @param packageName The package name of the installed app. + * + * @see LocalInstaller + */ +@Suppress("MemberVisibilityCanBePrivate") +class LocalInstallerResult internal constructor(val pmStatus: Int, val extra: String, val packageName: String) diff --git a/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerService.kt b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerService.kt new file mode 100644 index 0000000..dc15569 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstallerService.kt @@ -0,0 +1,57 @@ +package app.revanced.library.installation.installer + +import android.app.Service +import android.content.Intent +import android.content.pm.PackageInstaller +import android.os.Build +import android.os.IBinder + +class LocalInstallerService : Service() { + override fun onStartCommand( + intent: Intent, flags: Int, startId: Int + ): Int { + val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0) + val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + val extraPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) + + when (extraStatus) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + startActivity( + if (Build.VERSION.SDK_INT >= 33) { + intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(Intent.EXTRA_INTENT) + }?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + + else -> { + sendBroadcast( + Intent().apply { + action = ACTION + `package` = packageName + + putExtra(EXTRA_STATUS, extraStatus) + putExtra(EXTRA_STATUS_MESSAGE, extraStatusMessage) + putExtra(EXTRA_PACKAGE_NAME, extraPackageName) + } + ) + } + } + + stopSelf() + + return START_NOT_STICKY + } + + override fun onBind(intent: Intent?): IBinder? = null + + internal companion object { + internal const val ACTION = "PACKAGE_INSTALLER_ACTION" + + internal const val EXTRA_STATUS = "EXTRA_STATUS" + internal const val EXTRA_STATUS_MESSAGE = "EXTRA_STATUS_MESSAGE" + internal const val EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME" + } +} diff --git a/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalRootInstaller.kt b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalRootInstaller.kt new file mode 100644 index 0000000..4247e83 --- /dev/null +++ b/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalRootInstaller.kt @@ -0,0 +1,34 @@ +package app.revanced.library.installation.installer + +import android.content.Context +import app.revanced.library.installation.command.LocalShellCommandRunner +import app.revanced.library.installation.installer.Installer.Apk +import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException +import com.topjohnwu.superuser.ipc.RootService +import java.io.Closeable + +/** + * [LocalRootInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting. + * + * @param context The [Context] to use for binding to the [RootService]. + * @param onReady A callback to be invoked when [LocalRootInstaller] is ready to be used. + * + * @throws NoRootPermissionException If the device does not have root permission. + * + * @see Installer + * @see LocalShellCommandRunner + */ +@Suppress("unused") +class LocalRootInstaller( + context: Context, + onReady: LocalRootInstaller.() -> Unit = {}, +) : RootInstaller( + { installer -> + LocalShellCommandRunner(context) { + (installer as LocalRootInstaller).onReady() + } + }, +), + Closeable { + override fun close() = (shellCommandRunner as LocalShellCommandRunner).close() +} diff --git a/src/main/kotlin/app/revanced/library/ApkSigner.kt b/src/commonMain/kotlin/app/revanced/library/ApkSigner.kt similarity index 100% rename from src/main/kotlin/app/revanced/library/ApkSigner.kt rename to src/commonMain/kotlin/app/revanced/library/ApkSigner.kt diff --git a/src/main/kotlin/app/revanced/library/ApkUtils.kt b/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt similarity index 100% rename from src/main/kotlin/app/revanced/library/ApkUtils.kt rename to src/commonMain/kotlin/app/revanced/library/ApkUtils.kt diff --git a/src/commonMain/kotlin/app/revanced/library/Commands.kt b/src/commonMain/kotlin/app/revanced/library/Commands.kt new file mode 100644 index 0000000..3f25439 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/Commands.kt @@ -0,0 +1,43 @@ +@file:Suppress("DeprecatedCallableAddReplaceWith") + +package app.revanced.library + +import app.revanced.library.installation.command.AdbShellCommandRunner +import se.vidstige.jadb.JadbDevice +import se.vidstige.jadb.ShellProcessBuilder +import java.io.File + +@Deprecated("Do not use this anymore. Instead use AdbCommandRunner.") +internal fun JadbDevice.buildCommand( + command: String, + su: Boolean = true, +): ShellProcessBuilder { + if (su) return shellProcessBuilder("su -c \'$command\'") + + val args = command.split(" ") as ArrayList + val cmd = args.removeFirst() + + return shellProcessBuilder(cmd, *args.toTypedArray()) +} + +@Suppress("DEPRECATION") +@Deprecated("Use AdbShellCommandRunner instead.") +internal fun JadbDevice.run( + command: String, + su: Boolean = true, +) = buildCommand(command, su).start() + +@Deprecated("Use AdbShellCommandRunner instead.") +internal fun JadbDevice.hasSu() = AdbShellCommandRunner(this).hasRootPermission() + +@Deprecated("Use AdbShellCommandRunner instead.") +internal fun JadbDevice.push( + file: File, + targetFilePath: String, +) = AdbShellCommandRunner(this).move(file, targetFilePath) + +@Deprecated("Use AdbShellCommandRunner instead.") +internal fun JadbDevice.createFile( + targetFile: String, + content: String, +) = AdbShellCommandRunner(this).write(content.byteInputStream(), targetFile) diff --git a/src/main/kotlin/app/revanced/library/Options.kt b/src/commonMain/kotlin/app/revanced/library/Options.kt similarity index 100% rename from src/main/kotlin/app/revanced/library/Options.kt rename to src/commonMain/kotlin/app/revanced/library/Options.kt diff --git a/src/main/kotlin/app/revanced/library/PatchUtils.kt b/src/commonMain/kotlin/app/revanced/library/PatchUtils.kt similarity index 100% rename from src/main/kotlin/app/revanced/library/PatchUtils.kt rename to src/commonMain/kotlin/app/revanced/library/PatchUtils.kt diff --git a/src/commonMain/kotlin/app/revanced/library/Utils.kt b/src/commonMain/kotlin/app/revanced/library/Utils.kt new file mode 100644 index 0000000..557831f --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/Utils.kt @@ -0,0 +1,18 @@ +package app.revanced.library + +/** + * Utils for the library. + */ +@Suppress("unused") +object Utils { + /** + * True if the environment is Android. + */ + val isAndroidEnvironment = + try { + Class.forName("android.app.Application") + true + } catch (e: ClassNotFoundException) { + false + } +} diff --git a/src/commonMain/kotlin/app/revanced/library/adb/AdbManager.kt b/src/commonMain/kotlin/app/revanced/library/adb/AdbManager.kt new file mode 100644 index 0000000..1eefdd0 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/adb/AdbManager.kt @@ -0,0 +1,144 @@ +@file:Suppress("DEPRECATION") + +package app.revanced.library.adb + +import app.revanced.library.adb.AdbManager.Apk +import app.revanced.library.installation.installer.AdbInstaller +import app.revanced.library.installation.installer.AdbRootInstaller +import app.revanced.library.installation.installer.Constants.PLACEHOLDER +import app.revanced.library.installation.installer.Installer +import app.revanced.library.run +import se.vidstige.jadb.JadbDevice +import java.io.File + +/** + * [AdbManager] to install and uninstall [Apk] files. + * + * @param deviceSerial The serial of the device. If null, the first connected device will be used. + */ +@Deprecated("Use an implementation of Installer instead.") +@Suppress("unused") +sealed class AdbManager private constructor( + @Suppress("UNUSED_PARAMETER") deviceSerial: String?, +) { + protected abstract val installer: Installer<*, *> + + /** + * Installs the [Apk] file. + * + * @param apk The [Apk] file. + */ + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use Installer.install instead.") + open fun install(apk: Apk) = suspend { + installer.install(Installer.Apk(apk.file, apk.packageName)) + } + + /** + * Uninstalls the package. + * + * @param packageName The package name. + */ + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use Installer.uninstall instead.") + open fun uninstall(packageName: String) = suspend { + installer.uninstall(packageName) + } + + @Deprecated("Use Installer instead.") + companion object { + /** + * Gets an [AdbManager] for the supplied device serial. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + * @param root Whether to use root or not. + * @return The [AdbManager]. + * @throws DeviceNotFoundException If the device can not be found. + */ + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("This is deprecated.") + fun getAdbManager( + deviceSerial: String? = null, + root: Boolean = false, + ): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial) + } + + /** + * Adb manager for rooted devices. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + */ + @Deprecated("Use AdbRootInstaller instead.", ReplaceWith("AdbRootInstaller(deviceSerial)")) + class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) { + override val installer = AdbRootInstaller(deviceSerial) + + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use AdbRootInstaller.install instead.") + override fun install(apk: Apk) = suspend { + installer.install(Installer.Apk(apk.file, apk.packageName)) + } + + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use AdbRootInstaller.uninstall instead.") + override fun uninstall(packageName: String) = suspend { + installer.uninstall(packageName) + } + + @Deprecated("This is deprecated.") + companion object Utils { + private fun JadbDevice.run( + command: String, + with: String, + ) = run(command.applyReplacement(with)) + + private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with) + } + } + + /** + * Adb manager for non-rooted devices. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + */ + @Deprecated("Use AdbInstaller instead.") + class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) { + override val installer = AdbInstaller(deviceSerial) + + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use AdbInstaller.install instead.") + override fun install(apk: Apk) = suspend { + installer.install(Installer.Apk(apk.file, apk.packageName)) + } + + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use AdbInstaller.uninstall instead.") + override fun uninstall(packageName: String) = suspend { + installer.uninstall(packageName) + } + } + + /** + * Apk file for [AdbManager]. + * + * @param file The [Apk] file. + * @param packageName The package name of the [Apk] file. + */ + @Deprecated("Use Installer.Apk instead.") + class Apk(val file: File, val packageName: String? = null) + + @Deprecated("Use AdbCommandRunner.DeviceNotFoundException instead.") + class DeviceNotFoundException internal constructor(deviceSerial: String? = null) : + Exception( + deviceSerial?.let { + "The device with the ADB device serial \"$deviceSerial\" can not be found" + } ?: "No ADB device found", + ) + + @Deprecated("Use RootInstaller.FailedToFindInstalledPackageException instead.") + class FailedToFindInstalledPackageException internal constructor(packageName: String) : + Exception("Failed to find installed package \"$packageName\" because no activity was found") + + @Deprecated("Use RootInstaller.PackageNameRequiredException instead.") + class PackageNameRequiredException internal constructor() : + Exception("Package name is required") +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/command/AdbShellCommandRunner.kt b/src/commonMain/kotlin/app/revanced/library/installation/command/AdbShellCommandRunner.kt new file mode 100644 index 0000000..df60d1b --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/command/AdbShellCommandRunner.kt @@ -0,0 +1,59 @@ +package app.revanced.library.installation.command + +import app.revanced.library.installation.installer.Utils +import se.vidstige.jadb.JadbDevice +import se.vidstige.jadb.RemoteFile +import java.io.File +import java.io.InputStream + +/** + * [AdbShellCommandRunner] for running commands on a device remotely using ADB. + * + * @see ShellCommandRunner + */ +class AdbShellCommandRunner : ShellCommandRunner { + private val device: JadbDevice + + /** + * Creates a [AdbShellCommandRunner] for the given device. + * + * @param device The device. + */ + internal constructor(device: JadbDevice) { + this.device = device + } + + /** + * Creates a [AdbShellCommandRunner] for the device with the given serial. + * + * @param deviceSerial deviceSerial The device serial. If null, the first connected device will be used. + */ + internal constructor(deviceSerial: String?) { + device = Utils.getDevice(deviceSerial, logger) + } + + override fun runCommand(command: String) = device.shellProcessBuilder(command).start().let { process -> + object : RunResult { + override val exitCode by lazy { process.waitFor() } + override val output by lazy { process.inputStream.bufferedReader().readText() } + override val error by lazy { process.errorStream.bufferedReader().readText() } + + override fun waitFor() { + process.waitFor() + } + } + } + + override fun hasRootPermission(): Boolean = invoke("whoami").exitCode == 0 + + override fun write(content: InputStream, targetFilePath: String) = + device.push(content, System.currentTimeMillis(), 644, RemoteFile(targetFilePath)) + + /** + * Moves the given [file] from the local to the target file path on the device. + * + * @param file The file to move. + * @param targetFilePath The target file path. + */ + override fun move(file: File, targetFilePath: String) = device.push(file, RemoteFile(targetFilePath)) +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/command/RunResult.kt b/src/commonMain/kotlin/app/revanced/library/installation/command/RunResult.kt new file mode 100644 index 0000000..c5b0183 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/command/RunResult.kt @@ -0,0 +1,26 @@ +package app.revanced.library.installation.command + +/** + * The result of a command execution. + */ +interface RunResult { + /** + * The exit code of the command. + */ + val exitCode: Int + + /** + * The output of the command. + */ + val output: String + + /** + * The error of the command. + */ + val error: String + + /** + * Waits for the command to finish. + */ + fun waitFor() {} +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/command/ShellCommandRunner.kt b/src/commonMain/kotlin/app/revanced/library/installation/command/ShellCommandRunner.kt new file mode 100644 index 0000000..554ac0d --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/command/ShellCommandRunner.kt @@ -0,0 +1,59 @@ +package app.revanced.library.installation.command + +import java.io.File +import java.io.InputStream +import java.util.logging.Logger + +/** + * [ShellCommandRunner] for running commands on a device. + */ +abstract class ShellCommandRunner internal constructor() { + protected val logger: Logger = Logger.getLogger(this::class.java.name) + + /** + * Writes the given [content] to the file at the given [targetFilePath] path. + * + * @param content The content of the file. + * @param targetFilePath The target file path. + */ + internal abstract fun write( + content: InputStream, + targetFilePath: String, + ) + + /** + * Moves the given [file] to the given [targetFilePath] path. + * + * @param file The file to move. + * @param targetFilePath The target file path. + */ + internal abstract fun move( + file: File, + targetFilePath: String, + ) + + /** + * Runs the given [command] on the device as root. + * + * @param command The command to run. + * @return The [RunResult]. + */ + protected abstract fun runCommand(command: String): RunResult + + /** + * Checks if the device has root permission. + * + * @return True if the device has root permission, false otherwise. + */ + internal abstract fun hasRootPermission(): Boolean + + /** + * Runs a command on the device as root. + * + * @param command The command to run. + * @return The [RunResult]. + */ + internal operator fun invoke( + command: String, + ) = runCommand("su -c \'$command\'") +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt new file mode 100644 index 0000000..4358e64 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt @@ -0,0 +1,51 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.command.AdbShellCommandRunner +import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH +import app.revanced.library.installation.installer.Installer.Apk +import se.vidstige.jadb.JadbException +import se.vidstige.jadb.managers.Package +import se.vidstige.jadb.managers.PackageManager + +/** + * [AdbInstaller] for installing and uninstalling [Apk] files using ADB. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + * + * @see Installer + */ +class AdbInstaller( + deviceSerial: String? = null, +) : Installer() { + private val device = Utils.getDevice(deviceSerial, logger) + private val adbShellCommandRunner = AdbShellCommandRunner(device) + private val packageManager = PackageManager(device) + + init { + logger.fine("Connected to $deviceSerial") + } + + override suspend fun install(apk: Apk): AdbInstallerResult { + logger.info("Installing ${apk.file.name}") + + return runPackageManager { install(apk.file) } + } + + override suspend fun uninstall(packageName: String): AdbInstallerResult { + logger.info("Uninstalling $packageName") + + return runPackageManager { uninstall(Package(packageName)) } + } + + override suspend fun getInstallation(packageName: String): Installation? = packageManager.packages.find { + it.toString() == packageName + }?.let { Installation(adbShellCommandRunner(INSTALLED_APK_PATH).output) } + + private fun runPackageManager(block: PackageManager.() -> Unit) = try { + packageManager.run(block) + + AdbInstallerResult.Success + } catch (e: JadbException) { + AdbInstallerResult.Failure(e) + } +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstallerResult.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstallerResult.kt new file mode 100644 index 0000000..e519492 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstallerResult.kt @@ -0,0 +1,23 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.installer.Installer.Apk + +/** + * The result of installing or uninstalling an [Apk] via ADB using [AdbInstaller]. + * + * @see AdbInstaller + */ +@Suppress("MemberVisibilityCanBePrivate") +interface AdbInstallerResult { + /** + * The result of installing an [Apk] successfully. + */ + object Success : AdbInstallerResult + + /** + * The result of installing an [Apk] unsuccessfully. + * + * @param exception The exception that caused the installation to fail. + */ + class Failure internal constructor(val exception: Exception) : AdbInstallerResult +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbRootInstaller.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbRootInstaller.kt new file mode 100644 index 0000000..d2d771f --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbRootInstaller.kt @@ -0,0 +1,23 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.command.AdbShellCommandRunner +import app.revanced.library.installation.installer.Installer.Apk +import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException + +/** + * [AdbRootInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + * + * @throws NoRootPermissionException If the device does not have root permission. + * + * @see RootInstaller + * @see AdbShellCommandRunner + */ +class AdbRootInstaller( + deviceSerial: String? = null, +) : RootInstaller({ AdbShellCommandRunner(deviceSerial) }) { + init { + logger.fine("Connected to $deviceSerial") + } +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt new file mode 100644 index 0000000..24d9da1 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt @@ -0,0 +1,75 @@ +package app.revanced.library.installation.installer + +@Suppress("MemberVisibilityCanBePrivate") +internal object Constants { + const val PLACEHOLDER = "PLACEHOLDER" + + const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp" + const val MOUNT_PATH = "/data/adb/revanced/" + const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk" + const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh" + + const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1" + const val MOUNT_GREP = "grep $PLACEHOLDER /proc/mounts" + const val DELETE = "rm -rf $PLACEHOLDER" + const val CREATE_DIR = "mkdir -p" + const val RESTART = "am start -S $PLACEHOLDER" + const val KILL = "am force-stop $PLACEHOLDER" + const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER" + const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH" + + const val MOUNT_APK = + "base_path=\"$MOUNTED_APK_PATH\" && " + + "mv $TMP_FILE_PATH \$base_path && " + + "chmod 644 \$base_path && " + + "chown system:system \$base_path && " + + "chcon u:object_r:apk_data_file:s0 \$base_path" + + const val UMOUNT = + "grep $PLACEHOLDER /proc/mounts | " + + "while read -r line; do echo \$line | " + + "cut -d ' ' -f 2 | " + + "sed 's/apk.*/apk/' | " + + "xargs -r umount -l; done" + + const val INSTALL_MOUNT_SCRIPT = "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH" + + val MOUNT_SCRIPT = + """ + #!/system/bin/sh + until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done + until [ -d "/sdcard/Android" ]; do sleep 1; done + + stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' ) + + # Make sure the app is installed. + if [ -z "${'$'}stock_path" ]; then + exit 1 + fi + + # Unmount any existing installations to prevent multiple unnecessary mounts. + $UMOUNT + + base_path="$MOUNTED_APK_PATH" + + chcon u:object_r:apk_data_file:s0 ${'$'}base_path + + # Use Magisk mirror, if possible. + if command -v magisk &> /dev/null; then + MIRROR="${'$'}(magisk --path)/.magisk/mirror" + fi + + mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path + + # Kill the app to force it to restart the mounted APK in case it's currently running. + $KILL + """.trimIndent() + + /** + * Replaces the [PLACEHOLDER] with the given [replacement]. + * + * @param replacement The replacement to use. + * @return The replaced string. + */ + operator fun String.invoke(replacement: String) = replace(PLACEHOLDER, replacement) +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/Installation.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/Installation.kt new file mode 100644 index 0000000..54efc6f --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/Installation.kt @@ -0,0 +1,11 @@ +package app.revanced.library.installation.installer + +/** + * [Installation] of an apk file. + * + * @param apkFilePath The apk file path. + */ +@Suppress("MemberVisibilityCanBePrivate") +open class Installation internal constructor( + val apkFilePath: String, +) diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt new file mode 100644 index 0000000..da709ea --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt @@ -0,0 +1,53 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.installer.Installer.Apk +import java.io.File +import java.util.logging.Logger + +/** + * [Installer] for installing and uninstalling [Apk] files. + * + * @param TInstallerResult The type of the result of the installation. + * @param TInstallation The type of the installation. + */ +abstract class Installer internal constructor() { + /** + * The [Logger]. + */ + protected val logger: Logger = Logger.getLogger(this::class.java.name) + + /** + * Installs the [Apk] file. + * + * @param apk The [Apk] file. + * + * @return The result of the installation. + */ + abstract suspend fun install(apk: Apk): TInstallerResult + + /** + * Uninstalls the package. + * + * @param packageName The package name. + * + * @return The result of the uninstallation. + */ + abstract suspend fun uninstall(packageName: String): TInstallerResult + + /** + * Gets the current installation or null if not installed. + * + * @param packageName The package name. + * + * @return The installation. + */ + abstract suspend fun getInstallation(packageName: String): TInstallation? + + /** + * Apk file for [Installer]. + * + * @param file The [Apk] file. + * @param packageName The package name of the [Apk] file. + */ + class Apk(val file: File, val packageName: String? = null) +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallation.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallation.kt new file mode 100644 index 0000000..95c7257 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallation.kt @@ -0,0 +1,15 @@ +package app.revanced.library.installation.installer + +/** + * [RootInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions. + * + * @param installedApkFilePath The installed apk file path or null if the apk is not installed. + * @param apkFilePath The mounting apk file path. + * @param mounted Whether the apk is mounted to [installedApkFilePath]. + */ +@Suppress("MemberVisibilityCanBePrivate") +class RootInstallation internal constructor( + val installedApkFilePath: String?, + apkFilePath: String, + val mounted: Boolean, +) : Installation(apkFilePath) diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt new file mode 100644 index 0000000..9fd4fac --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt @@ -0,0 +1,135 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.command.ShellCommandRunner +import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION_PATH +import app.revanced.library.installation.installer.Constants.DELETE +import app.revanced.library.installation.installer.Constants.EXISTS +import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH +import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT +import app.revanced.library.installation.installer.Constants.KILL +import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH +import app.revanced.library.installation.installer.Constants.MOUNT_APK +import app.revanced.library.installation.installer.Constants.MOUNT_GREP +import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT +import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH +import app.revanced.library.installation.installer.Constants.RESTART +import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH +import app.revanced.library.installation.installer.Constants.UMOUNT +import app.revanced.library.installation.installer.Constants.invoke +import app.revanced.library.installation.installer.Installer.Apk +import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException +import java.io.File + +/** + * [RootInstaller] for installing and uninstalling [Apk] files using root permissions by mounting. + * + * @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use. + * + * @throws NoRootPermissionException If the device does not have root permission. + */ +@Suppress("MemberVisibilityCanBePrivate") +abstract class RootInstaller internal constructor( + shellCommandRunnerSupplier: (RootInstaller) -> ShellCommandRunner, +) : Installer() { + + /** + * The command runner used to run commands on the device. + */ + @Suppress("LeakingThis") + protected val shellCommandRunner = shellCommandRunnerSupplier(this) + + init { + if (!shellCommandRunner.hasRootPermission()) throw NoRootPermissionException() + } + + /** + * Installs the given [apk] by mounting. + * + * @param apk The [Apk] to install. + * + * @throws PackageNameRequiredException If the [Apk] does not have a package name. + */ + override suspend fun install(apk: Apk): RootInstallerResult { + logger.info("Installing ${apk.packageName} by mounting") + + val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException() + + // Setup files. + apk.file.move(TMP_FILE_PATH) + CREATE_INSTALLATION_PATH().waitFor() + MOUNT_APK(packageName)().waitFor() + + // Install and run. + TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName)) + INSTALL_MOUNT_SCRIPT(packageName)().waitFor() + MOUNT_SCRIPT_PATH(packageName)().waitFor() + RESTART(packageName)() + + DELETE(TMP_FILE_PATH)() + + return RootInstallerResult.SUCCESS + } + + override suspend fun uninstall(packageName: String): RootInstallerResult { + logger.info("Uninstalling $packageName by unmounting") + + UMOUNT(packageName)() + + DELETE(MOUNTED_APK_PATH)(packageName)() + DELETE(MOUNT_SCRIPT_PATH)(packageName)() + DELETE(TMP_FILE_PATH)() // Remove residual. + + KILL(packageName)() + + return RootInstallerResult.SUCCESS + } + + override suspend fun getInstallation(packageName: String): RootInstallation? { + val patchedApkPath = MOUNTED_APK_PATH(packageName) + + val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0 + if (patchedApkExists) return null + + return RootInstallation( + INSTALLED_APK_PATH(packageName)().output.ifEmpty { null }, + patchedApkPath, + MOUNT_GREP(patchedApkPath)().exitCode == 0, + ) + } + + /** + * Runs a command on the device. + */ + protected operator fun String.invoke() = shellCommandRunner(this) + + /** + * Moves the given file to the given [targetFilePath]. + * + * @param targetFilePath The target file path. + */ + protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath) + + /** + * Writes the given [content] to the file. + * + * @param content The content of the file. + */ + protected fun String.write(content: String) = shellCommandRunner.write(content.byteInputStream(), this) + + /** + * Asserts that the package is installed. + * + * @throws FailedToFindInstalledPackageException If the package is not installed. + */ + private fun String.assertInstalled() { + if (INSTALLED_APK_PATH(this)().output.isNotEmpty()) { + throw FailedToFindInstalledPackageException(this) + } + } + + internal class FailedToFindInstalledPackageException internal constructor(packageName: String) : + Exception("Failed to find installed package \"$packageName\" because no activity was found") + + internal class PackageNameRequiredException internal constructor() : Exception("Package name is required") + internal class NoRootPermissionException internal constructor() : Exception("No root permission") +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallerResult.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallerResult.kt new file mode 100644 index 0000000..f9fe940 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstallerResult.kt @@ -0,0 +1,20 @@ +package app.revanced.library.installation.installer + +import app.revanced.library.installation.installer.Installer.Apk + +/** + * The result of installing or uninstalling an [Apk] with root permissions using [RootInstaller]. + * + * @see RootInstaller + */ +enum class RootInstallerResult { + /** + * The result of installing an [Apk] successfully. + */ + SUCCESS, + + /** + * The result of installing an [Apk] unsuccessfully. + */ + FAILURE, +} diff --git a/src/commonMain/kotlin/app/revanced/library/installation/installer/Utils.kt b/src/commonMain/kotlin/app/revanced/library/installation/installer/Utils.kt new file mode 100644 index 0000000..2082e95 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/installation/installer/Utils.kt @@ -0,0 +1,40 @@ +package app.revanced.library.installation.installer + +import se.vidstige.jadb.JadbConnection +import java.util.logging.Logger + +/** + * Utility functions for [Installer]. + * + * @see Installer + */ +internal object Utils { + /** + * Gets the device with the given serial. + * + * @param deviceSerial The device serial. If null, the first connected device will be used. + * @param logger The logger. + * @return The device. + * @throws DeviceNotFoundException If no device with the given serial is found. + */ + internal fun getDevice( + deviceSerial: String? = null, + logger: Logger, + ) = with(JadbConnection().devices) { + if (isEmpty()) throw DeviceNotFoundException() + + deviceSerial?.let { + firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException( + deviceSerial, + ) + } ?: first().also { + logger.warning("No device serial supplied. Using device with serial ${it.serial}") + } + }!! + + class DeviceNotFoundException internal constructor(deviceSerial: String? = null) : Exception( + deviceSerial?.let { + "The device with the ADB device serial \"$deviceSerial\" can not be found" + } ?: "No ADB device found", + ) +} diff --git a/src/main/kotlin/app/revanced/library/logging/Logger.kt b/src/commonMain/kotlin/app/revanced/library/logging/Logger.kt similarity index 91% rename from src/main/kotlin/app/revanced/library/logging/Logger.kt rename to src/commonMain/kotlin/app/revanced/library/logging/Logger.kt index 04669fa..1495d78 100644 --- a/src/main/kotlin/app/revanced/library/logging/Logger.kt +++ b/src/commonMain/kotlin/app/revanced/library/logging/Logger.kt @@ -5,15 +5,17 @@ import java.util.logging.Level import java.util.logging.LogRecord import java.util.logging.SimpleFormatter -@Suppress("MemberVisibilityCanBePrivate") +@Suppress("MemberVisibilityCanBePrivate", "unused") object Logger { /** * Rules for allowed loggers. */ private val allowedLoggersRules = arrayOf Boolean>( - { startsWith("app.revanced") }, // ReVanced loggers. - { this == "" }, // Logs warnings when compiling resources (Logger in class brut.util.OS). + // ReVanced loggers. + { startsWith("app.revanced") }, + // Logs warnings when compiling resources (Logger in class brut.util.OS). + { this == "" }, ) private val rootLogger = java.util.logging.Logger.getLogger("") diff --git a/src/test/kotlin/app/revanced/library/PatchOptionsTest.kt b/src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt similarity index 61% rename from src/test/kotlin/app/revanced/library/PatchOptionsTest.kt rename to src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt index c0c03be..cc70e5e 100644 --- a/src/test/kotlin/app/revanced/library/PatchOptionsTest.kt +++ b/src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt @@ -6,36 +6,29 @@ import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption -import org.junit.jupiter.api.MethodOrderer -import org.junit.jupiter.api.Order -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestMethodOrder +import kotlin.test.Test -@TestMethodOrder(MethodOrderer.OrderAnnotation::class) -internal object PatchOptionsTest { +class PatchOptionsTest { private var patches = setOf(PatchOptionsTestPatch) - @Test - @Order(1) - fun serializeTest() { - assert(SERIALIZED_JSON == Options.serialize(patches)) - } + private val serializedJson = + "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\"," + + "\"value\":true}]}]" + + private val changedJson = + "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2" + + "\",\"value\":false}]}]" @Test - @Order(2) - fun loadOptionsTest() { - patches.setOptions(CHANGED_JSON) + fun `serializes and deserializes`() { + assert(serializedJson == Options.serialize(patches)) + + patches.setOptions(changedJson) assert(PatchOptionsTestPatch.option1 == "test") assert(PatchOptionsTestPatch.option2 == false) } - private const val SERIALIZED_JSON = - "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\",\"value\":true}]}]" - - private const val CHANGED_JSON = - "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]" - @Patch("PatchOptionsTestPatch") object PatchOptionsTestPatch : BytecodePatch(emptySet()) { var option1 by stringPatchOption("key1", null, null, "title1", "description1") diff --git a/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt b/src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt similarity index 99% rename from src/test/kotlin/app/revanced/library/PatchUtilsTest.kt rename to src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt index da4a609..dfb3f36 100644 --- a/src/test/kotlin/app/revanced/library/PatchUtilsTest.kt +++ b/src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt @@ -7,12 +7,12 @@ import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption -import org.junit.jupiter.api.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import kotlin.test.Test import kotlin.test.assertEquals -internal object PatchUtilsTest { +internal class PatchUtilsTest { private val patches = arrayOf( newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") }, diff --git a/src/main/kotlin/app/revanced/library/adb/AdbManager.kt b/src/main/kotlin/app/revanced/library/adb/AdbManager.kt deleted file mode 100644 index 82a71f5..0000000 --- a/src/main/kotlin/app/revanced/library/adb/AdbManager.kt +++ /dev/null @@ -1,183 +0,0 @@ -package app.revanced.library.adb - -import app.revanced.library.adb.AdbManager.Apk -import app.revanced.library.adb.Constants.CREATE_DIR -import app.revanced.library.adb.Constants.DELETE -import app.revanced.library.adb.Constants.GET_INSTALLED_PATH -import app.revanced.library.adb.Constants.INSTALLATION_PATH -import app.revanced.library.adb.Constants.INSTALL_MOUNT_SCRIPT -import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK -import app.revanced.library.adb.Constants.KILL -import app.revanced.library.adb.Constants.MOUNT_SCRIPT -import app.revanced.library.adb.Constants.MOUNT_SCRIPT_PATH -import app.revanced.library.adb.Constants.PATCHED_APK_PATH -import app.revanced.library.adb.Constants.PLACEHOLDER -import app.revanced.library.adb.Constants.RESTART -import app.revanced.library.adb.Constants.TMP_PATH -import app.revanced.library.adb.Constants.UMOUNT -import se.vidstige.jadb.JadbConnection -import se.vidstige.jadb.JadbDevice -import se.vidstige.jadb.managers.Package -import se.vidstige.jadb.managers.PackageManager -import java.io.File -import java.util.logging.Logger - -/** - * Adb manager. Used to install and uninstall [Apk] files. - * - * @param deviceSerial The serial of the device. If null, the first connected device will be used. - */ -sealed class AdbManager private constructor(deviceSerial: String?) { - protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name) - - protected val device = - with(JadbConnection().devices) { - if (isEmpty()) throw DeviceNotFoundException() - - deviceSerial?.let { - firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(deviceSerial) - } ?: first().also { - logger.warning("No device serial supplied. Using device with serial ${it.serial}") - } - }!! - - init { - logger.fine("Connected to ${device.serial}") - } - - /** - * Installs the [Apk] file. - * - * @param apk The [Apk] file. - */ - open fun install(apk: Apk) { - logger.info("Finished installing ${apk.file.name}") - } - - /** - * Uninstalls the package. - * - * @param packageName The package name. - */ - open fun uninstall(packageName: String) { - logger.info("Finished uninstalling $packageName") - } - - companion object { - /** - * Gets an [AdbManager] for the supplied device serial. - * - * @param deviceSerial The device serial. If null, the first connected device will be used. - * @param root Whether to use root or not. - * @return The [AdbManager]. - * @throws DeviceNotFoundException If the device can not be found. - */ - fun getAdbManager( - deviceSerial: String? = null, - root: Boolean = false, - ): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial) - } - - /** - * Adb manager for rooted devices. - * - * @param deviceSerial The device serial. If null, the first connected device will be used. - */ - class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) { - init { - if (!device.hasSu()) throw IllegalArgumentException("Root required on ${device.serial}. Task failed") - } - - override fun install(apk: Apk) { - logger.info("Installing by mounting") - - val packageName = apk.packageName ?: throw PackageNameRequiredException() - - device.run(GET_INSTALLED_PATH, packageName).inputStream.bufferedReader().readLine().let { line -> - if (line != null) return@let - throw throw FailedToFindInstalledPackageException(packageName) - } - - device.push(apk.file, TMP_PATH) - - device.run("$CREATE_DIR $INSTALLATION_PATH").waitFor() - device.run(INSTALL_PATCHED_APK, packageName).waitFor() - - device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName)) - - device.run(INSTALL_MOUNT_SCRIPT, packageName).waitFor() - device.run(MOUNT_SCRIPT_PATH, packageName).waitFor() - device.run(RESTART, packageName) - device.run(DELETE, TMP_PATH) - - super.install(apk) - } - - override fun uninstall(packageName: String) { - logger.info("Uninstalling $packageName by unmounting") - - device.run(UMOUNT, packageName) - device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName) - device.run(DELETE, MOUNT_SCRIPT_PATH.applyReplacement(packageName)) - device.run(DELETE, TMP_PATH) - device.run(KILL, packageName) - - super.uninstall(packageName) - } - - companion object Utils { - private fun JadbDevice.run( - command: String, - with: String, - ) = run(command.applyReplacement(with)) - - private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with) - } - } - - /** - * Adb manager for non-rooted devices. - * - * @param deviceSerial The device serial. If null, the first connected device will be used. - */ - class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) { - private val packageManager = PackageManager(device) - - override fun install(apk: Apk) { - logger.info("Installing ${apk.file.name}") - - PackageManager(device).install(apk.file) - - super.install(apk) - } - - override fun uninstall(packageName: String) { - logger.info("Uninstalling $packageName") - - packageManager.uninstall(Package(packageName)) - - super.uninstall(packageName) - } - } - - /** - * Apk file for [AdbManager]. - * - * @param file The [Apk] file. - * @param packageName The package name of the [Apk] file. - */ - class Apk(val file: File, val packageName: String? = null) - - class DeviceNotFoundException internal constructor(deviceSerial: String? = null) : - Exception( - deviceSerial?.let { - "The device with the ADB device serial \"$deviceSerial\" can not be found" - } ?: "No ADB device found", - ) - - class FailedToFindInstalledPackageException internal constructor(packageName: String) : - Exception("Failed to find installed package \"$packageName\" because no activity was found") - - class PackageNameRequiredException internal constructor() : - Exception("Package name is required") -} diff --git a/src/main/kotlin/app/revanced/library/adb/Commands.kt b/src/main/kotlin/app/revanced/library/adb/Commands.kt deleted file mode 100644 index 7db6774..0000000 --- a/src/main/kotlin/app/revanced/library/adb/Commands.kt +++ /dev/null @@ -1,35 +0,0 @@ -package app.revanced.library.adb - -import se.vidstige.jadb.JadbDevice -import se.vidstige.jadb.RemoteFile -import se.vidstige.jadb.ShellProcessBuilder -import java.io.File - -internal fun JadbDevice.buildCommand( - command: String, - su: Boolean = true, -): ShellProcessBuilder { - if (su) return shellProcessBuilder("su -c \'$command\'") - - val args = command.split(" ") as ArrayList - val cmd = args.removeFirst() - - return shellProcessBuilder(cmd, *args.toTypedArray()) -} - -internal fun JadbDevice.run( - command: String, - su: Boolean = true, -) = this.buildCommand(command, su).start() - -internal fun JadbDevice.hasSu() = this.run("whoami", true).waitFor() == 0 - -internal fun JadbDevice.push( - file: File, - targetFilePath: String, -) = push(file, RemoteFile(targetFilePath)) - -internal fun JadbDevice.createFile( - targetFile: String, - content: String, -) = push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile)) diff --git a/src/main/kotlin/app/revanced/library/adb/Constants.kt b/src/main/kotlin/app/revanced/library/adb/Constants.kt deleted file mode 100644 index fcf4971..0000000 --- a/src/main/kotlin/app/revanced/library/adb/Constants.kt +++ /dev/null @@ -1,54 +0,0 @@ -package app.revanced.library.adb - -internal object Constants { - internal const val PLACEHOLDER = "PLACEHOLDER" - - internal const val TMP_PATH = "/data/local/tmp/revanced.tmp" - internal const val INSTALLATION_PATH = "/data/adb/revanced/" - internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk" - internal const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh" - - internal const val DELETE = "rm -rf $PLACEHOLDER" - internal const val CREATE_DIR = "mkdir -p" - internal const val RESTART = "am start -S $PLACEHOLDER" - internal const val KILL = "am force-stop $PLACEHOLDER" - internal const val GET_INSTALLED_PATH = "pm path $PLACEHOLDER" - - internal const val INSTALL_PATCHED_APK = - "base_path=\"$PATCHED_APK_PATH\" && " + - "mv $TMP_PATH ${'$'}base_path && " + - "chmod 644 ${'$'}base_path && " + - "chown system:system ${'$'}base_path && " + - "chcon u:object_r:apk_data_file:s0 ${'$'}base_path" - - internal const val UMOUNT = - "grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d ' ' -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done" - - internal const val INSTALL_MOUNT_SCRIPT = "mv $TMP_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH" - - internal val MOUNT_SCRIPT = - """ - #!/system/bin/sh - - # Use Magisk mirror, if possible. - if command -v magisk &> /dev/null; then - MIRROR="${'$'}(magisk --path)/.magisk/mirror" - fi - - # Wait for the system to boot. - until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done - until [ -d "/sdcard/Android" ]; do sleep 1; done - - # Unmount any existing mount as a safety measure. - $UMOUNT - - base_path="$PATCHED_APK_PATH" - stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' ) - - chcon u:object_r:apk_data_file:s0 ${'$'}base_path - mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path - - # Kill the app to force it to restart the mounted APK in case it's currently running. - $KILL - """.trimIndent() -}