document and improve the webview api

This commit is contained in:
Ax333l 2024-12-01 21:29:12 +01:00
parent 66c06a6fe5
commit c3e48a331a
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
4 changed files with 89 additions and 38 deletions

View File

@ -7,7 +7,7 @@ plugins {
android {
namespace = "app.revanced.manager.plugin.downloader"
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 26

View File

@ -5,6 +5,8 @@ import android.os.Bundle
import android.os.Parcelable
import app.revanced.manager.plugin.downloader.DownloaderScope
import app.revanced.manager.plugin.downloader.GetResult
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.Scope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -14,32 +16,49 @@ import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import java.net.HttpURLConnection
import java.net.URI
import kotlin.properties.Delegates
internal typealias PageLoadCallback<T> = suspend WebViewCallbackScope<T>.(url: String) -> Unit
internal typealias DownloadCallback<T> = suspend WebViewCallbackScope<T>.(url: String, mimeType: String, userAgent: String) -> Unit
internal typealias ReadyCallback<T> = suspend WebViewCallbackScope<T>.() -> Unit
@Parcelize
data class DownloadUrl(val url: String, val mimeType: String, val userAgent: String) : Parcelable {
/**
* 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
setRequestProperty("User-Agent", userAgent)
userAgent?.let { setRequestProperty("User-Agent", it) }
connectTimeout = 10_000
connect()
inputStream to getHeaderField("Content-Length").toLong()
}
}
interface WebViewCallbackScope<T : Parcelable> {
suspend fun finish(result: GetResult<T>?)
interface WebViewCallbackScope<T> : Scope {
/**
* Finishes the activity and returns the [result].
*/
suspend fun finish(result: T)
/**
* Tells the WebView to load the specified [url].
*/
suspend fun load(url: String)
}
class WebViewScope<T : Parcelable> internal constructor(
class WebViewScope<T> internal constructor(
coroutineScope: CoroutineScope,
setResult: (GetResult<T>?) -> Unit
) {
private val scopeImpl: Scope,
setResult: (T) -> Unit
) : Scope by scopeImpl {
private var onPageLoadCallback: PageLoadCallback<T> = {}
private var onDownloadCallback: DownloadCallback<T> = { _, _, _ -> }
private var onReadyCallback: ReadyCallback<T> =
@ -76,8 +95,8 @@ class WebViewScope<T : Parcelable> internal constructor(
}
}
private val callbackScope = object : WebViewCallbackScope<T> {
override suspend fun finish(result: GetResult<T>?) {
private val callbackScope = object : WebViewCallbackScope<T>, Scope by scopeImpl {
override suspend fun finish(result: T) {
setResult(result)
// Tell the WebViewActivity to finish
webView.let { withContext(Dispatchers.IO) { it.finish() } }
@ -89,38 +108,66 @@ class WebViewScope<T : Parcelable> internal constructor(
}
fun onDownload(block: DownloadCallback<T>) {
/**
* Called when the WebView attempts to navigate to a downloadable file.
*/
fun download(block: DownloadCallback<T>) {
onDownloadCallback = block
}
fun onPageLoad(block: PageLoadCallback<T>) {
/**
* Called when the WebView finishes loading a page.
*/
fun pageLoad(block: PageLoadCallback<T>) {
onPageLoadCallback = block
}
fun onReady(block: ReadyCallback<T>) {
/**
* Called when the WebView is ready. This should always call [WebViewCallbackScope.load].
*/
fun ready(block: ReadyCallback<T>) {
onReadyCallback = block
}
}
fun <T : Parcelable> DownloaderScope<T>.webView(block: WebViewScope<T>.(packageName: String, version: String?) -> Unit) =
get { pkgName, version ->
var result: GetResult<T>? = null
@JvmInline
private value class Container<U>(val value: U)
private suspend fun <T> GetScope.runWebView(title: String, block: WebViewScope<T>.() -> Unit) =
coroutineScope {
val scope = WebViewScope(this) { result = it }
scope.block(pkgName, version)
var result by Delegates.notNull<Container<T>>()
val scope = WebViewScope<T>(this@coroutineScope, this@runWebView) { result = Container(it) }
scope.block()
// Start the webview activity and wait until it finishes
requestStartActivity(Intent().apply {
putExtras(Bundle().apply {
putBinder(WebViewActivity.BINDER_KEY, scope.binder)
val pm = context.packageManager
val label = pm.getPackageInfo(pluginPackageName, 0).applicationInfo.loadLabel(pm).toString()
putString(WebViewActivity.TITLE_KEY, label)
putString(WebViewActivity.TITLE_KEY, title)
})
setClassName(
hostPackageName,
WebViewActivity::class.qualifiedName!!
)
})
result.value
}
/**
* Implements [DownloaderScope.get] using an [android.webkit.WebView]. Event handlers are defined in the provided [block].
* The activity will keep running until it is cancelled or an event handler calls [WebViewCallbackScope.finish].
*
* @param title The title that will be shown in the WebView activity. The default value is the plugin application label.
*/
fun <T : Parcelable> DownloaderScope<T>.webView(
title: String = context.applicationInfo.loadLabel(
context.packageManager
).toString(),
block: WebViewScope<GetResult<T>?>.(packageName: String, version: String?) -> Unit
) = get { pkgName, version ->
runWebView(title) {
block(pkgName, version)
}
result
}

View File

@ -9,12 +9,12 @@ android {
val packageName = "app.revanced.manager.plugin.downloader.example"
namespace = packageName
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = packageName
minSdk = 26
targetSdk = 34
targetSdk = 35
versionCode = 1
versionName = "1.0"
}

View File

@ -54,11 +54,15 @@ val installedAppDownloader = downloader<DownloadUrl> {
build().toString()
}
onDownload { url, mimeType, userAgent ->
finish(DownloadUrl(url, mimeType, userAgent) to version)
download { url, _, userAgent ->
finish(DownloadUrl(url, userAgent) to version)
}
onReady {
pageLoad { url ->
println(url)
}
ready {
load(startUrl)
}
}