refactor: minor cleanup

This commit is contained in:
Canny 2022-12-15 17:28:18 +03:00
parent 74318f30fb
commit 79d4330408
No known key found for this signature in database
GPG Key ID: 395CCB0AA979F27B
14 changed files with 213 additions and 211 deletions

View File

@ -1,10 +1,9 @@
package app.revanced.manager.di package app.revanced.manager.di
import app.revanced.manager.patcher.worker.PatcherWorker import app.revanced.manager.patcher.worker.PatcherWorker
import org.koin.android.ext.koin.androidContext import org.koin.androidx.workmanager.dsl.workerOf
import org.koin.androidx.workmanager.dsl.worker
import org.koin.dsl.module import org.koin.dsl.module
val workerModule = module { val workerModule = module {
worker { PatcherWorker(androidContext(), get(), get(), get()) } workerOf(::PatcherWorker)
} }

View File

@ -38,7 +38,7 @@ class ManagerAPI(
return out return out
} }
suspend fun downloadPatches() = withContext(Dispatchers.Default) { suspend fun downloadPatches() = withContext(Dispatchers.IO) {
try { try {
val asset = val asset =
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar") if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")
@ -46,7 +46,6 @@ class ManagerAPI(
asset.run { asset.run {
downloadAsset(app.cacheDir, downloadUrl).run { downloadAsset(app.cacheDir, downloadUrl).run {
patcherUtils.run { patcherUtils.run {
patchBundleFile = absolutePath
loadPatchBundle(absolutePath) loadPatchBundle(absolutePath)
} }
} }

View File

@ -7,11 +7,8 @@ import android.util.Log
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.viewmodel.PatchClass
import app.revanced.manager.util.tag import app.revanced.manager.util.tag
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.patch.PatchBundle import app.revanced.patcher.util.patch.PatchBundle
import dalvik.system.DexClassLoader import dalvik.system.DexClassLoader
@ -19,12 +16,10 @@ import io.sentry.Sentry
import java.util.* import java.util.*
class PatcherUtils(val app: Application) { class PatcherUtils(val app: Application) {
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading) val patches = mutableStateOf<Resource<List<ReVancedPatch>>>(Resource.Loading)
val filteredPatches = mutableStateListOf<PatchClass>()
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>()) val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
val selectedAppPackagePath = mutableStateOf<String?>(null) val selectedAppPackagePath = mutableStateOf<String?>(null)
val selectedPatches = mutableStateListOf<String>() val selectedPatches = mutableStateListOf<ReVancedPatch>()
lateinit var patchBundleFile: String
fun cleanup() { fun cleanup() {
patches.value = Resource.Loading patches.value = Resource.Loading
@ -32,16 +27,15 @@ class PatcherUtils(val app: Application) {
selectedPatches.clear() selectedPatches.clear()
} }
fun loadPatchBundle(file: String? = patchBundleFile) { fun loadPatchBundle(file: String) {
cleanup()
try { try {
if (this::patchBundleFile.isInitialized) { val patchClasses = PatchBundle.Dex(
val patchClasses = PatchBundle.Dex( file, DexClassLoader(
file!!, DexClassLoader( file, app.codeCacheDir.absolutePath, null, javaClass.classLoader
file, app.codeCacheDir.absolutePath, null, javaClass.classLoader )
) ).loadPatches()
).loadPatches() patches.value = Resource.Success(patchClasses)
patches.value = Resource.Success(patchClasses)
} else throw IllegalStateException("No patch bundle(s) selected.")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "Failed to load patch bundle.", e) Log.e(tag, "Failed to load patch bundle.", e)
Sentry.captureException(e) Sentry.captureException(e)
@ -50,7 +44,9 @@ class PatcherUtils(val app: Application) {
fun getSelectedPackageInfo(): PackageInfo? { fun getSelectedPackageInfo(): PackageInfo? {
return if (selectedAppPackage.value.isPresent) { return if (selectedAppPackage.value.isPresent) {
val path = selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value ?: return null val path =
selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value
?: return null
app.packageManager.getPackageArchiveInfo( app.packageManager.getPackageArchiveInfo(
path, 1 path, 1
) )
@ -58,9 +54,5 @@ class PatcherUtils(val app: Application) {
null null
} }
} }
fun findPatchesByIds(ids: Iterable<String>): List<Class<out Patch<Context>>> {
val (patches) = patches.value as? Resource.Success ?: return listOf()
return patches.filter { patch -> ids.any { it == patch.patchName } && patch.compatiblePackages!!.any { it.name == getSelectedPackageInfo()?.packageName } }
}
} }
typealias ReVancedPatch = Class<out Patch<Context>>

View File

@ -13,6 +13,7 @@ import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import app.revanced.manager.R import app.revanced.manager.R
@ -80,20 +81,6 @@ class PatcherWorker(
Sentry.captureException(e) Sentry.captureException(e)
} }
return try {
runPatcher(workdir)
Result.success()
} catch (e: Exception) {
log("Error while patching: ${e::class.simpleName}: ${e.message}", ERROR)
Log.e(tag, e.stackTraceToString())
Sentry.captureException(e)
Result.failure()
}
}
private suspend fun runPatcher(
workdir: File
): Boolean {
val wakeLock: PowerManager.WakeLock = val wakeLock: PowerManager.WakeLock =
(applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run { (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply { newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply {
@ -102,7 +89,8 @@ class PatcherWorker(
} }
Log.d(tag, "Acquired wakelock.") Log.d(tag, "Acquired wakelock.")
try { return try {
val aaptPath = Aapt.binary(applicationContext)?.absolutePath val aaptPath = Aapt.binary(applicationContext)?.absolutePath
if (aaptPath == null) { if (aaptPath == null) {
log("AAPT2 not found.", ERROR) log("AAPT2 not found.", ERROR)
@ -118,13 +106,14 @@ class PatcherWorker(
val appPath = patcherUtils.selectedAppPackagePath.value val appPath = patcherUtils.selectedAppPackagePath.value
log("Checking prerequisites...", INFO) log("Checking prerequisites...", INFO)
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches) val patches = patcherUtils.selectedPatches
if (patches.isEmpty()) throw IllegalStateException("No patches selected.") if (patches.isEmpty()) throw IllegalStateException("No patches selected.")
log("Creating directories...", INFO) log("Creating directories...", INFO)
val inputFile = File(workdir, "input.apk") val inputFile = File(workdir, "input.apk")
val patchedFile = File(workdir, "patched.apk") val patchedFile = File(workdir, "patched.apk")
val outputFile = File(inputData.getString("output")!!) val outputFile = File(workdir, "output.apk")
val finalFile = reVancedFolder.resolve(appInfo.packageName + ".apk")
val cacheDirectory = workdir.resolve("cache") val cacheDirectory = workdir.resolve("cache")
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir) val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
@ -203,19 +192,24 @@ class PatcherWorker(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Files.copy( Files.copy(
outputFile.inputStream(), outputFile.inputStream(),
reVancedFolder.resolve(appInfo.packageName + ".apk").toPath(), finalFile.toPath(),
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
) )
} }
log("Successfully patched!", SUCCESS) log("Successfully patched!", SUCCESS)
patcherUtils.cleanup() Result.success(Data.Builder().putString(OUTPUT, finalFile.absolutePath).build())
} catch (e: Exception) {
log("Error while patching: ${e::class.simpleName}: ${e.message}", ERROR)
Log.e(tag, e.stackTraceToString())
Sentry.captureException(e)
Result.failure()
} finally { } finally {
Log.d(tag, "Deleting workdir") Log.d(tag, "Deleting workdir")
workdir.deleteRecursively() workdir.deleteRecursively()
wakeLock.release() wakeLock.release()
Log.d(tag, "Released wakelock.") Log.d(tag, "Released wakelock.")
} }
return false
} }
@ -236,6 +230,7 @@ class PatcherWorker(
const val PATCH_MESSAGE = "PATCH_MESSAGE" const val PATCH_MESSAGE = "PATCH_MESSAGE"
const val PATCH_STATUS = "PATCH_STATUS" const val PATCH_STATUS = "PATCH_STATUS"
const val PATCH_LOG = "PATCH_LOG" const val PATCH_LOG = "PATCH_LOG"
const val OUTPUT = "output"
const val INFO = 0 const val INFO = 0
const val ERROR = 1 const val ERROR = 1

View File

@ -29,14 +29,12 @@ fun PatcherScreen(
onClickSourceSelector: () -> Unit, onClickSourceSelector: () -> Unit,
vm: PatcherScreenViewModel = getViewModel(), vm: PatcherScreenViewModel = getViewModel(),
) { ) {
val hasAppSelected by mutableStateOf(vm.selectedAppPackage.isPresent)
val patchesLoaded by mutableStateOf(vm.patchesLoaded is Resource.Success)
val context = LocalContext.current val context = LocalContext.current
Scaffold( Scaffold(
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
enabled = hasAppSelected && vm.selectedPatches.isNotEmpty(), enabled = vm.selectedAppPackage.isPresent && vm.selectedPatches.isNotEmpty(),
onClick = onClickPatch, onClick = onClickPatch,
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") }, icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
text = { Text(stringResource(R.string.patch)) } text = { Text(stringResource(R.string.patch)) }
@ -66,7 +64,7 @@ fun PatcherScreen(
modifier = Modifier modifier = Modifier
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
.fillMaxWidth(), .fillMaxWidth(),
enabled = patchesLoaded, enabled = vm.patches is Resource.Success,
onClick = onClickAppSelector onClick = onClickAppSelector
) { ) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
@ -86,7 +84,7 @@ fun PatcherScreen(
Spacer(Modifier.width(5.dp)) Spacer(Modifier.width(5.dp))
} }
Text( Text(
text = if (patchesLoaded) { text = if (vm.patches is Resource.Success) {
if (vm.selectedAppPackage.isPresent) { if (vm.selectedAppPackage.isPresent) {
vm.selectedAppPackage.get().packageName vm.selectedAppPackage.get().packageName
} else { } else {
@ -105,7 +103,7 @@ fun PatcherScreen(
modifier = Modifier modifier = Modifier
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
.fillMaxWidth(), .fillMaxWidth(),
enabled = hasAppSelected, enabled = vm.selectedAppPackage.isPresent,
onClick = onClickPatchSelector onClick = onClickPatchSelector
) { ) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
@ -114,7 +112,7 @@ fun PatcherScreen(
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Text( Text(
text = if (!hasAppSelected) { text = if (!vm.selectedAppPackage.isPresent) {
stringResource(R.string.select_an_application_first) stringResource(R.string.select_an_application_first)
} else if (vm.selectedPatches.isNotEmpty()) { } else if (vm.selectedPatches.isNotEmpty()) {
"${vm.selectedPatches.size} patches selected." "${vm.selectedPatches.size} patches selected."

View File

@ -26,7 +26,7 @@ fun ContributorsSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: ContributorsViewModel = getViewModel() vm: ContributorsViewModel = getViewModel()
) { ) {
val ctx = LocalContext.current.applicationContext val ctx = LocalContext.current
AppScaffold( AppScaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
AppLargeTopBar( AppLargeTopBar(

View File

@ -15,12 +15,9 @@ import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppMediumTopBar import app.revanced.manager.ui.component.AppMediumTopBar
import app.revanced.manager.ui.component.AppScaffold import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.PatchCard import app.revanced.manager.ui.component.PatchCard
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@SuppressLint("UnrememberedMutableState") @SuppressLint("UnrememberedMutableState")
@ -30,101 +27,79 @@ fun PatchesSelectorSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: PatchesSelectorViewModel = getViewModel(), vm: PatchesSelectorViewModel = getViewModel(),
) { ) {
val patches = vm.filteredPatches
var query by mutableStateOf("")
LaunchedEffect(null) { AppScaffold(topBar = { scrollBehavior ->
launch(Dispatchers.Default) { AppMediumTopBar(
vm.filterPatches() topBarTitle = stringResource(id = R.string.card_patches_header),
} scrollBehavior = scrollBehavior,
} actions = {
AppScaffold( IconButton(onClick = {
topBar = { scrollBehavior -> vm.selectAllPatches(vm.patches, vm.selectedPatches.isEmpty())
AppMediumTopBar( }) {
topBarTitle = stringResource(id = R.string.card_patches_header), if (vm.selectedPatches.isEmpty()) Icon(
scrollBehavior = scrollBehavior, Icons.Default.SelectAll, contentDescription = null
actions = { ) else Icon(Icons.Default.Deselect, contentDescription = null)
IconButton(onClick = { }
vm.selectAllPatches(patches, vm.selectedPatches.isEmpty()) },
}) { onBackClick = onBackClick
if (vm.selectedPatches.isEmpty()) Icon( )
Icons.Default.SelectAll, contentDescription = null }) { paddingValues ->
) else Icon(Icons.Default.Deselect, contentDescription = null)
}
},
onBackClick = onBackClick
)
}
) { paddingValues ->
Column( Column(
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {
if (!vm.loading) { val search = vm.search
if (patches.isNotEmpty()) { if (vm.patches.isNotEmpty()) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp, 4.dp), .padding(8.dp, 4.dp),
) {
Row(
modifier = Modifier.fillMaxWidth()
) { ) {
Row( OutlinedTextField(
modifier = Modifier.fillMaxWidth() modifier = Modifier
) { .fillMaxWidth()
OutlinedTextField( .padding(8.dp),
modifier = Modifier shape = RoundedCornerShape(12.dp),
.fillMaxWidth() value = search,
.padding(8.dp), onValueChange = { vm.search(it) },
shape = RoundedCornerShape(12.dp), leadingIcon = {
value = query, Icon(Icons.Default.Search, "Search")
onValueChange = { newValue -> },
query = newValue trailingIcon = {
}, if (search.isNotEmpty()) {
leadingIcon = { IconButton(onClick = {
Icon(Icons.Default.Search, "Search") vm.clearSearch()
}, }) {
trailingIcon = { Icon(Icons.Default.Clear, "Clear")
if (query.isNotEmpty()) {
IconButton(onClick = {
query = ""
}) {
Icon(Icons.Default.Clear, "Clear")
}
}
},
)
}
}
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
if (query.isEmpty() || query.isBlank()) {
items(count = patches.size) {
val patch = patches[it]
val name = patch.patch.patchName
PatchCard(patch, vm.isPatchSelected(name)) {
vm.selectPatch(name, !vm.isPatchSelected(name))
}
}
} else {
items(count = patches.size) {
val patch = patches[it]
val name = patch.patch.patchName
if (name.contains(query.lowercase())) {
PatchCard(patch, vm.isPatchSelected(name)) {
vm.selectPatch(name, !vm.isPatchSelected(name))
} }
} }
} },
} )
}
} else {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text(stringResource(R.string.no_compatible_patches))
} }
} }
} else LoadingIndicator(null) LazyColumn(Modifier.padding(0.dp, 2.dp)) {
items(count = vm.patches.size) {
val patchClass = vm.patches[it]
val name = patchClass.patch.patchName
if (search.isBlank() && name.contains(search.lowercase())) {
PatchCard(patchClass, vm.isPatchSelected(patchClass.patch)) {
vm.selectPatch(
patchClass.patch,
!vm.isPatchSelected(patchClass.patch)
)
}
}
}
}
} else {
Column(
Modifier.fillMaxSize(), Arrangement.Center, Alignment.CenterHorizontally
) {
Text(stringResource(R.string.no_compatible_patches))
}
}
} }
} }
} }

View File

@ -53,7 +53,7 @@ fun PatchingSubscreen(
Column { Column {
if (vm.installFailure) { if (vm.installFailure) {
InstallFailureDialog( InstallFailureDialog(
onDismiss = { vm.installFailure = false }, onDismiss = { vm.dismissDialog() },
status = vm.pmStatus, status = vm.pmStatus,
result = vm.extra result = vm.extra
) )
@ -149,7 +149,7 @@ fun PatchingSubscreen(
) { ) {
Spacer(Modifier.weight(1f, true)) Spacer(Modifier.weight(1f, true))
Button(onClick = { Button(onClick = {
vm.installApk(vm.outputFile) vm.installApk()
}) { }) {
Text(text = stringResource(R.string.install)) Text(text = stringResource(R.string.install))
} }

View File

@ -25,7 +25,6 @@ class AppSelectorViewModel(
val filteredApps = mutableStateListOf<ApplicationInfo>() val filteredApps = mutableStateListOf<ApplicationInfo>()
val patches = patcherUtils.patches val patches = patcherUtils.patches
private val filteredPatches = patcherUtils.filteredPatches
private val selectedAppPackage = patcherUtils.selectedAppPackage private val selectedAppPackage = patcherUtils.selectedAppPackage
private val selectedAppPackagePath = patcherUtils.selectedAppPackagePath private val selectedAppPackagePath = patcherUtils.selectedAppPackagePath
private val selectedPatches = patcherUtils.selectedPatches private val selectedPatches = patcherUtils.selectedPatches
@ -34,22 +33,27 @@ class AppSelectorViewModel(
viewModelScope.launch { filterApps() } viewModelScope.launch { filterApps() }
} }
private suspend fun filterApps() = withContext(Dispatchers.Default) { @Suppress("RemoveExplicitTypeArguments")
private suspend fun filterApps() = withContext(Dispatchers.IO) {
try { try {
val (patches) = patches.value as Resource.Success val (patches) = patches.value as Resource.Success
patches.forEach patch@{ patch -> val apps = buildList<ApplicationInfo> {
patch.compatiblePackages?.forEach { pkg -> patches.forEach patch@{ patch ->
try { patch.compatiblePackages?.forEach { pkg ->
if (!(filteredApps.any { it.packageName == pkg.name })) { try {
val appInfo = app.packageManager.getApplicationInfo(pkg.name, 1) if (!any { it.packageName == pkg.name }) {
filteredApps.add(appInfo) add(app.packageManager.getApplicationInfo(pkg.name, 1))
return@forEach
}
} catch (e: Exception) {
return@forEach return@forEach
} }
} catch (e: Exception) {
return@forEach
} }
} }
} }
withContext(Dispatchers.Main) {
filteredApps.addAll(apps)
}
Log.d(tag, "Filtered apps.") Log.d(tag, "Filtered apps.")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "An error occurred while filtering", e) Log.e(tag, "An error occurred while filtering", e)
@ -69,7 +73,6 @@ class AppSelectorViewModel(
selectedAppPackage.value.ifPresent { s -> selectedAppPackage.value.ifPresent { s ->
if (s != appId) { if (s != appId) {
selectedPatches.clear() selectedPatches.clear()
filteredPatches.clear()
} }
} }
selectedAppPackage.value = Optional.of(appId) selectedAppPackage.value = Optional.of(appId)
@ -86,8 +89,7 @@ class AppSelectorViewModel(
setSelectedAppPackage( setSelectedAppPackage(
app.packageManager.getPackageArchiveInfo( app.packageManager.getPackageArchiveInfo(
apkDir.path, 1 apkDir.path, 1
)!!.applicationInfo, )!!.applicationInfo, apkDir.absolutePath
apkDir.absolutePath
) )
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "Failed to load apk", e) Log.e(tag, "Failed to load apk", e)

View File

@ -21,5 +21,5 @@ class PatcherScreenViewModel(
val selectedPatches = patcherUtils.selectedPatches val selectedPatches = patcherUtils.selectedPatches
val selectedAppPackage by patcherUtils.selectedAppPackage val selectedAppPackage by patcherUtils.selectedAppPackage
val patchesLoaded by patcherUtils.patches val patches by patcherUtils.patches
} }

View File

@ -2,61 +2,79 @@ package app.revanced.manager.ui.viewmodel
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.patcher.ReVancedPatch
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class PatchesSelectorViewModel( class PatchesSelectorViewModel(
private val patcherUtils: PatcherUtils private val patcherUtils: PatcherUtils
) : ViewModel() { ) : ViewModel() {
val filteredPatches = patcherUtils.filteredPatches val patches = mutableStateListOf<PatchClass>()
val selectedPatches = patcherUtils.selectedPatches val selectedPatches = patcherUtils.selectedPatches
var loading by mutableStateOf(true) var search by mutableStateOf("")
private set
fun isPatchSelected(patchId: String): Boolean { fun search(search: String) {
return selectedPatches.contains(patchId) this.search = search
} }
fun selectPatch(patchId: String, state: Boolean) { fun clearSearch() {
if (state) selectedPatches.add(patchId) search = ""
else selectedPatches.remove(patchId) }
init {
viewModelScope.launch { filterPatches() }
}
fun isPatchSelected(patch: ReVancedPatch): Boolean {
return selectedPatches.contains(patch)
}
fun selectPatch(patch: ReVancedPatch, state: Boolean) {
if (state) selectedPatches.add(patch)
else selectedPatches.remove(patch)
} }
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) { fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
patchList.forEach { patch -> patchList.forEach { patchClass ->
val patchId = patch.patch.patchName val patch = patchClass.patch
if (selectAll && !patch.unsupported) selectedPatches.add(patchId) if (selectAll && !patchClass.unsupported) selectedPatches.add(patch)
else selectedPatches.remove(patchId) else selectedPatches.remove(patch)
} }
} }
fun filterPatches() { private suspend fun filterPatches() = withContext(Dispatchers.IO) {
loading = true val selected = patcherUtils.getSelectedPackageInfo() ?: return@withContext
val selected = patcherUtils.getSelectedPackageInfo() ?: return val (patchList) = patcherUtils.patches.value as? Resource.Success ?: return@withContext
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return val filtered = buildList {
if (filteredPatches.isNotEmpty()) { patchList.forEach { patch ->
loading = false; return var unsupported = false
} patch.compatiblePackages?.forEach { pkg ->
patches.forEach patch@{ patch -> // if we detect unsupported once, don't overwrite it
var unsupported = false if (pkg.name == selected.packageName) {
patch.compatiblePackages?.forEach { pkg -> if (!unsupported)
// if we detect unsupported once, don't overwrite it unsupported =
if (pkg.name == selected.packageName) { pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
if (!unsupported) add(PatchClass(patch, unsupported))
unsupported = }
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
filteredPatches.add(PatchClass(patch, unsupported))
} }
} }
} }
loading = false withContext(Dispatchers.Main) {
patches.addAll(filtered)
}
} }
} }

View File

@ -17,6 +17,7 @@ import app.revanced.manager.installer.service.InstallService
import app.revanced.manager.installer.service.UninstallService import app.revanced.manager.installer.service.UninstallService
import app.revanced.manager.installer.utils.PM import app.revanced.manager.installer.utils.PM
import app.revanced.manager.patcher.worker.PatcherWorker import app.revanced.manager.patcher.worker.PatcherWorker
import app.revanced.manager.util.toast
import java.io.File import java.io.File
class PatchingScreenViewModel( class PatchingScreenViewModel(
@ -38,32 +39,43 @@ class PatchingScreenViewModel(
object Failure : Status() object Failure : Status()
} }
val workManager = WorkManager.getInstance(app) private var output: String? = null
var installFailure by mutableStateOf(false)
var pmStatus by mutableStateOf(-999)
var extra by mutableStateOf("")
val outputFile = File(app.cacheDir, "output.apk") var installFailure by mutableStateOf(false)
private set
var pmStatus by mutableStateOf(-999)
private set
var extra by mutableStateOf("")
private set
private val workManager = WorkManager.getInstance(app)
private val patcherWorker = private val patcherWorker =
OneTimeWorkRequest.Builder(PatcherWorker::class.java) // create Worker OneTimeWorkRequest.Builder(PatcherWorker::class.java) // create Worker
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).setInputData( .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).setInputData(
Data.Builder().putString("output", outputFile.path).build() Data.Builder().putString(PatcherWorker.OUTPUT, null).build()
).build() ).build()
private val liveData = workManager.getWorkInfoByIdLiveData(patcherWorker.id) // get LiveData private val liveData = workManager.getWorkInfoByIdLiveData(patcherWorker.id)
private val observer = Observer { workInfo: WorkInfo -> // observer for observing patch status private val observer = Observer { workInfo: WorkInfo ->
status = when (workInfo.state) { status = when (workInfo.state) {
WorkInfo.State.RUNNING -> Status.Patching WorkInfo.State.RUNNING -> Status.Patching
WorkInfo.State.SUCCEEDED -> Status.Success WorkInfo.State.SUCCEEDED -> Status.Success
WorkInfo.State.FAILED -> Status.Failure WorkInfo.State.FAILED -> Status.Failure
else -> Status.Idle else -> Status.Idle
} }
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
output = workInfo.outputData.getString("output")
}
} }
val logs = mutableStateListOf<PatchLog>() val logs = mutableStateListOf<PatchLog>()
var status by mutableStateOf<Status>(Status.Idle) var status by mutableStateOf<Status>(Status.Idle)
private set
private val installBroadcastReceiver = object : BroadcastReceiver() { private val installBroadcastReceiver = object : BroadcastReceiver() {
@ -95,15 +107,25 @@ class PatchingScreenViewModel(
workManager.enqueueUniqueWork("patching", ExistingWorkPolicy.KEEP, patcherWorker) workManager.enqueueUniqueWork("patching", ExistingWorkPolicy.KEEP, patcherWorker)
liveData.observeForever(observer) liveData.observeForever(observer)
app.registerReceiver(installBroadcastReceiver, IntentFilter().apply { app.registerReceiver(installBroadcastReceiver, IntentFilter().apply {
addAction(InstallService.APP_INSTALL_ACTION) arrayOf(
addAction(UninstallService.APP_UNINSTALL_ACTION) InstallService.APP_INSTALL_ACTION,
addAction(PatcherWorker.PATCH_LOG) UninstallService.APP_UNINSTALL_ACTION,
PatcherWorker.PATCH_LOG
).forEach {
addAction(it)
}
}) })
} }
fun installApk(apk: File) { fun dismissDialog() {
PM.installApp(apk, app) installFailure = false
log(PatchLog.Info("Installing...")) }
fun installApk() {
if (output != null) {
PM.installApp(File(output!!), app)
log(PatchLog.Info("Installing..."))
} else app.toast("Couldn't find APK file.")
} }
fun postInstallStatus() { fun postInstallStatus() {

View File

@ -7,11 +7,11 @@ import androidx.lifecycle.ViewModel
import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.util.tag import app.revanced.manager.util.tag
import io.sentry.Sentry import io.sentry.Sentry
import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
class SourceSelectorViewModel(val app: Application, val patcherUtils: PatcherUtils) : ViewModel() { class SourceSelectorViewModel(val app: Application, private val patcherUtils: PatcherUtils) :
ViewModel() {
fun loadBundle(uri: Uri) { fun loadBundle(uri: Uri) {
try { try {
val patchesFile = app.cacheDir.resolve("patches.jar") val patchesFile = app.cacheDir.resolve("patches.jar")
@ -20,10 +20,7 @@ class SourceSelectorViewModel(val app: Application, val patcherUtils: PatcherUti
patchesFile.toPath(), patchesFile.toPath(),
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
) )
patchesFile.absolutePath.also { patcherUtils.loadPatchBundle(patchesFile.absolutePath)
patcherUtils.patchBundleFile = it
patcherUtils.loadPatchBundle(it)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "Failed to load bundle", e) Log.e(tag, "Failed to load bundle", e)
Sentry.captureException(e) Sentry.captureException(e)

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.Toast
import androidx.core.net.toUri import androidx.core.net.toUri
@ -20,3 +21,7 @@ fun Context.loadIcon(string: String): Drawable? {
null null
} }
} }
fun Context.toast(string: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, string, duration).show()
}