mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-29 22:14:28 +02:00
feat: app installation (#15)
This commit is contained in:
parent
0adf6c1eaa
commit
cf49b49494
@ -50,6 +50,8 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<service android:name=".installer.service.InstallService" />
|
||||
<service android:name=".installer.service.UninstallService" />
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,47 @@
|
||||
package app.revanced.manager.installer.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
|
||||
class InstallService : Service() {
|
||||
|
||||
override fun onStartCommand(
|
||||
intent: Intent, flags: Int, startId: Int
|
||||
): Int {
|
||||
val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)
|
||||
val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
when (extraStatus) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
startActivity(if (Build.VERSION.SDK_INT >= 33) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||
} else {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
}.apply {
|
||||
this?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
})
|
||||
}
|
||||
else -> {
|
||||
sendBroadcast(Intent().apply {
|
||||
action = APP_INSTALL_ACTION
|
||||
putExtra(EXTRA_INSTALL_STATUS, extraStatus)
|
||||
putExtra(EXTRA_INSTALL_STATUS_MESSAGE, extraStatusMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
companion object {
|
||||
const val APP_INSTALL_ACTION = "APP_INSTALL_ACTION"
|
||||
|
||||
const val EXTRA_INSTALL_STATUS = "EXTRA_INSTALL_STATUS"
|
||||
const val EXTRA_INSTALL_STATUS_MESSAGE = "EXTRA_INSTALL_STATUS_MESSAGE"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package app.revanced.manager.installer.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
|
||||
class UninstallService : Service() {
|
||||
|
||||
override fun onStartCommand(
|
||||
intent: Intent,
|
||||
flags: Int,
|
||||
startId: Int
|
||||
): Int {
|
||||
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
startActivity(if (Build.VERSION.SDK_INT >= 33) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||
} else {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
}.apply {
|
||||
this?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
})
|
||||
}
|
||||
else -> {
|
||||
sendBroadcast(Intent().apply {
|
||||
action = APP_UNINSTALL_ACTION
|
||||
})
|
||||
}
|
||||
}
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
companion object {
|
||||
const val APP_UNINSTALL_ACTION = "APP_UNINSTALL_ACTION"
|
||||
}
|
||||
|
||||
}
|
68
app/src/main/java/app/revanced/manager/installer/utils/PM.kt
Normal file
68
app/src/main/java/app/revanced/manager/installer/utils/PM.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package app.revanced.manager.installer.utils
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import app.revanced.manager.installer.service.InstallService
|
||||
import app.revanced.manager.installer.service.UninstallService
|
||||
import java.io.File
|
||||
|
||||
private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable
|
||||
|
||||
object PM {
|
||||
|
||||
fun installApp(apk: File, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
session.writeApk(apk)
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
fun uninstallPackage(pkg: String, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
packageInstaller.uninstall(pkg, context.uninstallIntentSender)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PackageInstaller.Session.writeApk(apk: File) {
|
||||
apk.inputStream().use { inputStream ->
|
||||
openWrite(apk.name, 0, apk.length()).use { outputStream ->
|
||||
inputStream.copyTo(outputStream, byteArraySize)
|
||||
fsync(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val intentFlags
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
else
|
||||
0
|
||||
|
||||
private val sessionParams
|
||||
get() = PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||
).apply {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
|
||||
private val Context.installIntentSender
|
||||
get() = PendingIntent.getService(
|
||||
this,
|
||||
0,
|
||||
Intent(this, InstallService::class.java),
|
||||
intentFlags
|
||||
).intentSender
|
||||
|
||||
private val Context.uninstallIntentSender
|
||||
get() = PendingIntent.getService(
|
||||
this,
|
||||
0,
|
||||
Intent(this, UninstallService::class.java),
|
||||
intentFlags
|
||||
).intentSender
|
@ -0,0 +1,48 @@
|
||||
package app.revanced.manager.ui.component
|
||||
|
||||
import android.content.pm.PackageInstaller
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
|
||||
@Composable
|
||||
fun InstallFailureDialog(
|
||||
onDismiss: () -> Unit, status: Int, result: String
|
||||
) {
|
||||
var showDetails by remember { mutableStateOf(false) }
|
||||
|
||||
val reason = when (status) {
|
||||
PackageInstaller.STATUS_FAILURE_BLOCKED -> stringResource(R.string.status_failure_blocked)
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> stringResource(R.string.status_failure_aborted)
|
||||
PackageInstaller.STATUS_FAILURE_CONFLICT -> stringResource(R.string.status_failure_conflict)
|
||||
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> stringResource(R.string.status_failure_incompatible)
|
||||
PackageInstaller.STATUS_FAILURE_INVALID -> stringResource(R.string.status_failure_invalid)
|
||||
PackageInstaller.STATUS_FAILURE_STORAGE -> stringResource(R.string.status_failure_storage)
|
||||
else -> stringResource(R.string.status_failure)
|
||||
}
|
||||
if (showDetails) {
|
||||
AlertDialog(onDismissRequest = onDismiss, confirmButton = {
|
||||
Button(onClick = { showDetails = false }) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
}, title = { Text(stringResource(R.string.details)) }, text = {
|
||||
Text(result)
|
||||
})
|
||||
} else {
|
||||
AlertDialog(onDismissRequest = onDismiss, confirmButton = {
|
||||
Button(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
}, dismissButton = {
|
||||
OutlinedButton(onClick = { showDetails = true }) {
|
||||
Text(stringResource(R.string.details))
|
||||
}
|
||||
}, title = { Text(stringResource(R.string.install)) }, text = {
|
||||
Text(reason)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package app.revanced.manager.ui.screen.subscreens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
@ -13,11 +15,15 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.InstallFailureDialog
|
||||
import app.revanced.manager.ui.viewmodel.PatchingScreenViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatchingScreenViewModel.PatchLog
|
||||
import app.revanced.manager.ui.viewmodel.PatchingScreenViewModel.Status
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
|
||||
@ -27,14 +33,10 @@ import org.koin.androidx.compose.getViewModel
|
||||
fun PatchingSubscreen(
|
||||
onBackClick: () -> Unit,
|
||||
vm: PatchingScreenViewModel = getViewModel()
|
||||
|
||||
) {
|
||||
var patching by mutableStateOf(false)
|
||||
LaunchedEffect(patching) {
|
||||
if (!patching) {
|
||||
patching = true
|
||||
vm.startPatcher()
|
||||
}
|
||||
}
|
||||
val scrollState = rememberScrollState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -49,6 +51,13 @@ fun PatchingSubscreen(
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column {
|
||||
if (vm.installFailure) {
|
||||
InstallFailureDialog(
|
||||
onDismiss = { vm.installFailure = false },
|
||||
status = vm.pmStatus,
|
||||
result = vm.extra
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
@ -62,7 +71,6 @@ fun PatchingSubscreen(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
when (vm.status) {
|
||||
is Status.Failure -> {
|
||||
Icon(
|
||||
@ -72,7 +80,7 @@ fun PatchingSubscreen(
|
||||
.padding(vertical = 16.dp)
|
||||
.size(30.dp)
|
||||
)
|
||||
Text(text = "Failed!", fontSize = 30.sp)
|
||||
Text(text = stringResource(R.string.failed), fontSize = 30.sp)
|
||||
}
|
||||
is Status.Patching -> {
|
||||
CircularProgressIndicator(
|
||||
@ -80,7 +88,7 @@ fun PatchingSubscreen(
|
||||
.padding(vertical = 16.dp)
|
||||
.size(30.dp)
|
||||
)
|
||||
Text(text = "Patching...", fontSize = 30.sp)
|
||||
Text(text = stringResource(R.string.patching), fontSize = 30.sp)
|
||||
}
|
||||
is Status.Success -> {
|
||||
Icon(
|
||||
@ -90,7 +98,7 @@ fun PatchingSubscreen(
|
||||
.padding(vertical = 16.dp)
|
||||
.size(30.dp)
|
||||
)
|
||||
Text(text = "Completed!", fontSize = 30.sp)
|
||||
Text(text = stringResource(R.string.completed), fontSize = 30.sp)
|
||||
}
|
||||
Status.Idle -> {}
|
||||
}
|
||||
@ -98,18 +106,23 @@ fun PatchingSubscreen(
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(20.dp)
|
||||
) {
|
||||
ElevatedCard {
|
||||
LazyColumn(
|
||||
ElevatedCard(
|
||||
Modifier
|
||||
.weight(1f, true)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
items(vm.logs) { log ->
|
||||
vm.logs.forEach { log ->
|
||||
Text(
|
||||
modifier = Modifier.height(36.dp),
|
||||
modifier = Modifier.requiredHeightIn(min = 36.dp),
|
||||
text = log.message,
|
||||
color = when (log) {
|
||||
is PatchLog.Success -> Color.Green
|
||||
@ -117,10 +130,31 @@ fun PatchingSubscreen(
|
||||
is PatchLog.Error -> Color.Red
|
||||
},
|
||||
fontSize = 20.sp,
|
||||
onTextLayout = {
|
||||
coroutineScope.launch {
|
||||
scrollState.animateScrollTo(
|
||||
9999, tween(1000, easing = LinearEasing)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vm.status is Status.Success) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
Button(onClick = {
|
||||
vm.installApk(vm.outputFile)
|
||||
}) {
|
||||
Text(text = stringResource(R.string.install))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,24 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Environment
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.installer.service.InstallService
|
||||
import app.revanced.manager.installer.service.UninstallService
|
||||
import app.revanced.manager.installer.utils.PM
|
||||
import app.revanced.manager.network.api.ManagerAPI
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.patcher.aapt.Aapt
|
||||
@ -24,7 +32,6 @@ import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import io.sentry.Sentry
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@ -39,6 +46,11 @@ class PatchingScreenViewModel(
|
||||
private val patcherUtils: PatcherUtils
|
||||
) : ViewModel() {
|
||||
|
||||
var installFailure by mutableStateOf(false)
|
||||
|
||||
var pmStatus by mutableStateOf(-999)
|
||||
var extra by mutableStateOf("")
|
||||
|
||||
sealed interface PatchLog {
|
||||
val message: String
|
||||
|
||||
@ -47,9 +59,6 @@ class PatchingScreenViewModel(
|
||||
data class Error(override val message: String) : PatchLog
|
||||
}
|
||||
|
||||
val logs = mutableStateListOf<PatchLog>()
|
||||
var status by mutableStateOf<Status>(Status.Idle)
|
||||
|
||||
sealed class Status {
|
||||
object Idle : Status()
|
||||
object Patching : Status()
|
||||
@ -57,72 +66,95 @@ class PatchingScreenViewModel(
|
||||
object Failure : Status()
|
||||
}
|
||||
|
||||
fun startPatcher() {
|
||||
cancelPatching() // cancel patching if its still running
|
||||
logs.clear()
|
||||
status = Status.Patching
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
runPatcher(createWorkDir())
|
||||
} catch (e: Exception) {
|
||||
status = Status.Failure
|
||||
Log.e(tag, "Error while patching: ${e.message ?: e::class.simpleName}")
|
||||
Sentry.captureException(e)
|
||||
val outputFile = File(app.filesDir, "output.apk")
|
||||
val logs = mutableStateListOf<PatchLog>()
|
||||
var status by mutableStateOf<Status>(Status.Idle)
|
||||
|
||||
private val installBroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
InstallService.APP_INSTALL_ACTION -> {
|
||||
pmStatus = intent.getIntExtra(InstallService.EXTRA_INSTALL_STATUS, -999)
|
||||
extra = intent.getStringExtra(InstallService.EXTRA_INSTALL_STATUS_MESSAGE)!!
|
||||
postInstallStatus()
|
||||
}
|
||||
UninstallService.APP_UNINSTALL_ACTION -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelPatching() {
|
||||
viewModelScope.coroutineContext.cancelChildren(CancellationException("Patching was cancelled by user."))
|
||||
logs.clear()
|
||||
init {
|
||||
status = Status.Patching
|
||||
app.registerReceiver(
|
||||
installBroadcastReceiver,
|
||||
IntentFilter().apply {
|
||||
addAction(InstallService.APP_INSTALL_ACTION)
|
||||
addAction(UninstallService.APP_UNINSTALL_ACTION)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun runPatcher(
|
||||
workdir: File
|
||||
) {
|
||||
fun installApk(apk: File) {
|
||||
PM.installApp(apk, app)
|
||||
log(PatchLog.Info("Installing..."))
|
||||
}
|
||||
|
||||
fun postInstallStatus() {
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
log(PatchLog.Success("Successfully installed!"))
|
||||
} else {
|
||||
installFailure = true
|
||||
log(PatchLog.Error("Failed to install!"))
|
||||
}
|
||||
}
|
||||
|
||||
private val patcher = viewModelScope.launch(Dispatchers.IO) {
|
||||
val workdir = createWorkDir()
|
||||
val wakeLock: PowerManager.WakeLock =
|
||||
(app.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
newWakeLock(PowerManager.FULL_WAKE_LOCK, "$tag::Patcher").apply {
|
||||
newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply {
|
||||
acquire(10 * 60 * 1000L)
|
||||
}
|
||||
}
|
||||
Log.d(tag, "Acquired wakelock.")
|
||||
val aaptPath = Aapt.binary(app)?.absolutePath
|
||||
if (aaptPath == null) {
|
||||
log(PatchLog.Error("AAPT2 not found."))
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
val frameworkPath = app.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
|
||||
val integrationsCacheDir = app.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
||||
val reVancedFolder =
|
||||
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
|
||||
val appInfo = patcherUtils.selectedAppPackage.value.get()
|
||||
|
||||
log(PatchLog.Info("Checking prerequisites..."))
|
||||
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches)
|
||||
if (patches.isEmpty()) return
|
||||
|
||||
log(PatchLog.Info("Creating directories..."))
|
||||
val inputFile = File(app.filesDir, "input.apk")
|
||||
val patchedFile = File(workdir, "patched.apk")
|
||||
val outputFile = File(app.filesDir, "output.apk")
|
||||
val cacheDirectory = workdir.resolve("cache")
|
||||
|
||||
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
||||
|
||||
log(PatchLog.Info("Copying APK from device..."))
|
||||
withContext(Dispatchers.IO) {
|
||||
Files.copy(
|
||||
File(appInfo.publicSourceDir).toPath(),
|
||||
inputFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
val aaptPath = Aapt.binary(app)?.absolutePath
|
||||
if (aaptPath == null) {
|
||||
log(PatchLog.Error("AAPT2 not found."))
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
val frameworkPath = app.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
|
||||
val integrationsCacheDir =
|
||||
app.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
||||
val reVancedFolder =
|
||||
Environment.getExternalStorageDirectory().resolve("ReVanced").also { it.mkdirs() }
|
||||
val appInfo = patcherUtils.selectedAppPackage.value.get()
|
||||
|
||||
log(PatchLog.Info("Checking prerequisites..."))
|
||||
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches)
|
||||
if (patches.isEmpty()) throw IllegalStateException("No patches selected.")
|
||||
|
||||
log(PatchLog.Info("Creating directories..."))
|
||||
val inputFile = File(app.filesDir, "input.apk")
|
||||
val patchedFile = File(workdir, "patched.apk")
|
||||
val cacheDirectory = workdir.resolve("cache")
|
||||
|
||||
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
||||
|
||||
log(PatchLog.Info("Copying APK from device..."))
|
||||
withContext(Dispatchers.IO) {
|
||||
Files.copy(
|
||||
File(appInfo.publicSourceDir).toPath(),
|
||||
inputFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
log(PatchLog.Info("Decoding resources"))
|
||||
val patcher = Patcher( // start patcher
|
||||
PatcherOptions(inputFile,
|
||||
PatcherOptions(
|
||||
inputFile,
|
||||
cacheDirectory.absolutePath,
|
||||
aaptPath = aaptPath,
|
||||
frameworkFolderLocation = frameworkPath,
|
||||
@ -189,12 +221,22 @@ class PatchingScreenViewModel(
|
||||
log(PatchLog.Success("Successfully patched!"))
|
||||
patcherUtils.cleanup()
|
||||
status = Status.Success
|
||||
} finally {
|
||||
Log.d(tag, "Deleting workdir")
|
||||
workdir.deleteRecursively()
|
||||
wakeLock.release()
|
||||
Log.d(tag, "Released wakelock.")
|
||||
} catch (e: Exception) {
|
||||
status = Status.Failure
|
||||
Log.e(tag, "Error while patching: ${e.message ?: e::class.simpleName}")
|
||||
Sentry.captureException(e)
|
||||
}
|
||||
Log.d(tag, "Deleting workdir")
|
||||
workdir.deleteRecursively()
|
||||
wakeLock.release()
|
||||
Log.d(tag, "Released wakelock.")
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
app.unregisterReceiver(installBroadcastReceiver)
|
||||
patcher.cancel(CancellationException("ViewModel cleared"))
|
||||
logs.clear()
|
||||
}
|
||||
|
||||
private fun createWorkDir(): File {
|
||||
|
@ -52,4 +52,17 @@
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="patch">Patch</string>
|
||||
<string name="no_compatible_patches">No compatible patches found.</string>
|
||||
<string name="status_failure_blocked">Installation failed due to installer being blocked by the operating system.</string>
|
||||
<string name="status_failure">Installation failed due to an unknown error.</string>
|
||||
<string name="status_failure_conflict">Installation failed due to conflicting packages. Please uninstall the conflicting app before retrying installation.</string>
|
||||
<string name="status_failure_aborted">Installation failed due to user abortion.</string>
|
||||
<string name="status_failure_incompatible">Installation failed due to the app you\'re trying to install being incompatible with current device.</string>
|
||||
<string name="status_failure_invalid">Installation failed due to package being invalid. This might be caused by unsigned or corrupted package.</string>
|
||||
<string name="status_failure_storage">Installation failed due to insufficient storage.</string>
|
||||
<string name="details">Details</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="patching">Patching…</string>
|
||||
<string name="completed">Completed!</string>
|
||||
<string name="failed">Failed!</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user