mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
fix: pass worker inputs without serialization (#44)
Because androidx.work.Data sucks and causes our app to crash.
This commit is contained in:
parent
1eac42dab8
commit
4302ea8832
@ -91,7 +91,6 @@ dependencies {
|
||||
// KotlinX
|
||||
val serializationVersion = "1.5.1"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:$serializationVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
||||
|
||||
// Room
|
||||
|
@ -5,6 +5,7 @@ import app.revanced.manager.domain.repository.ReVancedRepository
|
||||
import app.revanced.manager.network.api.ManagerAPI
|
||||
import app.revanced.manager.domain.repository.SourcePersistenceRepository
|
||||
import app.revanced.manager.domain.repository.SourceRepository
|
||||
import app.revanced.manager.domain.worker.WorkerRepository
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
@ -14,4 +15,5 @@ val repositoryModule = module {
|
||||
singleOf(::SourcePersistenceRepository)
|
||||
singleOf(::PatchSelectionRepository)
|
||||
singleOf(::SourceRepository)
|
||||
singleOf(::WorkerRepository)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.manager.domain.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
|
||||
abstract class Worker<ARGS>(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters)
|
@ -0,0 +1,36 @@
|
||||
package app.revanced.manager.domain.worker
|
||||
|
||||
import android.app.Application
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import java.util.UUID
|
||||
|
||||
class WorkerRepository(app: Application) {
|
||||
val workManager = WorkManager.getInstance(app)
|
||||
|
||||
/**
|
||||
* The standard WorkManager communication APIs use [androidx.work.Data], which has too many limitations.
|
||||
* We can get around those limits by passing inputs using global variables instead.
|
||||
*/
|
||||
val workerInputs = mutableMapOf<UUID, Any>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <A : Any, W : Worker<A>> claimInput(worker: W): A {
|
||||
val data = workerInputs[worker.id] ?: throw IllegalStateException("Worker was not launched via WorkerRepository")
|
||||
workerInputs.remove(worker.id)
|
||||
|
||||
return data as A
|
||||
}
|
||||
|
||||
inline fun <reified W : Worker<A>, A : Any> launchExpedited(name: String, input: A): UUID {
|
||||
val request =
|
||||
OneTimeWorkRequest.Builder(W::class.java) // create Worker
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
workerInputs[request.id] = input
|
||||
workManager.enqueueUniqueWork(name, ExistingWorkPolicy.REPLACE, request)
|
||||
return request.id
|
||||
}
|
||||
}
|
@ -3,10 +3,9 @@ package app.revanced.manager.patcher.worker
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.util.serialize
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
sealed class Progress {
|
||||
object Unpacking : Progress()
|
||||
@ -18,23 +17,19 @@ sealed class Progress {
|
||||
object Saving : Progress()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class State {
|
||||
WAITING, COMPLETED, FAILED
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class SubStep(
|
||||
val name: String,
|
||||
val state: State = State.WAITING,
|
||||
@SerialName("msg")
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Step(
|
||||
@StringRes val name: Int,
|
||||
val substeps: List<SubStep>,
|
||||
val substeps: ImmutableList<SubStep>,
|
||||
val state: State = State.WAITING
|
||||
)
|
||||
|
||||
@ -58,7 +53,7 @@ class PatcherProgressManager(context: Context, selectedPatches: List<String>) {
|
||||
|
||||
Step(step.name, step.substeps.mapIndexed { index, subStep ->
|
||||
if (index != key.substep) subStep else SubStep(subStep.name, state, message)
|
||||
}, newStepState)
|
||||
}.toImmutableList(), newStepState)
|
||||
}
|
||||
|
||||
val isFinal = isLastSubStep && key.step == steps.lastIndex
|
||||
@ -95,7 +90,7 @@ class PatcherProgressManager(context: Context, selectedPatches: List<String>) {
|
||||
|
||||
fun success() = updateCurrent(State.COMPLETED)
|
||||
|
||||
fun workData() = steps.serialize()
|
||||
fun getProgress(): List<Step> = steps
|
||||
|
||||
companion object {
|
||||
/**
|
||||
@ -110,7 +105,7 @@ class PatcherProgressManager(context: Context, selectedPatches: List<String>) {
|
||||
|
||||
private fun generatePatchesStep(selectedPatches: List<String>) = Step(
|
||||
R.string.patcher_step_group_patching,
|
||||
selectedPatches.map { SubStep(it) }
|
||||
selectedPatches.map { SubStep(it) }.toImmutableList()
|
||||
)
|
||||
|
||||
fun generateSteps(context: Context, selectedPatches: List<String>) = mutableListOf(
|
||||
|
@ -11,36 +11,39 @@ import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.repository.SourceRepository
|
||||
import app.revanced.manager.domain.worker.Worker
|
||||
import app.revanced.manager.domain.worker.WorkerRepository
|
||||
import app.revanced.manager.patcher.Session
|
||||
import app.revanced.manager.patcher.aapt.Aapt
|
||||
import app.revanced.manager.util.PatchesSelection
|
||||
import app.revanced.manager.util.deserialize
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
CoroutineWorker(context, parameters),
|
||||
Worker<PatcherWorker.Args>(context, parameters),
|
||||
KoinComponent {
|
||||
private val sourceRepository: SourceRepository by inject()
|
||||
private val workerRepository: WorkerRepository by inject()
|
||||
|
||||
@Serializable
|
||||
data class Args(
|
||||
val input: String,
|
||||
val output: String,
|
||||
val selectedPatches: PatchesSelection,
|
||||
val packageName: String,
|
||||
val packageVersion: String
|
||||
val packageVersion: String,
|
||||
val progress: MutableStateFlow<ImmutableList<Step>>
|
||||
)
|
||||
|
||||
companion object {
|
||||
@ -75,7 +78,7 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val args = inputData.deserialize<Args>()!!
|
||||
val args = workerRepository.claimInput(this)
|
||||
|
||||
try {
|
||||
// This does not always show up for some reason.
|
||||
@ -113,9 +116,11 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
val progressManager =
|
||||
PatcherProgressManager(applicationContext, args.selectedPatches.flatMap { it.value })
|
||||
|
||||
suspend fun updateProgress(progress: Progress) {
|
||||
progressManager.handle(progress)
|
||||
setProgress(progressManager.workData())
|
||||
val progressFlow = args.progress
|
||||
|
||||
fun updateProgress(progress: Progress?) {
|
||||
progress?.let { progressManager.handle(it) }
|
||||
progressFlow.value = progressManager.getProgress().toImmutableList()
|
||||
}
|
||||
|
||||
return try {
|
||||
@ -143,11 +148,13 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
|
||||
Log.i(tag, "Patching succeeded".logFmt())
|
||||
progressManager.success()
|
||||
Result.success(progressManager.workData())
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Got exception while patching".logFmt(), e)
|
||||
progressManager.failure(e)
|
||||
Result.failure(progressManager.workData())
|
||||
Result.failure()
|
||||
} finally {
|
||||
updateProgress(null)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,6 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material3.*
|
||||
@ -33,6 +31,7 @@ import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.worker.Step
|
||||
import app.revanced.manager.patcher.worker.State
|
||||
@ -41,7 +40,6 @@ import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.ArrowButton
|
||||
import app.revanced.manager.ui.viewmodel.InstallerViewModel
|
||||
import app.revanced.manager.util.APK_MIMETYPE
|
||||
import kotlin.math.exp
|
||||
import kotlin.math.floor
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -52,8 +50,9 @@ fun InstallerScreen(
|
||||
) {
|
||||
val exportApkLauncher =
|
||||
rememberLauncherForActivityResult(CreateDocument(APK_MIMETYPE), vm::export)
|
||||
val patcherState by vm.patcherState.observeAsState(vm.initialState)
|
||||
val canInstall by remember { derivedStateOf { patcherState.succeeded == true && (vm.installedPackageName != null || !vm.isInstalling) } }
|
||||
val patcherState by vm.patcherState.observeAsState(null)
|
||||
val steps by vm.progress.collectAsStateWithLifecycle()
|
||||
val canInstall by remember { derivedStateOf { patcherState == true && (vm.installedPackageName != null || !vm.isInstalling) } }
|
||||
|
||||
AppScaffold(
|
||||
topBar = {
|
||||
@ -77,7 +76,7 @@ fun InstallerScreen(
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxSize()
|
||||
) {
|
||||
patcherState.steps.forEach {
|
||||
steps.forEach {
|
||||
InstallStep(it)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
@ -18,18 +18,19 @@ import androidx.lifecycle.map
|
||||
import androidx.work.*
|
||||
import app.revanced.manager.domain.manager.KeystoreManager
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.worker.WorkerRepository
|
||||
import app.revanced.manager.patcher.worker.PatcherProgressManager
|
||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||
import app.revanced.manager.patcher.worker.Step
|
||||
import app.revanced.manager.service.InstallService
|
||||
import app.revanced.manager.service.UninstallService
|
||||
import app.revanced.manager.util.AppInfo
|
||||
import app.revanced.manager.util.PM
|
||||
import app.revanced.manager.util.PatchesSelection
|
||||
import app.revanced.manager.util.deserialize
|
||||
import app.revanced.manager.util.serialize
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.manager.util.toast
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
@ -43,6 +44,7 @@ class InstallerViewModel(
|
||||
private val keystoreManager: KeystoreManager by inject()
|
||||
private val app: Application by inject()
|
||||
private val pm: PM by inject()
|
||||
private val workerRepository: WorkerRepository by inject()
|
||||
|
||||
val packageName: String = input.packageName
|
||||
private val outputFile = File(app.cacheDir, "output.apk")
|
||||
@ -57,38 +59,31 @@ class InstallerViewModel(
|
||||
|
||||
private val workManager = WorkManager.getInstance(app)
|
||||
|
||||
private val patcherWorker =
|
||||
OneTimeWorkRequest.Builder(PatcherWorker::class.java) // create Worker
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).setInputData(
|
||||
PatcherWorker.Args(
|
||||
input.path!!.absolutePath,
|
||||
outputFile.path,
|
||||
selectedPatches,
|
||||
input.packageName,
|
||||
input.packageInfo!!.versionName,
|
||||
).serialize()
|
||||
).build()
|
||||
private val _progress = MutableStateFlow(PatcherProgressManager.generateSteps(
|
||||
app,
|
||||
selectedPatches.flatMap { (_, selected) -> selected }
|
||||
).toImmutableList())
|
||||
val progress = _progress.asStateFlow()
|
||||
|
||||
val initialState = PatcherState(
|
||||
succeeded = null,
|
||||
steps = PatcherProgressManager.generateSteps(
|
||||
app,
|
||||
selectedPatches.flatMap { (_, selected) -> selected }
|
||||
private val patcherWorkerId =
|
||||
workerRepository.launchExpedited<PatcherWorker, PatcherWorker.Args>(
|
||||
"patching", PatcherWorker.Args(
|
||||
input.path!!.absolutePath,
|
||||
outputFile.path,
|
||||
selectedPatches,
|
||||
input.packageName,
|
||||
input.packageInfo!!.versionName,
|
||||
_progress
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val patcherState =
|
||||
workManager.getWorkInfoByIdLiveData(patcherWorker.id).map { workInfo: WorkInfo ->
|
||||
var status: Boolean? = null
|
||||
val steps = when (workInfo.state) {
|
||||
WorkInfo.State.RUNNING -> workInfo.progress
|
||||
WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED -> workInfo.outputData.also {
|
||||
status = workInfo.state == WorkInfo.State.SUCCEEDED
|
||||
}
|
||||
|
||||
workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo ->
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED -> true
|
||||
WorkInfo.State.FAILED -> false
|
||||
else -> null
|
||||
}?.deserialize<List<Step>>()
|
||||
|
||||
PatcherState(status, steps ?: initialState.steps)
|
||||
}
|
||||
}
|
||||
|
||||
private val installBroadcastReceiver = object : BroadcastReceiver() {
|
||||
@ -114,7 +109,6 @@ class InstallerViewModel(
|
||||
}
|
||||
|
||||
init {
|
||||
workManager.enqueueUniqueWork("patching", ExistingWorkPolicy.KEEP, patcherWorker)
|
||||
app.registerReceiver(installBroadcastReceiver, IntentFilter().apply {
|
||||
addAction(InstallService.APP_INSTALL_ACTION)
|
||||
addAction(UninstallService.APP_UNINSTALL_ACTION)
|
||||
@ -124,7 +118,7 @@ class InstallerViewModel(
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
app.unregisterReceiver(installBroadcastReceiver)
|
||||
workManager.cancelWorkById(patcherWorker.id)
|
||||
workManager.cancelWorkById(patcherWorkerId)
|
||||
|
||||
outputFile.delete()
|
||||
signedFile.delete()
|
||||
@ -165,6 +159,4 @@ class InstallerViewModel(
|
||||
isInstalling = false
|
||||
}
|
||||
}
|
||||
|
||||
data class PatcherState(val succeeded: Boolean?, val steps: List<Step>)
|
||||
}
|
@ -12,8 +12,6 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.work.Data
|
||||
import androidx.work.workDataOf
|
||||
import io.ktor.http.Url
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -21,10 +19,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.cbor.Cbor
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
|
||||
typealias PatchesSelection = Map<Int, List<String>>
|
||||
|
||||
@ -98,14 +92,4 @@ inline fun <T, reified R, C> Flow<Iterable<T>>.flatMapLatestAndCombine(
|
||||
combine(iterable.map(transformer)) {
|
||||
combiner(it)
|
||||
}
|
||||
}
|
||||
|
||||
const val workDataKey = "payload"
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
inline fun <reified T> T.serialize(): Data =
|
||||
workDataOf(workDataKey to Cbor.Default.encodeToByteArray(this))
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
inline fun <reified T> Data.deserialize(): T? =
|
||||
getByteArray(workDataKey)?.let { Cbor.Default.decodeFromByteArray(it) }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user