mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
I think the new API is done
This commit is contained in:
parent
e14497a1ce
commit
38fe7bf9fd
14 changed files with 209 additions and 154 deletions
|
@ -2,12 +2,13 @@ package app.revanced.manager.domain.repository
|
|||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
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.App
|
||||
import app.revanced.manager.plugin.downloader.DownloadScope
|
||||
import app.revanced.manager.util.PM
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
|
@ -19,7 +20,7 @@ import java.nio.file.StandardOpenOption
|
|||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
||||
class DownloadedAppRepository(app: Application, db: AppDatabase, private val pm: PM) {
|
||||
private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE)
|
||||
private val dao = db.downloadedAppDao()
|
||||
|
||||
|
@ -32,10 +33,13 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
|||
|
||||
suspend fun download(
|
||||
plugin: LoadedDownloaderPlugin,
|
||||
app: App,
|
||||
data: Parcelable,
|
||||
expectedPackageName: String,
|
||||
expectedVersion: String?,
|
||||
onDownload: suspend (downloadProgress: Pair<Double, Double?>) -> Unit,
|
||||
): File {
|
||||
this.get(app.packageName, app.version)?.let { downloaded ->
|
||||
if (expectedVersion != null) this.get(expectedPackageName, expectedVersion)
|
||||
?.let { downloaded ->
|
||||
return getApkFileForApp(downloaded)
|
||||
}
|
||||
|
||||
|
@ -80,19 +84,23 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
|||
)
|
||||
}
|
||||
}
|
||||
plugin.download(scope, app, stream)
|
||||
plugin.download(scope, data, stream)
|
||||
}
|
||||
}
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect { (downloaded, size) -> onDownload(downloaded.megaBytes to size.megaBytes) }
|
||||
|
||||
if (downloadedBytes.get() < 1) throw Exception("Downloader did not download any files")
|
||||
if (downloadedBytes.get() < 1) error("Downloader did not download anything.")
|
||||
val pkgInfo =
|
||||
pm.getPackageInfo(targetFile.toFile()) ?: error("Downloaded APK file is invalid")
|
||||
if (pkgInfo.packageName != expectedPackageName) error("Downloaded APK has the wrong package name. Expected: $expectedPackageName, Actual: ${pkgInfo.packageName}")
|
||||
if (expectedVersion != null && pkgInfo.versionName != expectedVersion) error("Downloaded APK has the wrong version. Expected: $expectedVersion, Actual: ${pkgInfo.versionName}")
|
||||
|
||||
dao.insert(
|
||||
DownloadedApp(
|
||||
packageName = app.packageName,
|
||||
version = app.version,
|
||||
packageName = pkgInfo.packageName,
|
||||
version = pkgInfo.versionName,
|
||||
directory = relativePath,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -2,15 +2,14 @@ package app.revanced.manager.domain.repository
|
|||
|
||||
import android.app.Application
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import app.revanced.manager.data.room.AppDatabase
|
||||
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.network.downloader.DownloaderPluginState
|
||||
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
||||
import app.revanced.manager.network.downloader.ParceledDownloaderApp
|
||||
import app.revanced.manager.plugin.downloader.App
|
||||
import app.revanced.manager.plugin.downloader.Downloader
|
||||
import app.revanced.manager.network.downloader.ParceledDownloaderData
|
||||
import app.revanced.manager.plugin.downloader.DownloaderBuilder
|
||||
import app.revanced.manager.plugin.downloader.PluginHostApi
|
||||
import app.revanced.manager.util.PM
|
||||
|
@ -67,12 +66,12 @@ class DownloaderPluginRepository(
|
|||
}
|
||||
}
|
||||
|
||||
fun unwrapParceledApp(app: ParceledDownloaderApp): Pair<LoadedDownloaderPlugin, App> {
|
||||
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloaderPlugin, Parcelable> {
|
||||
val plugin =
|
||||
(_pluginStates.value[app.pluginPackageName] as? DownloaderPluginState.Loaded)?.plugin
|
||||
?: throw Exception("Downloader plugin with name ${app.pluginPackageName} is not available")
|
||||
(_pluginStates.value[data.pluginPackageName] as? DownloaderPluginState.Loaded)?.plugin
|
||||
?: throw Exception("Downloader plugin with name ${data.pluginPackageName} is not available")
|
||||
|
||||
return plugin to app.unwrapWith(plugin)
|
||||
return plugin to data.unwrapWith(plugin)
|
||||
}
|
||||
|
||||
private suspend fun loadPlugin(packageName: String): DownloaderPluginState {
|
||||
|
@ -159,7 +158,7 @@ class DownloaderPluginRepository(
|
|||
fun Class<*>.getDownloaderBuilder() =
|
||||
declaredMethods
|
||||
.firstOrNull { it.modifiers.isPublicStatic && it.returnType.isDownloaderBuilder && it.parameterTypes.isEmpty() }
|
||||
?.let { it(null) as DownloaderBuilder<App> }
|
||||
?.let { it(null) as DownloaderBuilder<Parcelable> }
|
||||
?: throw Exception("Could not find a valid downloader implementation in class $canonicalName")
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package app.revanced.manager.network.downloader
|
||||
|
||||
import app.revanced.manager.plugin.downloader.App
|
||||
import android.os.Parcelable
|
||||
import app.revanced.manager.plugin.downloader.DownloadScope
|
||||
import app.revanced.manager.plugin.downloader.GetScope
|
||||
import java.io.OutputStream
|
||||
|
@ -9,7 +9,7 @@ class LoadedDownloaderPlugin(
|
|||
val packageName: String,
|
||||
val name: String,
|
||||
val version: String,
|
||||
val get: suspend GetScope.(packageName: String, version: String?) -> App?,
|
||||
val download: suspend DownloadScope.(app: App, outputStream: OutputStream) -> Unit,
|
||||
val get: suspend GetScope.(packageName: String, version: String?) -> Pair<Parcelable, String?>?,
|
||||
val download: suspend DownloadScope.(data: Parcelable, outputStream: OutputStream) -> Unit,
|
||||
val classLoader: ClassLoader
|
||||
)
|
|
@ -3,43 +3,42 @@ package app.revanced.manager.network.downloader
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import app.revanced.manager.plugin.downloader.App
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
/**
|
||||
* A parceled [App]. Instances of this class can be safely stored in a bundle without needing to set the [ClassLoader].
|
||||
* A container for [Parcelable] data returned from downloaders. Instances of this class can be safely stored in a bundle without needing to set the [ClassLoader].
|
||||
*/
|
||||
class ParceledDownloaderApp private constructor(
|
||||
class ParceledDownloaderData private constructor(
|
||||
val pluginPackageName: String,
|
||||
private val bundle: Bundle
|
||||
) : Parcelable {
|
||||
constructor(plugin: LoadedDownloaderPlugin, app: App) : this(
|
||||
constructor(plugin: LoadedDownloaderPlugin, data: Parcelable) : this(
|
||||
plugin.packageName,
|
||||
createBundle(app)
|
||||
createBundle(data)
|
||||
)
|
||||
|
||||
fun unwrapWith(plugin: LoadedDownloaderPlugin): App {
|
||||
fun unwrapWith(plugin: LoadedDownloaderPlugin): Parcelable {
|
||||
bundle.classLoader = plugin.classLoader
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val className = bundle.getString(CLASS_NAME_KEY)!!
|
||||
val clazz = plugin.classLoader.loadClass(className)
|
||||
|
||||
bundle.getParcelable(APP_KEY, clazz)!! as App
|
||||
} else @Suppress("Deprecation") bundle.getParcelable(APP_KEY)!!
|
||||
bundle.getParcelable(DATA_KEY, clazz)!! as Parcelable
|
||||
} else @Suppress("Deprecation") bundle.getParcelable(DATA_KEY)!!
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CLASS_NAME_KEY = "class"
|
||||
const val APP_KEY = "app"
|
||||
const val DATA_KEY = "data"
|
||||
|
||||
fun createBundle(app: App) = Bundle().apply {
|
||||
putParcelable(APP_KEY, app)
|
||||
fun createBundle(data: Parcelable) = Bundle().apply {
|
||||
putParcelable(DATA_KEY, data)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) putString(
|
||||
CLASS_NAME_KEY,
|
||||
app::class.java.canonicalName
|
||||
data::class.java.canonicalName
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.manager.patcher.worker
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
|
@ -9,9 +10,11 @@ import android.content.Intent
|
|||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
|
@ -30,10 +33,9 @@ import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
|||
import app.revanced.manager.patcher.logger.Logger
|
||||
import app.revanced.manager.patcher.runtime.CoroutineRuntime
|
||||
import app.revanced.manager.patcher.runtime.ProcessRuntime
|
||||
import app.revanced.manager.plugin.downloader.ActivityLaunchPermit
|
||||
import app.revanced.manager.plugin.downloader.GetScope
|
||||
import app.revanced.manager.plugin.downloader.PluginHostApi
|
||||
import app.revanced.manager.plugin.downloader.UserInteractionException
|
||||
import app.revanced.manager.plugin.downloader.App as DownloaderApp
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.ui.model.State
|
||||
import app.revanced.manager.util.Options
|
||||
|
@ -49,6 +51,7 @@ import java.io.File
|
|||
|
||||
typealias ProgressEventHandler = (name: String?, state: State?, message: String?) -> Unit
|
||||
|
||||
@OptIn(PluginHostApi::class)
|
||||
class PatcherWorker(
|
||||
context: Context,
|
||||
parameters: WorkerParameters
|
||||
|
@ -71,7 +74,7 @@ class PatcherWorker(
|
|||
val logger: Logger,
|
||||
val downloadProgress: MutableStateFlow<Pair<Double, Double?>?>,
|
||||
val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
|
||||
val handleUserInteractionRequest: suspend () -> ActivityLaunchPermit?,
|
||||
val handleStartActivityRequest: suspend (Intent) -> ActivityResult,
|
||||
val setInputFile: (File) -> Unit,
|
||||
val onProgress: ProgressEventHandler
|
||||
) {
|
||||
|
@ -150,10 +153,12 @@ class PatcherWorker(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun download(plugin: LoadedDownloaderPlugin, app: DownloaderApp) =
|
||||
suspend fun download(plugin: LoadedDownloaderPlugin, data: Parcelable) =
|
||||
downloadedAppRepository.download(
|
||||
plugin,
|
||||
app,
|
||||
data,
|
||||
args.packageName,
|
||||
args.input.version,
|
||||
onDownload = args.downloadProgress::emit
|
||||
).also {
|
||||
args.setInputFile(it)
|
||||
|
@ -162,16 +167,24 @@ class PatcherWorker(
|
|||
|
||||
val inputFile = when (val selectedApp = args.input) {
|
||||
is SelectedApp.Download -> {
|
||||
val (plugin, app) = downloaderPluginRepository.unwrapParceledApp(selectedApp.app)
|
||||
val (plugin, data) = downloaderPluginRepository.unwrapParceledData(selectedApp.app)
|
||||
|
||||
download(plugin, app)
|
||||
download(plugin, data)
|
||||
}
|
||||
|
||||
is SelectedApp.Search -> {
|
||||
val getScope = object : GetScope {
|
||||
override suspend fun requestUserInteraction() =
|
||||
args.handleUserInteractionRequest()
|
||||
?: throw UserInteractionException.RequestDenied()
|
||||
override suspend fun requestStartActivity(intent: Intent): Intent? {
|
||||
val result = args.handleStartActivityRequest(intent)
|
||||
return when (result.resultCode) {
|
||||
Activity.RESULT_OK -> result.data
|
||||
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()
|
||||
else -> throw UserInteractionException.Activity.NotCompleted(
|
||||
result.resultCode,
|
||||
result.data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloaderPluginRepository.loadedPluginsFlow.first()
|
||||
|
@ -182,12 +195,12 @@ class PatcherWorker(
|
|||
selectedApp.packageName,
|
||||
selectedApp.version
|
||||
)
|
||||
?.takeIf { selectedApp.version == null || it.version == selectedApp.version }
|
||||
?.takeIf { (_, version) -> selectedApp.version == null || version == selectedApp.version }
|
||||
} catch (e: UserInteractionException.Activity.NotCompleted) {
|
||||
throw e
|
||||
} catch (_: UserInteractionException) {
|
||||
null
|
||||
}?.let { app -> download(plugin, app) }
|
||||
}?.let { (data, _) -> download(plugin, data) }
|
||||
} ?: throw Exception("App is not available.")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package app.revanced.manager.ui.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import app.revanced.manager.network.downloader.ParceledDownloaderApp
|
||||
import app.revanced.manager.network.downloader.ParceledDownloaderData
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
|
||||
|
@ -13,7 +13,7 @@ sealed interface SelectedApp : Parcelable {
|
|||
data class Download(
|
||||
override val packageName: String,
|
||||
override val version: String,
|
||||
val app: ParceledDownloaderApp
|
||||
val app: ParceledDownloaderData
|
||||
) : SelectedApp
|
||||
|
||||
@Parcelize
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
|
@ -32,7 +31,7 @@ import app.revanced.manager.domain.worker.WorkerRepository
|
|||
import app.revanced.manager.patcher.logger.LogLevel
|
||||
import app.revanced.manager.patcher.logger.Logger
|
||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||
import app.revanced.manager.plugin.downloader.ActivityLaunchPermit
|
||||
import app.revanced.manager.plugin.downloader.PluginHostApi
|
||||
import app.revanced.manager.plugin.downloader.UserInteractionException
|
||||
import app.revanced.manager.service.InstallService
|
||||
import app.revanced.manager.ui.destination.Destination
|
||||
|
@ -64,6 +63,7 @@ import java.time.Duration
|
|||
import java.util.UUID
|
||||
|
||||
@Stable
|
||||
@OptIn(PluginHostApi::class)
|
||||
class PatcherViewModel(
|
||||
private val input: Destination.Patcher
|
||||
) : ViewModel(), KoinComponent {
|
||||
|
@ -81,9 +81,8 @@ class PatcherViewModel(
|
|||
var isInstalling by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private var currentInteractionRequest: CompletableDeferred<ActivityLaunchPermit?>? by mutableStateOf(
|
||||
null
|
||||
)
|
||||
// TODO: rename these
|
||||
private var currentInteractionRequest: CompletableDeferred<Boolean>? by mutableStateOf(null)
|
||||
val activeInteractionRequest by derivedStateOf { currentInteractionRequest != null }
|
||||
private var launchedActivity: CompletableDeferred<ActivityResult>? = null
|
||||
private val launchActivityChannel = Channel<Intent>()
|
||||
|
@ -130,13 +129,29 @@ class PatcherViewModel(
|
|||
downloadProgress,
|
||||
patchesProgress,
|
||||
setInputFile = { inputFile = it },
|
||||
handleUserInteractionRequest = {
|
||||
handleStartActivityRequest = { intent ->
|
||||
withContext(Dispatchers.Main) {
|
||||
if (activeInteractionRequest) throw Exception("Another request is already pending.")
|
||||
if (currentInteractionRequest != null) throw Exception("Another request is already pending.")
|
||||
try {
|
||||
val job = CompletableDeferred<ActivityLaunchPermit?>()
|
||||
currentInteractionRequest = job
|
||||
job.await()
|
||||
// Wait for the dialog interaction.
|
||||
val accepted = with(CompletableDeferred<Boolean>()) {
|
||||
currentInteractionRequest = this
|
||||
|
||||
println(activeInteractionRequest)
|
||||
await()
|
||||
}
|
||||
if (!accepted) throw UserInteractionException.RequestDenied()
|
||||
|
||||
// Launch the activity and wait for the result.
|
||||
try {
|
||||
with(CompletableDeferred<ActivityResult>()) {
|
||||
launchedActivity = this
|
||||
launchActivityChannel.send(intent)
|
||||
await()
|
||||
}
|
||||
} finally {
|
||||
launchedActivity = null
|
||||
}
|
||||
} finally {
|
||||
currentInteractionRequest = null
|
||||
}
|
||||
|
@ -232,10 +247,12 @@ class PatcherViewModel(
|
|||
}
|
||||
|
||||
fun rejectInteraction() {
|
||||
currentInteractionRequest?.complete(null)
|
||||
currentInteractionRequest?.complete(false)
|
||||
}
|
||||
|
||||
fun allowInteraction() {
|
||||
currentInteractionRequest?.complete(true)
|
||||
/*
|
||||
currentInteractionRequest?.complete(ActivityLaunchPermit { intent ->
|
||||
withContext(Dispatchers.Main) {
|
||||
if (launchedActivity != null) throw Exception("An activity has already been launched.")
|
||||
|
@ -257,7 +274,7 @@ class PatcherViewModel(
|
|||
launchedActivity = null
|
||||
}
|
||||
}
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
fun handleActivityResult(result: ActivityResult) {
|
||||
|
|
|
@ -1,26 +1,3 @@
|
|||
public abstract interface class app/revanced/manager/plugin/downloader/ActivityLaunchPermit {
|
||||
public abstract fun launch (Landroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public class app/revanced/manager/plugin/downloader/App : android/os/Parcelable {
|
||||
public static final field CREATOR Landroid/os/Parcelable$Creator;
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public fun describeContents ()I
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public fun getPackageName ()Ljava/lang/String;
|
||||
public fun getVersion ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun writeToParcel (Landroid/os/Parcel;I)V
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/App$Creator : android/os/Parcelable$Creator {
|
||||
public fun <init> ()V
|
||||
public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/App;
|
||||
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
|
||||
public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/App;
|
||||
public synthetic fun newArray (I)[Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/ConstantsKt {
|
||||
public static final field PLUGIN_HOST_PERMISSION Ljava/lang/String;
|
||||
}
|
||||
|
@ -35,12 +12,6 @@ public final class app/revanced/manager/plugin/downloader/Downloader {
|
|||
public final class app/revanced/manager/plugin/downloader/DownloaderBuilder {
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/DownloaderContext {
|
||||
public fun <init> (Landroid/content/Context;Ljava/lang/String;)V
|
||||
public final fun getAndroidContext ()Landroid/content/Context;
|
||||
public final fun getPluginHostPackageName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/DownloaderKt {
|
||||
public static final fun downloader (Lkotlin/jvm/functions/Function1;)Lapp/revanced/manager/plugin/downloader/DownloaderBuilder;
|
||||
}
|
||||
|
@ -50,6 +21,7 @@ public final class app/revanced/manager/plugin/downloader/DownloaderScope {
|
|||
public final fun get (Lkotlin/jvm/functions/Function4;)V
|
||||
public final fun getHostPackageName ()Ljava/lang/String;
|
||||
public final fun getPluginPackageName ()Ljava/lang/String;
|
||||
public final fun withBoundService (Landroid/content/Intent;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/ExtensionsKt {
|
||||
|
@ -57,7 +29,31 @@ public final class app/revanced/manager/plugin/downloader/ExtensionsKt {
|
|||
}
|
||||
|
||||
public abstract interface class app/revanced/manager/plugin/downloader/GetScope {
|
||||
public abstract fun requestUserInteraction (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun requestStartActivity (Landroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/Package : android/os/Parcelable {
|
||||
public static final field CREATOR Landroid/os/Parcelable$Creator;
|
||||
public fun <init> (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/Package;
|
||||
public static synthetic fun copy$default (Lapp/revanced/manager/plugin/downloader/Package;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lapp/revanced/manager/plugin/downloader/Package;
|
||||
public final fun describeContents ()I
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getVersion ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public final fun writeToParcel (Landroid/os/Parcel;I)V
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/Package$Creator : android/os/Parcelable$Creator {
|
||||
public fun <init> ()V
|
||||
public final fun createFromParcel (Landroid/os/Parcel;)Lapp/revanced/manager/plugin/downloader/Package;
|
||||
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
|
||||
public final fun newArray (I)[Lapp/revanced/manager/plugin/downloader/Package;
|
||||
public synthetic fun newArray (I)[Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface annotation class app/revanced/manager/plugin/downloader/PluginHostApi : java/lang/annotation/Annotation {
|
||||
|
@ -72,16 +68,13 @@ public abstract class app/revanced/manager/plugin/downloader/UserInteractionExce
|
|||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$Cancelled : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$NotCompleted : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
|
||||
public fun <init> (ILandroid/content/Intent;)V
|
||||
public final fun getIntent ()Landroid/content/Intent;
|
||||
public final fun getResultCode ()I
|
||||
}
|
||||
|
||||
public final class app/revanced/manager/plugin/downloader/UserInteractionException$RequestDenied : app/revanced/manager/plugin/downloader/UserInteractionException {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package app.revanced.manager.plugin.downloader
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Objects
|
||||
|
||||
@Parcelize
|
||||
open class App(open val packageName: String, open val version: String) : Parcelable {
|
||||
override fun hashCode() = Objects.hash(packageName, version)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is App) return false
|
||||
|
||||
return other.packageName == packageName && other.version == version
|
||||
}
|
||||
}
|
|
@ -1,9 +1,16 @@
|
|||
package app.revanced.manager.plugin.downloader
|
||||
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@RequiresOptIn(
|
||||
level = RequiresOptIn.Level.ERROR,
|
||||
|
@ -12,43 +19,38 @@ import java.io.OutputStream
|
|||
annotation class PluginHostApi
|
||||
|
||||
interface GetScope {
|
||||
suspend fun requestUserInteraction(): ActivityLaunchPermit
|
||||
}
|
||||
|
||||
fun interface ActivityLaunchPermit {
|
||||
suspend fun launch(intent: Intent): Intent?
|
||||
}
|
||||
|
||||
interface DownloadScope {
|
||||
suspend fun reportSize(size: Long)
|
||||
suspend fun requestStartActivity(intent: Intent): Intent?
|
||||
}
|
||||
|
||||
typealias Size = Long
|
||||
typealias DownloadResult = Pair<InputStream, Size?>
|
||||
|
||||
class DownloaderScope<A : App> internal constructor(
|
||||
typealias Version = String
|
||||
typealias GetResult<T> = Pair<T, Version?>
|
||||
|
||||
class DownloaderScope<T : Parcelable> internal constructor(
|
||||
/**
|
||||
* The package name of ReVanced Manager.
|
||||
*/
|
||||
val hostPackageName: String,
|
||||
internal val context: Context
|
||||
) {
|
||||
internal var download: (suspend DownloadScope.(A, OutputStream) -> Unit)? = null
|
||||
internal var get: (suspend GetScope.(String, String?) -> A?)? = null
|
||||
internal var download: (suspend DownloadScope.(T, OutputStream) -> Unit)? = null
|
||||
internal var get: (suspend GetScope.(String, String?) -> GetResult<T>?)? = null
|
||||
|
||||
/**
|
||||
* The package name of the plugin.
|
||||
*/
|
||||
val pluginPackageName: String get() = context.packageName
|
||||
|
||||
fun get(block: suspend GetScope.(packageName: String, version: String?) -> A?) {
|
||||
fun get(block: suspend GetScope.(packageName: String, version: String?) -> GetResult<T>?) {
|
||||
get = block
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the download function for this plugin.
|
||||
*/
|
||||
fun download(block: suspend (app: A) -> DownloadResult) {
|
||||
fun download(block: suspend (data: T) -> DownloadResult) {
|
||||
download = { app, outputStream ->
|
||||
val (inputStream, size) = block(app)
|
||||
|
||||
|
@ -58,12 +60,34 @@ class DownloaderScope<A : App> internal constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <R : Any?> withBoundService(intent: Intent, block: suspend (IBinder) -> R): R {
|
||||
var onBind: ((IBinder) -> Unit)? = null
|
||||
val serviceConn = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) =
|
||||
onBind!!(service!!)
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {}
|
||||
}
|
||||
|
||||
class DownloaderBuilder<A : App> internal constructor(private val block: DownloaderScope<A>.() -> Unit) {
|
||||
return try {
|
||||
// TODO: add a timeout
|
||||
block(suspendCoroutine { continuation ->
|
||||
onBind = continuation::resume
|
||||
context.bindService(intent, serviceConn, Context.BIND_AUTO_CREATE)
|
||||
})
|
||||
} finally {
|
||||
onBind = null
|
||||
// TODO: should we stop it?
|
||||
context.unbindService(serviceConn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DownloaderBuilder<T : Parcelable> internal constructor(private val block: DownloaderScope<T>.() -> Unit) {
|
||||
@PluginHostApi
|
||||
fun build(hostPackageName: String, context: Context) =
|
||||
with(DownloaderScope<A>(hostPackageName, context)) {
|
||||
with(DownloaderScope<T>(hostPackageName, context)) {
|
||||
block()
|
||||
|
||||
Downloader(
|
||||
|
@ -73,19 +97,20 @@ class DownloaderBuilder<A : App> internal constructor(private val block: Downloa
|
|||
}
|
||||
}
|
||||
|
||||
class Downloader<A : App> internal constructor(
|
||||
@property:PluginHostApi val get: suspend GetScope.(packageName: String, version: String?) -> A?,
|
||||
@property:PluginHostApi val download: suspend DownloadScope.(app: A, outputStream: OutputStream) -> Unit
|
||||
class Downloader<T : Parcelable> internal constructor(
|
||||
@property:PluginHostApi val get: suspend GetScope.(packageName: String, version: String?) -> GetResult<T>?,
|
||||
@property:PluginHostApi val download: suspend DownloadScope.(data: T, outputStream: OutputStream) -> Unit
|
||||
)
|
||||
|
||||
fun <A : App> downloader(block: DownloaderScope<A>.() -> Unit) = DownloaderBuilder(block)
|
||||
fun <T : Parcelable> downloader(block: DownloaderScope<T>.() -> Unit) = DownloaderBuilder(block)
|
||||
|
||||
sealed class UserInteractionException(message: String) : Exception(message) {
|
||||
class RequestDenied : UserInteractionException("Request was denied")
|
||||
class RequestDenied @PluginHostApi constructor() :
|
||||
UserInteractionException("Request was denied")
|
||||
|
||||
sealed class Activity(message: String) : UserInteractionException(message) {
|
||||
class Cancelled : Activity("Interaction was cancelled")
|
||||
class NotCompleted(val resultCode: Int, val intent: Intent?) :
|
||||
class Cancelled @PluginHostApi constructor() : Activity("Interaction was cancelled")
|
||||
class NotCompleted @PluginHostApi constructor(val resultCode: Int, val intent: Intent?) :
|
||||
Activity("Unexpected activity result code: $resultCode")
|
||||
}
|
||||
}
|
|
@ -1,8 +1,29 @@
|
|||
package app.revanced.manager.plugin.downloader
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import java.io.OutputStream
|
||||
|
||||
interface DownloadScope {
|
||||
suspend fun reportSize(size: Long)
|
||||
}
|
||||
|
||||
// OutputStream-based version of download
|
||||
fun <A : App> DownloaderScope<A>.download(block: suspend DownloadScope.(A, OutputStream) -> Unit) {
|
||||
fun <T : Parcelable> DownloaderScope<T>.download(block: suspend DownloadScope.(T, OutputStream) -> Unit) {
|
||||
download = block
|
||||
}
|
||||
|
||||
suspend inline fun <reified ACTIVITY : Activity> GetScope.requestStartActivity(packageName: String) =
|
||||
requestStartActivity(
|
||||
Intent().apply { setClassName(packageName, ACTIVITY::class.qualifiedName!!) }
|
||||
)
|
||||
|
||||
suspend inline fun <reified SERVICE : Service, R : Any?> DownloaderScope<*>.withBoundService(
|
||||
packageName: String,
|
||||
noinline block: suspend (IBinder) -> R
|
||||
) = withBoundService(
|
||||
Intent().apply { setClassName(packageName, SERVICE::class.qualifiedName!!) }, block
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
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
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<meta-data
|
||||
android:name="app.revanced.manager.plugin.downloader.class"
|
||||
android:value="app.revanced.manager.plugin.downloader.example.ExamplePluginsKt" />
|
||||
android:value="app.revanced.manager.plugin.downloader.example.ExamplePluginKt" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -3,25 +3,21 @@
|
|||
package app.revanced.manager.plugin.downloader.example
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import app.revanced.manager.plugin.downloader.App
|
||||
import android.os.Parcelable
|
||||
import app.revanced.manager.plugin.downloader.download
|
||||
import app.revanced.manager.plugin.downloader.downloader
|
||||
import app.revanced.manager.plugin.downloader.requestStartActivity
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.fileSize
|
||||
import kotlin.io.path.inputStream
|
||||
|
||||
// TODO: document and update API, change dispatcher, finish UI
|
||||
// TODO: document and update API (update requestUserInteraction, add bound service function), change dispatcher, finish UI
|
||||
|
||||
@Parcelize
|
||||
class InstalledApp(
|
||||
override val packageName: String,
|
||||
override val version: String,
|
||||
internal val apkPath: String
|
||||
) : App(packageName, version)
|
||||
class InstalledApp(val path: String) : Parcelable
|
||||
|
||||
private val application by lazy {
|
||||
// Don't do this in a real plugin.
|
||||
|
@ -39,27 +35,19 @@ val installedAppDownloader = downloader<InstalledApp> {
|
|||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
return@get null
|
||||
}
|
||||
if (version != null && packageInfo.versionName != version) return@get null
|
||||
|
||||
requestUserInteraction().launch(Intent().apply {
|
||||
setClassName(
|
||||
pluginPackageName,
|
||||
InteractionActivity::class.java.canonicalName!!
|
||||
)
|
||||
})
|
||||
requestStartActivity<InteractionActivity>(pluginPackageName)
|
||||
|
||||
InstalledApp(
|
||||
packageName,
|
||||
packageInfo.versionName,
|
||||
packageInfo.applicationInfo.sourceDir
|
||||
).takeIf { version == null || it.version == version }
|
||||
InstalledApp(packageInfo.applicationInfo.sourceDir) to packageInfo.versionName
|
||||
}
|
||||
|
||||
download { app ->
|
||||
with(Path(app.apkPath)) { inputStream() to fileSize() }
|
||||
with(Path(app.path)) { inputStream() to fileSize() }
|
||||
}
|
||||
|
||||
download { app, outputStream ->
|
||||
val path = Path(app.apkPath)
|
||||
val path = Path(app.path)
|
||||
reportSize(path.fileSize())
|
||||
Files.copy(path, outputStream)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue