Skip to content

Commit

Permalink
fix: process death resilience and account for android 11 bug (#2355)
Browse files Browse the repository at this point in the history
  • Loading branch information
Axelen123 authored Dec 22, 2024
1 parent 9916e4d commit 49f75f9
Show file tree
Hide file tree
Showing 20 changed files with 377 additions and 171 deletions.
35 changes: 35 additions & 0 deletions app/src/main/java/app/revanced/manager/ManagerApplication.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package app.revanced.manager

import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.util.Log
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.di.*
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.util.tag
import kotlinx.coroutines.Dispatchers
import coil.Coil
import coil.ImageLoader
Expand All @@ -25,6 +30,7 @@ class ManagerApplication : Application() {
private val prefs: PreferencesManager by inject()
private val patchBundleRepository: PatchBundleRepository by inject()
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
private val fs: Filesystem by inject()

override fun onCreate() {
super.onCreate()
Expand Down Expand Up @@ -71,5 +77,34 @@ class ManagerApplication : Application() {
updateCheck()
}
}
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var firstActivityCreated = false

override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (firstActivityCreated) return
firstActivityCreated = true

// We do not want to call onFreshProcessStart() if there is state to restore.
// This can happen on system-initiated process death.
if (savedInstanceState == null) {
Log.d(tag, "Fresh process created")
onFreshProcessStart()
} else Log.d(tag, "System-initiated process death detected")
}

override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}

private fun onFreshProcessStart() {
fs.uiTempDir.apply {
deleteRecursively()
mkdirs()
}
}
}
24 changes: 19 additions & 5 deletions app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import android.os.Environment
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import app.revanced.manager.util.RequestManageStorageContract
import java.io.File
import java.nio.file.Path

class Filesystem(private val app: Application) {
val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here.
Expand All @@ -17,21 +19,33 @@ class Filesystem(private val app: Application) {
* A directory that gets cleared when the app restarts.
* Do not store paths to this directory in a parcel.
*/
val tempDir = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
val tempDir: File = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
deleteRecursively()
mkdirs()
}

fun externalFilesDir() = Environment.getExternalStorageDirectory().toPath()
/**
* A directory for storing temporary files related to UI.
* This is the same as [tempDir], but does not get cleared on system-initiated process death.
* Paths to this directory can be safely stored in parcels.
*/
val uiTempDir: File = app.getDir("ui_ephemeral", Context.MODE_PRIVATE)

fun externalFilesDir(): Path = Environment.getExternalStorageDirectory().toPath()

private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R

private val storagePermissionName = if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
private val storagePermissionName =
if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE

fun permissionContract(): Pair<ActivityResultContract<String, Boolean>, String> {
val contract = if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
val contract =
if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
return contract to storagePermissionName
}

fun hasStoragePermission() = if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(storagePermissionName) == PackageManager.PERMISSION_GRANTED
fun hasStoragePermission() =
if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(
storagePermissionName
) == PackageManager.PERMISSION_GRANTED
}
2 changes: 1 addition & 1 deletion app/src/main/java/app/revanced/manager/patcher/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Session(
private val androidContext: Context,
private val logger: Logger,
private val input: File,
private val onPatchCompleted: () -> Unit,
private val onPatchCompleted: suspend () -> Unit,
private val onProgress: (name: String?, state: State?, message: String?) -> Unit
) : Closeable {
private fun updateProgress(name: String? = null, state: State? = null, message: String? = null) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: () -> Unit,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
) {
val bundles = bundles()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: () -> Unit,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
) = coroutineScope {
// Get the location of our own Apk.
Expand Down Expand Up @@ -123,7 +123,9 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
val eventHandler = object : IPatcherEvents.Stub() {
override fun log(level: String, msg: String) = logger.log(enumValueOf(level), msg)

override fun patchSucceeded() = onPatchCompleted()
override fun patchSucceeded() {
launch { onPatchCompleted() }
}

override fun progress(name: String?, state: String?, msg: String?) =
onProgress(name, state?.let { enumValueOf<State>(it) }, msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ sealed class Runtime(context: Context) : KoinComponent {
selectedPatches: PatchSelection,
options: Options,
logger: Logger,
onPatchCompleted: () -> Unit,
onPatchCompleted: suspend () -> Unit,
onProgress: ProgressEventHandler,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ import app.revanced.manager.util.PM
import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.tag
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
Expand Down Expand Up @@ -73,10 +71,10 @@ class PatcherWorker(
val selectedPatches: PatchSelection,
val options: Options,
val logger: Logger,
val downloadProgress: MutableStateFlow<Pair<Long, Long?>?>,
val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
val onDownloadProgress: suspend (Pair<Long, Long?>?) -> Unit,
val onPatchCompleted: suspend () -> Unit,
val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult,
val setInputFile: (File) -> Unit,
val setInputFile: suspend (File) -> Unit,
val onProgress: ProgressEventHandler
) {
val packageName get() = input.packageName
Expand Down Expand Up @@ -160,7 +158,7 @@ class PatcherWorker(
data,
args.packageName,
args.input.version,
onDownload = args.downloadProgress::emit
onDownload = args.onDownloadProgress
).also {
args.setInputFile(it)
updateProgress(state = State.COMPLETED) // Download APK
Expand Down Expand Up @@ -224,11 +222,7 @@ class PatcherWorker(
args.selectedPatches,
args.options,
args.logger,
onPatchCompleted = {
args.patchesProgress.update { (completed, total) ->
completed + 1 to total
}
},
args.onPatchCompleted,
args.onProgress
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
Expand All @@ -21,35 +20,25 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.model.InstallerModel
import com.github.materiiapps.enumutil.FromValue

private typealias InstallerStatusDialogButtonHandler = ((model: InstallerModel) -> Unit)
private typealias InstallerStatusDialogButton = @Composable (model: InstallerStatusDialogModel) -> Unit

interface InstallerModel {
fun reinstall()
fun install()
}

interface InstallerStatusDialogModel : InstallerModel {
var packageInstallerStatus: Int?
}
private typealias InstallerStatusDialogButton = @Composable (model: InstallerModel, dismiss: () -> Unit) -> Unit

@Composable
fun InstallerStatusDialog(model: InstallerStatusDialogModel) {
fun InstallerStatusDialog(installerStatus: Int, model: InstallerModel, onDismiss: () -> Unit) {
val dialogKind = remember {
DialogKind.fromValue(model.packageInstallerStatus!!) ?: DialogKind.FAILURE
DialogKind.fromValue(installerStatus) ?: DialogKind.FAILURE
}

AlertDialog(
onDismissRequest = {
model.packageInstallerStatus = null
},
onDismissRequest = onDismiss,
confirmButton = {
dialogKind.confirmButton(model)
dialogKind.confirmButton(model, onDismiss)
},
dismissButton = {
dialogKind.dismissButton?.invoke(model)
dialogKind.dismissButton?.invoke(model, onDismiss)
},
icon = {
Icon(dialogKind.icon, null)
Expand All @@ -75,10 +64,10 @@ fun InstallerStatusDialog(model: InstallerStatusDialogModel) {
private fun installerStatusDialogButton(
@StringRes buttonStringResId: Int,
buttonHandler: InstallerStatusDialogButtonHandler = { },
): InstallerStatusDialogButton = { model ->
): InstallerStatusDialogButton = { model, dismiss ->
TextButton(
onClick = {
model.packageInstallerStatus = null
dismiss()
buttonHandler(model)
}
) {
Expand Down Expand Up @@ -154,6 +143,7 @@ enum class DialogKind(
model.install()
},
);

// Needed due to the @FromValue annotation.
companion object
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ 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.ui.component.ArrowButton
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.model.ProgressKey
import app.revanced.manager.ui.model.State
import app.revanced.manager.ui.model.Step
import app.revanced.manager.ui.model.StepCategory
import app.revanced.manager.ui.model.StepProgressProvider
import java.util.Locale
import kotlin.math.floor

Expand All @@ -52,6 +53,7 @@ fun Steps(
category: StepCategory,
steps: List<Step>,
stepCount: Pair<Int, Int>? = null,
stepProgressProvider: StepProgressProvider
) {
var expanded by rememberSaveable { mutableStateOf(true) }

Expand Down Expand Up @@ -116,13 +118,20 @@ fun Steps(
modifier = Modifier.fillMaxWidth()
) {
steps.forEach { step ->
val downloadProgress = step.downloadProgress?.collectAsStateWithLifecycle()
val (progress, progressText) = when (step.progressKey) {
null -> null
ProgressKey.DOWNLOAD -> stepProgressProvider.downloadProgress?.let { (downloaded, total) ->
if (total != null) downloaded.toFloat() / total.toFloat() to "${downloaded.megaBytes}/${total.megaBytes} MB"
else null to "${downloaded.megaBytes} MB"
}
} ?: (null to null)

SubStep(
name = step.name,
state = step.state,
message = step.message,
downloadProgress = downloadProgress?.value
progress = progress,
progressText = progressText
)
}
}
Expand All @@ -135,7 +144,8 @@ fun SubStep(
name: String,
state: State,
message: String? = null,
downloadProgress: Pair<Long, Long?>? = null
progress: Float? = null,
progressText: String? = null
) {
var messageExpanded by rememberSaveable { mutableStateOf(true) }

Expand All @@ -156,7 +166,7 @@ fun SubStep(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center
) {
StepIcon(state, downloadProgress, size = 20.dp)
StepIcon(state, progress, size = 20.dp)
}

Text(
Expand All @@ -167,8 +177,8 @@ fun SubStep(
modifier = Modifier.weight(1f, true),
)

if (message != null) {
Box(
when {
message != null -> Box(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center
) {
Expand All @@ -178,13 +188,11 @@ fun SubStep(
onClick = null
)
}
} else {
downloadProgress?.let { (current, total) ->
Text(
if (total != null) "${current.megaBytes}/${total.megaBytes} MB" else "${current.megaBytes} MB",
style = MaterialTheme.typography.labelSmall
)
}

progressText != null -> Text(
progressText,
style = MaterialTheme.typography.labelSmall
)
}
}

Expand All @@ -200,7 +208,7 @@ fun SubStep(
}

@Composable
fun StepIcon(state: State, progress: Pair<Long, Long?>? = null, size: Dp) {
fun StepIcon(state: State, progress: Float? = null, size: Dp) {
val strokeWidth = Dp(floor(size.value / 10) + 1)

when (state) {
Expand Down Expand Up @@ -234,12 +242,7 @@ fun StepIcon(state: State, progress: Pair<Long, Long?>? = null, size: Dp) {
contentDescription = description
}
},
progress = {
progress?.let { (current, total) ->
if (total == null) return@let null
current / total
}?.toFloat()
},
progress = { progress },
strokeWidth = strokeWidth
)
}
Expand Down
Loading

0 comments on commit 49f75f9

Please sign in to comment.