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 033e24c..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ 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() -}