Skip to content

Commit

Permalink
fix: pass worker inputs without serialization (#44)
Browse files Browse the repository at this point in the history
Because androidx.work.Data sucks and causes our app to crash.
  • Loading branch information
Axelen123 committed Jun 27, 2023
1 parent bcdcdbe commit 59a99c8
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 83 deletions.
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/app/revanced/manager/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -14,4 +15,5 @@ val repositoryModule = module {
singleOf(::SourcePersistenceRepository)
singleOf(::PatchSelectionRepository)
singleOf(::SourceRepository)
singleOf(::WorkerRepository)
}
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
)

Expand All @@ -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
Expand Down Expand Up @@ -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 {
/**
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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 = {
Expand All @@ -77,7 +76,7 @@ fun InstallerScreen(
.verticalScroll(rememberScrollState())
.fillMaxSize()
) {
patcherState.steps.forEach {
steps.forEach {
InstallStep(it)
}
Spacer(modifier = Modifier.weight(1f))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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()

val initialState = PatcherState(
succeeded = null,
steps = PatcherProgressManager.generateSteps(
app,
selectedPatches.flatMap { (_, selected) -> selected }
private val _progress = MutableStateFlow(PatcherProgressManager.generateSteps(
app,
selectedPatches.flatMap { (_, selected) -> selected }
).toImmutableList())
val progress = _progress.asStateFlow()

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
}

val patcherState =
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() {
Expand All @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -165,6 +159,4 @@ class InstallerViewModel(
isInstalling = false
}
}

data class PatcherState(val succeeded: Boolean?, val steps: List<Step>)
}

0 comments on commit 59a99c8

Please sign in to comment.