diff --git a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt index 834285c4..74d157f0 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt @@ -7,7 +7,7 @@ import app.revanced.manager.data.room.AppDatabase import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.apps.downloaded.DownloadedApp import app.revanced.manager.network.downloader.LoadedDownloaderPlugin -import app.revanced.manager.plugin.downloader.DownloadScope +import app.revanced.manager.plugin.downloader.OutputDownloadScope import app.revanced.manager.util.PM import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.channelFlow @@ -52,7 +52,7 @@ class DownloadedAppRepository( val downloadedBytes = AtomicLong(0) channelFlow { - val scope = object : DownloadScope { + val scope = object : OutputDownloadScope { override val pluginPackageName = plugin.packageName override val hostPackageName = app.packageName override suspend fun reportSize(size: Long) { diff --git a/app/src/main/java/app/revanced/manager/network/downloader/LoadedDownloaderPlugin.kt b/app/src/main/java/app/revanced/manager/network/downloader/LoadedDownloaderPlugin.kt index ce28d047..50ddd561 100644 --- a/app/src/main/java/app/revanced/manager/network/downloader/LoadedDownloaderPlugin.kt +++ b/app/src/main/java/app/revanced/manager/network/downloader/LoadedDownloaderPlugin.kt @@ -1,7 +1,7 @@ package app.revanced.manager.network.downloader import android.os.Parcelable -import app.revanced.manager.plugin.downloader.DownloadScope +import app.revanced.manager.plugin.downloader.OutputDownloadScope import app.revanced.manager.plugin.downloader.GetScope import java.io.OutputStream @@ -10,6 +10,6 @@ class LoadedDownloaderPlugin( val name: String, val version: String, val get: suspend GetScope.(packageName: String, version: String?) -> Pair?, - val download: suspend DownloadScope.(data: Parcelable, outputStream: OutputStream) -> Unit, + val download: suspend OutputDownloadScope.(data: Parcelable, outputStream: OutputStream) -> Unit, val classLoader: ClassLoader ) \ No newline at end of file diff --git a/downloader-plugin/api/downloader-plugin.api b/downloader-plugin/api/downloader-plugin.api index 6b43dc1a..01630c4d 100644 --- a/downloader-plugin/api/downloader-plugin.api +++ b/downloader-plugin/api/downloader-plugin.api @@ -5,6 +5,32 @@ public final class app/revanced/manager/plugin/downloader/ConstantsKt { public static final field PLUGIN_HOST_PERMISSION Ljava/lang/String; } +public final class app/revanced/manager/plugin/downloader/DownloadUrl : android/os/Parcelable { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lapp/revanced/manager/plugin/downloader/DownloadUrl; + public static synthetic fun copy$default (Lapp/revanced/manager/plugin/downloader/DownloadUrl;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lapp/revanced/manager/plugin/downloader/DownloadUrl; + public final fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getHeaders ()Ljava/util/Map; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun toDownloadResult ()Lkotlin/Pair; + public fun toString ()Ljava/lang/String; + public final fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class app/revanced/manager/plugin/downloader/DownloadUrl$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/DownloadUrl; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/DownloadUrl; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class app/revanced/manager/plugin/downloader/Downloader { } @@ -94,31 +120,6 @@ public final class app/revanced/manager/plugin/downloader/webview/APIKt { public static final fun runWebView (Lapp/revanced/manager/plugin/downloader/GetScope;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class app/revanced/manager/plugin/downloader/webview/DownloadUrl : android/os/Parcelable { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/manager/plugin/downloader/webview/DownloadUrl; - public static synthetic fun copy$default (Lapp/revanced/manager/plugin/downloader/webview/DownloadUrl;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lapp/revanced/manager/plugin/downloader/webview/DownloadUrl; - public final fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public final fun getUrl ()Ljava/lang/String; - public final fun getUserAgent ()Ljava/lang/String; - public fun hashCode ()I - public final fun toResult ()Lkotlin/Pair; - public fun toString ()Ljava/lang/String; - public final fun writeToParcel (Landroid/os/Parcel;I)V -} - -public final class app/revanced/manager/plugin/downloader/webview/DownloadUrl$Creator : android/os/Parcelable$Creator { - public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/webview/DownloadUrl; - public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/webview/DownloadUrl; - public synthetic fun newArray (I)[Ljava/lang/Object; -} - public abstract interface class app/revanced/manager/plugin/downloader/webview/IWebView : android/os/IInterface { public static final field DESCRIPTOR Ljava/lang/String; public abstract fun finish ()V @@ -162,12 +163,19 @@ public abstract class app/revanced/manager/plugin/downloader/webview/IWebViewEve } public final class app/revanced/manager/plugin/downloader/webview/WebViewActivity : androidx/activity/ComponentActivity { - public static final field BINDER_KEY Ljava/lang/String; - public static final field TITLE_KEY Ljava/lang/String; + public static final field KEY Ljava/lang/String; public fun ()V public fun onOptionsItemSelected (Landroid/view/MenuItem;)Z } +public final class app/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/webview/WebViewActivity$Parameters; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public abstract interface class app/revanced/manager/plugin/downloader/webview/WebViewCallbackScope : app/revanced/manager/plugin/downloader/Scope { public abstract fun finish (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun load (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -176,7 +184,9 @@ public abstract interface class app/revanced/manager/plugin/downloader/webview/W public final class app/revanced/manager/plugin/downloader/webview/WebViewScope : app/revanced/manager/plugin/downloader/Scope { public final fun download (Lkotlin/jvm/functions/Function5;)V public fun getHostPackageName ()Ljava/lang/String; + public final fun getJsEnabled ()Z public fun getPluginPackageName ()Ljava/lang/String; public final fun pageLoad (Lkotlin/jvm/functions/Function3;)V + public final fun setJsEnabled (Z)V } diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Constants.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Constants.kt index 1b213bee..469daaae 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Constants.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Constants.kt @@ -1,3 +1,7 @@ package app.revanced.manager.plugin.downloader +/** + * The permission ID of the special plugin host permission. Only ReVanced Manager will have this permission. + * Plugin UI activities and internal services can be protected using this permission. + */ const val PLUGIN_HOST_PERMISSION = "app.revanced.manager.permission.PLUGIN_HOST" \ No newline at end of file diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Downloader.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Downloader.kt index 716ede2a..bf0a219b 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Downloader.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Downloader.kt @@ -40,7 +40,7 @@ interface Scope { */ interface GetScope : Scope { /** - * Ask the user to perform some required interaction contained in the activity specified by the provided [Intent]. + * Ask the user to perform some required interaction in the activity specified by the provided [Intent]. * This function returns normally with the resulting [Intent] when the activity finishes with code [Activity.RESULT_OK]. * * @throws UserInteractionException.RequestDenied User decided to skip this plugin. @@ -74,7 +74,7 @@ class DownloaderScope internal constructor( private val inputDownloadScopeImpl = object : InputDownloadScope, Scope by scopeImpl {} /** - * Define the download function for this plugin. + * Define the download block of the plugin. */ fun download(block: suspend InputDownloadScope.(data: T) -> DownloadResult) { download = { app, outputStream -> @@ -88,7 +88,8 @@ class DownloaderScope internal constructor( } /** - * Define the get function for this plugin. + * Define the get block of the plugin. + * The block should return null if the app cannot be found. The version in the result must match the version argument unless it is null. */ fun get(block: suspend GetScope.(packageName: String, version: String?) -> GetResult?) { get = block @@ -139,14 +140,25 @@ class Downloader internal constructor( @property:PluginHostApi val download: suspend OutputDownloadScope.(data: T, outputStream: OutputStream) -> Unit ) +/** + * Define a downloader plugin. + */ fun Downloader(block: DownloaderScope.() -> Unit) = DownloaderBuilder(block) +/** + * @see GetScope.requestStartActivity + */ sealed class UserInteractionException(message: String) : Exception(message) { class RequestDenied @PluginHostApi constructor() : - UserInteractionException("Request was denied") + UserInteractionException("Request denied by user") sealed class Activity(message: String) : UserInteractionException(message) { - class Cancelled @PluginHostApi constructor() : Activity("Interaction was cancelled") + class Cancelled @PluginHostApi constructor() : Activity("Interaction cancelled") + + /** + * @param resultCode The result code of the activity. + * @param intent The [Intent] of the activity. + */ class NotCompleted @PluginHostApi constructor(val resultCode: Int, val intent: Intent?) : Activity("Unexpected activity result code: $resultCode") } diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Extensions.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Extensions.kt index 2798cb54..a1e6bf79 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Extensions.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Extensions.kt @@ -8,7 +8,7 @@ import android.os.Parcelable import java.io.OutputStream /** - * The scope of [DownloaderScope.download]. + * The scope of the [OutputStream] version of [DownloaderScope.download]. */ interface OutputDownloadScope : BaseDownloadScope { suspend fun reportSize(size: Long) @@ -16,6 +16,7 @@ interface OutputDownloadScope : BaseDownloadScope { /** * A replacement for [DownloaderScope.download] that uses [OutputStream]. + * The provided [OutputStream] does not need to be closed manually. */ fun DownloaderScope.download(block: suspend OutputDownloadScope.(T, OutputStream) -> Unit) { download = block diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Package.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Package.kt deleted file mode 100644 index f2646872..00000000 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Package.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.revanced.manager.plugin.downloader - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class Package(val name: String, val version: String) : Parcelable \ No newline at end of file diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Parcelables.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Parcelables.kt new file mode 100644 index 00000000..414ad889 --- /dev/null +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/Parcelables.kt @@ -0,0 +1,39 @@ +package app.revanced.manager.plugin.downloader + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.net.HttpURLConnection +import java.net.URI + +/** + * A simple parcelable data class for storing a package name and version. + * This can be used as the data type for plugins that only need a name and version to implement their [DownloaderScope.download] function. + * + * @param name The package name. + * @param version The version. + */ +@Parcelize +data class Package(val name: String, val version: String) : Parcelable + +/** + * A data class for storing a download URL. + * + * @param url The download URL. + * @param headers The headers to use for the request. + */ +@Parcelize +data class DownloadUrl(val url: String, val headers: Map = emptyMap()) : Parcelable { + /** + * Converts this into a [DownloadResult]. + */ + fun toDownloadResult(): DownloadResult = with(URI.create(url).toURL().openConnection() as HttpURLConnection) { + useCaches = false + allowUserInteraction = false + headers.forEach(::setRequestProperty) + + connectTimeout = 10_000 + connect() + + inputStream to getHeaderField("Content-Length").toLong() + } +} \ No newline at end of file diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/API.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/API.kt index b44840d3..8b807a9c 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/API.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/API.kt @@ -2,7 +2,7 @@ package app.revanced.manager.plugin.downloader.webview import android.content.Intent import android.os.Bundle -import android.os.Parcelable +import app.revanced.manager.plugin.downloader.DownloadUrl import app.revanced.manager.plugin.downloader.DownloaderScope import app.revanced.manager.plugin.downloader.GetScope import app.revanced.manager.plugin.downloader.Scope @@ -14,35 +14,12 @@ import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext -import kotlinx.parcelize.Parcelize -import java.net.HttpURLConnection -import java.net.URI import kotlin.properties.Delegates typealias InitialUrl = String typealias PageLoadCallback = suspend WebViewCallbackScope.(url: String) -> Unit typealias DownloadCallback = suspend WebViewCallbackScope.(url: String, mimeType: String, userAgent: String) -> Unit -@Parcelize -/** - * A data class for storing a download - */ -data class DownloadUrl(val url: String, val userAgent: String?) : Parcelable { - /** - * Converts this into a [app.revanced.manager.plugin.downloader.DownloadResult]. - */ - fun toResult() = with(URI.create(url).toURL().openConnection() as HttpURLConnection) { - useCaches = false - allowUserInteraction = false - userAgent?.let { setRequestProperty("User-Agent", it) } - - connectTimeout = 10_000 - connect() - - inputStream to getHeaderField("Content-Length").toLong() - } -} - interface WebViewCallbackScope : Scope { /** * Finishes the activity and returns the [result]. @@ -68,6 +45,12 @@ class WebViewScope internal constructor( private lateinit var webView: IWebView internal lateinit var initialUrl: String + /** + * Controls whether JavaScript is enabled in the WebView. The default value is false. + * Changing this after the WebView has been launched has no effect. + */ + var jsEnabled = false + internal val binder = object : IWebViewEvents.Stub() { override fun ready(iface: IWebView?) { coroutineScope.launch(dispatcher) { @@ -107,7 +90,7 @@ class WebViewScope internal constructor( } /** - * Called when the WebView attempts to navigate to a downloadable file. + * Called when the WebView attempts to download a file to disk. */ fun download(block: DownloadCallback) { onDownloadCallback = block @@ -127,9 +110,10 @@ private value class Container(val value: U) /** * Run a [android.webkit.WebView] Activity controlled by the provided code block. * The activity will keep running until it is cancelled or an event handler calls [WebViewCallbackScope.finish]. + * The [block] defines the event handlers and returns the initial URL. * - * @param title The string displayed in the action bar - * @param block Defines event handlers and returns an initial URL + * @param title The string displayed in the action bar. + * @param block The control block. */ suspend fun GetScope.runWebView( title: String, @@ -140,12 +124,12 @@ suspend fun GetScope.runWebView( val scope = WebViewScope(this@supervisorScope, this@runWebView) { result = Container(it) } scope.initialUrl = scope.block() - // Start the webview activity and wait until it finishes + // Start the webview activity and wait until it finishes. requestStartActivity(Intent().apply { - putExtras(Bundle().apply { - putBinder(WebViewActivity.BINDER_KEY, scope.binder) - putString(WebViewActivity.TITLE_KEY, title) - }) + putExtra( + WebViewActivity.KEY, + WebViewActivity.Parameters(title, scope.jsEnabled, scope.binder) + ) setClassName( hostPackageName, WebViewActivity::class.qualifiedName!! @@ -174,7 +158,14 @@ fun WebViewDownloader(block: suspend WebViewScope.(packageName: Str try { runWebView(label) { - download { url, _, userAgent -> finish(DownloadUrl(url, userAgent)) } + download { url, _, userAgent -> + finish( + DownloadUrl( + url, + mapOf("User-Agent" to userAgent) + ) + ) + } block(this@runWebView, packageName, version) ?: throw ReturnNull() } to version @@ -184,6 +175,6 @@ fun WebViewDownloader(block: suspend WebViewScope.(packageName: Str } download { - it.toResult() + it.toDownloadResult() } } \ No newline at end of file diff --git a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt index b548318c..903931b5 100644 --- a/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt +++ b/downloader-plugin/src/main/java/app/revanced/manager/plugin/downloader/webview/WebViewActivity.kt @@ -2,6 +2,8 @@ package app.revanced.manager.plugin.downloader.webview import android.annotation.SuppressLint import android.os.Bundle +import android.os.IBinder +import android.os.Parcelable import android.view.MenuItem import android.webkit.CookieManager import android.webkit.WebSettings @@ -21,6 +23,7 @@ import app.revanced.manager.plugin.downloader.R import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize class WebViewActivity : ComponentActivity() { @SuppressLint("SetJavaScriptEnabled") @@ -36,22 +39,23 @@ class WebViewActivity : ComponentActivity() { v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } + val params = intent.getParcelableExtra(KEY)!! actionBar?.apply { - title = intent.getStringExtra(TITLE_KEY) + title = intent.getStringExtra(params.title) setHomeAsUpIndicator(android.R.drawable.ic_menu_close_clear_cancel) setDisplayHomeAsUpEnabled(true) } - val events = IWebViewEvents.Stub.asInterface(intent.extras!!.getBinder(BINDER_KEY))!! + val events = IWebViewEvents.Stub.asInterface(params.events)!! vm.setup(events) val webView = findViewById(R.id.content).apply { settings.apply { cacheMode = WebSettings.LOAD_NO_CACHE databaseEnabled = false - allowContentAccess = true + allowContentAccess = false domStorageEnabled = false - javaScriptEnabled = true + javaScriptEnabled = params.jsEnabled } webViewClient = vm.webViewClient @@ -82,9 +86,13 @@ class WebViewActivity : ComponentActivity() { true } else super.onOptionsItemSelected(item) + @Parcelize + internal class Parameters( + val title: String, val jsEnabled: Boolean, val events: IBinder + ) : Parcelable + internal companion object { - const val BINDER_KEY = "EVENTS" - const val TITLE_KEY = "TITLE" + const val KEY = "params" } }