Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: process death resilience and account for android 11 bug #2355

Merged
merged 10 commits into from
Dec 22, 2024
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
Loading