From 290989b0e6d33df85972c917819576065fac2670 Mon Sep 17 00:00:00 2001 From: Nullptr <52071314+Dr-TSNG@users.noreply.github.com> Date: Mon, 30 May 2022 16:20:18 +0800 Subject: [PATCH] Support module scope --- manager/build.gradle.kts | 6 + .../org/lsposed/lspatch/LSPApplication.kt | 3 + .../lsposed/lspatch/config/ConfigManager.kt | 72 ++++++++++++ .../lsposed/lspatch/database/LSPDatabase.kt | 15 +++ .../lsposed/lspatch/database/dao/ModuleDao.kt | 21 ++++ .../lsposed/lspatch/database/dao/ScopeDao.kt | 21 ++++ .../lsposed/lspatch/database/entity/Module.kt | 10 ++ .../lsposed/lspatch/database/entity/Scope.kt | 13 +++ .../lsposed/lspatch/manager/ManagerService.kt | 14 ++- .../lsposed/lspatch/manager/ModuleProvider.kt | 36 +----- .../lsposed/lspatch/ui/component/AppItem.kt | 11 +- .../org/lsposed/lspatch/ui/page/HomePage.kt | 11 +- .../org/lsposed/lspatch/ui/page/ManagePage.kt | 103 ++++++++++++------ .../lsposed/lspatch/ui/page/NewPatchPage.kt | 15 ++- .../lsposed/lspatch/ui/page/SelectAppsPage.kt | 12 +- .../lsposed/lspatch/ui/page/SettingsPage.kt | 14 ++- .../lspatch/ui/viewmodel/NewPatchViewModel.kt | 22 +++- .../lsposed/lspatch/util/LSPPackageManager.kt | 12 +- manager/src/main/res/layout/activity_main.xml | 14 --- settings.gradle.kts | 1 + 20 files changed, 321 insertions(+), 105 deletions(-) create mode 100644 manager/src/main/java/org/lsposed/lspatch/config/ConfigManager.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/database/LSPDatabase.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/database/dao/ModuleDao.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/database/dao/ScopeDao.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/database/entity/Module.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/database/entity/Scope.kt delete mode 100644 manager/src/main/res/layout/activity_main.xml diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index a6fcc64..9b8ae56 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -7,6 +7,7 @@ val coreVerName: String by rootProject.extra plugins { id("com.android.application") + id("com.google.devtools.ksp") id("dev.rikka.tools.refine") id("kotlin-parcelize") kotlin("android") @@ -73,6 +74,8 @@ dependencies { implementation(projects.share.android) implementation(projects.share.java) + val roomVersion = "2.4.2" + annotationProcessor("androidx.room:room-compiler:$roomVersion") compileOnly("dev.rikka.hidden:stub:2.3.1") implementation("dev.rikka.hidden:compat:2.3.1") implementation("androidx.core:core-ktx:1.7.0") @@ -85,6 +88,8 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01") implementation("androidx.navigation:navigation-compose:2.5.0-rc01") implementation("androidx.preference:preference:1.2.0") + implementation("androidx.room:room-ktx:$roomVersion") + implementation("androidx.room:room-runtime:$roomVersion") implementation("com.google.accompanist:accompanist-drawablepainter:0.24.9-beta") implementation("com.google.accompanist:accompanist-navigation-animation:0.24.9-beta") implementation("com.google.accompanist:accompanist-swiperefresh:0.24.9-beta") @@ -93,4 +98,5 @@ dependencies { implementation("dev.rikka.shizuku:api:12.1.0") implementation("dev.rikka.shizuku:provider:12.1.0") implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") + ksp("androidx.room:room-compiler:$roomVersion") } diff --git a/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt b/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt index 31da106..965cc1c 100644 --- a/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt +++ b/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt @@ -5,7 +5,9 @@ import android.content.Context import android.content.SharedPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.lsposed.hiddenapibypass.HiddenApiBypass +import org.lsposed.lspatch.util.LSPPackageManager import org.lsposed.lspatch.util.ShizukuApi import java.io.File @@ -29,5 +31,6 @@ class LSPApplication : Application() { tmpApkDir.mkdirs() prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE) ShizukuApi.init() + globalScope.launch { LSPPackageManager.fetchAppList() } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/config/ConfigManager.kt b/manager/src/main/java/org/lsposed/lspatch/config/ConfigManager.kt new file mode 100644 index 0000000..c6977df --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/config/ConfigManager.kt @@ -0,0 +1,72 @@ +package org.lsposed.lspatch.config + +import androidx.room.Room +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.withContext +import org.lsposed.lspatch.database.LSPDatabase +import org.lsposed.lspatch.database.entity.Module +import org.lsposed.lspatch.database.entity.Scope +import org.lsposed.lspatch.lspApp +import org.lsposed.lspatch.util.ModuleLoader + +object ConfigManager { + + @OptIn(ExperimentalCoroutinesApi::class) + private val dispatcher = Dispatchers.Default.limitedParallelism(1) + + private val db: LSPDatabase = Room.databaseBuilder( + lspApp, LSPDatabase::class.java, "modules_config.db" + ).build() + + private val moduleDao = db.moduleDao() + private val scopeDao = db.scopeDao() + + private val loadedModules = mutableMapOf() + + suspend fun updateModules(newModules: Map) = + withContext(dispatcher) { + for (module in moduleDao.getAll()) { + val apkPath = newModules[module.pkgName] + if (apkPath == null) { + moduleDao.delete(module) + loadedModules.remove(module) + } else if (module.apkPath != apkPath) { + module.apkPath = apkPath + loadedModules.remove(module) + } + } + for ((pkgName, apkPath) in newModules) { + moduleDao.insert(Module(pkgName, apkPath)) + } + } + + suspend fun activateModule(pkgName: String, module: Module) = + withContext(dispatcher) { + scopeDao.insert(Scope(appPkgName = pkgName, modulePkgName = module.pkgName)) + } + + suspend fun deactivateModule(pkgName: String, module: Module) = + withContext(dispatcher) { + scopeDao.delete(Scope(appPkgName = pkgName, modulePkgName = module.pkgName)) + } + + suspend fun getModulesForApp(pkgName: String): List = + withContext(dispatcher) { + return@withContext scopeDao.getModulesForApp(pkgName) + } + + suspend fun getModuleFilesForApp(pkgName: String): List = + withContext(dispatcher) { + val modules = scopeDao.getModulesForApp(pkgName) + return@withContext modules.map { + loadedModules.getOrPut(it) { + org.lsposed.lspd.models.Module().apply { + packageName = it.pkgName + apkPath = it.apkPath + file = ModuleLoader.loadModule(it.apkPath) + } + } + } + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/database/LSPDatabase.kt b/manager/src/main/java/org/lsposed/lspatch/database/LSPDatabase.kt new file mode 100644 index 0000000..6c11d75 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/database/LSPDatabase.kt @@ -0,0 +1,15 @@ +package org.lsposed.lspatch.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import org.lsposed.lspatch.database.dao.ModuleDao +import org.lsposed.lspatch.database.dao.ScopeDao + +import org.lsposed.lspatch.database.entity.Module +import org.lsposed.lspatch.database.entity.Scope + +@Database(entities = [Module::class, Scope::class], version = 1) +abstract class LSPDatabase : RoomDatabase() { + abstract fun moduleDao(): ModuleDao + abstract fun scopeDao(): ScopeDao +} diff --git a/manager/src/main/java/org/lsposed/lspatch/database/dao/ModuleDao.kt b/manager/src/main/java/org/lsposed/lspatch/database/dao/ModuleDao.kt new file mode 100644 index 0000000..828ea9a --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/database/dao/ModuleDao.kt @@ -0,0 +1,21 @@ +package org.lsposed.lspatch.database.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.lsposed.lspatch.database.entity.Module + +@Dao +interface ModuleDao { + + @Query("SELECT * FROM module") + suspend fun getAll(): List + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(module: Module) + + @Delete + suspend fun delete(module: Module) +} diff --git a/manager/src/main/java/org/lsposed/lspatch/database/dao/ScopeDao.kt b/manager/src/main/java/org/lsposed/lspatch/database/dao/ScopeDao.kt new file mode 100644 index 0000000..97a1965 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/database/dao/ScopeDao.kt @@ -0,0 +1,21 @@ +package org.lsposed.lspatch.database.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import org.lsposed.lspatch.database.entity.Module +import org.lsposed.lspatch.database.entity.Scope + +@Dao +interface ScopeDao { + + @Query("SELECT * FROM module INNER JOIN scope ON module.pkgName = scope.modulePkgName WHERE scope.appPkgName = :appPkgName") + suspend fun getModulesForApp(appPkgName: String): List + + @Insert + suspend fun insert(scope: Scope) + + @Delete + suspend fun delete(scope: Scope) +} diff --git a/manager/src/main/java/org/lsposed/lspatch/database/entity/Module.kt b/manager/src/main/java/org/lsposed/lspatch/database/entity/Module.kt new file mode 100644 index 0000000..1202586 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/database/entity/Module.kt @@ -0,0 +1,10 @@ +package org.lsposed.lspatch.database.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class Module( + @PrimaryKey val pkgName: String, + var apkPath: String +) diff --git a/manager/src/main/java/org/lsposed/lspatch/database/entity/Scope.kt b/manager/src/main/java/org/lsposed/lspatch/database/entity/Scope.kt new file mode 100644 index 0000000..8a110bd --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/database/entity/Scope.kt @@ -0,0 +1,13 @@ +package org.lsposed.lspatch.database.entity + +import androidx.room.Entity +import androidx.room.ForeignKey + +@Entity( + primaryKeys = ["appPkgName", "modulePkgName"], + foreignKeys = [ForeignKey(entity = Module::class, parentColumns = ["pkgName"], childColumns = ["modulePkgName"], onDelete = ForeignKey.CASCADE)] +) +data class Scope( + val appPkgName: String, + val modulePkgName: String +) diff --git a/manager/src/main/java/org/lsposed/lspatch/manager/ManagerService.kt b/manager/src/main/java/org/lsposed/lspatch/manager/ManagerService.kt index a89989d..f14a8f0 100644 --- a/manager/src/main/java/org/lsposed/lspatch/manager/ManagerService.kt +++ b/manager/src/main/java/org/lsposed/lspatch/manager/ManagerService.kt @@ -1,19 +1,31 @@ package org.lsposed.lspatch.manager +import android.os.Binder import android.os.Bundle import android.os.IBinder import android.os.ParcelFileDescriptor +import android.util.Log +import kotlinx.coroutines.runBlocking +import org.lsposed.lspatch.config.ConfigManager +import org.lsposed.lspatch.lspApp import org.lsposed.lspd.models.Module import org.lsposed.lspd.service.ILSPApplicationService object ManagerService : ILSPApplicationService.Stub() { + private const val TAG = "ManagerService" + override fun requestModuleBinder(name: String): IBinder { TODO("Not yet implemented") } override fun getModulesList(): List { - return ModuleProvider.allModules + val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid()) + val list = app?.let { + runBlocking { ConfigManager.getModuleFilesForApp(it) } + }.orEmpty() + Log.d(TAG, "$app calls getModulesList: $list") + return list } override fun getPrefsPath(packageName: String): String { diff --git a/manager/src/main/java/org/lsposed/lspatch/manager/ModuleProvider.kt b/manager/src/main/java/org/lsposed/lspatch/manager/ModuleProvider.kt index ce8475d..612daba 100644 --- a/manager/src/main/java/org/lsposed/lspatch/manager/ModuleProvider.kt +++ b/manager/src/main/java/org/lsposed/lspatch/manager/ModuleProvider.kt @@ -2,55 +2,31 @@ package org.lsposed.lspatch.manager import android.content.ContentProvider import android.content.ContentValues -import android.content.pm.PackageManager import android.database.Cursor import android.net.Uri import android.os.Binder import android.os.Bundle import android.util.Log import org.lsposed.lspatch.TAG -import org.lsposed.lspatch.util.ModuleLoader -import org.lsposed.lspd.models.Module +import org.lsposed.lspatch.lspApp class ModuleProvider : ContentProvider() { - companion object { - lateinit var allModules: List - } override fun onCreate(): Boolean { return false } override fun call(method: String, arg: String?, extras: Bundle?): Bundle { - val app = context!!.packageManager.getNameForUid(Binder.getCallingUid()) - Log.d(TAG, "$app calls binder") - when (method) { - "getBinder" -> { - loadAllModules() - return Bundle().apply { - putBinder("binder", ManagerService.asBinder()) - } + val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid()) + Log.d(TAG, "$app requests ModuleProvider") + return when (method) { + "getBinder" -> Bundle().apply { + putBinder("binder", ManagerService.asBinder()) } else -> throw IllegalArgumentException("Invalid method name") } } - private fun loadAllModules() { - val list = mutableListOf() - for (pkg in context!!.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)) { - val app = pkg.applicationInfo ?: continue - if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { - Module().apply { - apkPath = app.publicSourceDir - packageName = app.packageName - file = ModuleLoader.loadModule(apkPath) - }.also { list.add(it) } - Log.d(TAG, "send module ${app.packageName}") - } - } - allModules = list - } - override fun query(p0: Uri, p1: Array?, p2: String?, p3: Array?, p4: String?): Cursor? { return null } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt b/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt index ce089f1..ddd804e 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt @@ -5,6 +5,8 @@ import android.graphics.drawable.GradientDrawable import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -27,8 +29,11 @@ fun AppItem( onClick: () -> Unit, onLongClick: (() -> Unit)? = null, checked: Boolean? = null, + rightIcon: (@Composable () -> Unit)? = null, additionalContent: (@Composable () -> Unit)? = null, ) { + if (checked != null && rightIcon != null) + throw IllegalArgumentException("`checked` and `rightIcon` should not be both set") Column( modifier = modifier .fillMaxWidth() @@ -67,6 +72,9 @@ fun AppItem( modifier = Modifier.padding(start = 20.dp) ) } + if (rightIcon != null) { + rightIcon() + } } } } @@ -82,7 +90,8 @@ private fun AppItemPreview() { icon = shape, label = "Sample App", packageName = "org.lsposed.sample", - onClick = {} + onClick = {}, + rightIcon = { Icon(Icons.Filled.ArrowForwardIos, null) } ) } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt index 58dbf60..a84262c 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt @@ -47,6 +47,7 @@ fun HomePage() { ShizukuCard() InfoCard() SupportCard() + Spacer(Modifier) } } } @@ -84,11 +85,6 @@ private fun ShizukuCard() { } ElevatedCard( - modifier = Modifier.clickable { - if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) { - Shizuku.requestPermission(114514) - } - }, colors = CardDefaults.elevatedCardColors(containerColor = run { if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.errorContainer @@ -97,6 +93,11 @@ private fun ShizukuCard() { Row( modifier = Modifier .fillMaxWidth() + .clickable { + if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) { + Shizuku.requestPermission(114514) + } + } .padding(24.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt index d246d2a..3a14055 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt @@ -13,8 +13,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -29,20 +32,24 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.viewmodel.compose.viewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY import org.lsposed.lspatch.R -import org.lsposed.lspatch.TAG +import org.lsposed.lspatch.config.ConfigManager +import org.lsposed.lspatch.database.entity.Module import org.lsposed.lspatch.lspApp import org.lsposed.lspatch.ui.component.AppItem import org.lsposed.lspatch.ui.util.LocalNavController import org.lsposed.lspatch.ui.util.LocalSnackbarHost +import org.lsposed.lspatch.ui.util.observeState +import org.lsposed.lspatch.ui.util.setState import org.lsposed.lspatch.ui.viewmodel.ManageViewModel import org.lsposed.lspatch.util.LSPPackageManager +import org.lsposed.lspatch.util.LSPPackageManager.AppInfo import java.io.IOException +private const val TAG = "ManagePage" + @OptIn(ExperimentalMaterial3Api::class) @Composable fun ManagePage() { @@ -198,14 +205,8 @@ private fun Fab() { @Composable private fun Body() { val viewModel = viewModel() - - LaunchedEffect(Unit) { - if (LSPPackageManager.appList.isEmpty()) { - withContext(Dispatchers.IO) { - LSPPackageManager.fetchAppList() - } - } - } + val navController = LocalNavController.current + val scope = rememberCoroutineScope() if (viewModel.appList.isEmpty()) { Box(Modifier.fillMaxSize()) { @@ -220,33 +221,73 @@ private fun Body() { ) } } else { + var scopeApp by rememberSaveable { mutableStateOf("") } + val isCancelled by navController.currentBackStackEntry!!.observeState("isCancelled") + LaunchedEffect(isCancelled) { + if (isCancelled == false) { + val selected = navController.currentBackStackEntry!! + .savedStateHandle.getLiveData>("selected").value!!.toSet() + Log.d(TAG, "Clear module list for $scopeApp") + ConfigManager.getModulesForApp(scopeApp).forEach { + ConfigManager.deactivateModule(scopeApp, it) + } + selected.forEach { + Log.d(TAG, "Activate ${it.app.packageName} for $scopeApp") + ConfigManager.activateModule(scopeApp, Module(it.app.packageName, it.app.sourceDir)) + } + navController.currentBackStackEntry!!.setState("isCancelled", null) + } + } LazyColumn { items( items = viewModel.appList, key = { it.first.app.packageName } ) { - AppItem( - modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)), - icon = LSPPackageManager.getIcon(it.first), - label = it.first.label, - packageName = it.first.app.packageName, - onClick = {} - ) { - val text = buildAnnotatedString { - val (text, color) = - if (it.second.useManager) stringResource(R.string.patch_local) to MaterialTheme.colorScheme.secondary - else stringResource(R.string.patch_portable) to MaterialTheme.colorScheme.tertiary - append(AnnotatedString(text, SpanStyle(color = color))) - append(" ") - append(it.second.lspConfig.VERSION_CODE.toString()) - } - Text( - text = text, - fontWeight = FontWeight.SemiBold, - fontFamily = FontFamily.Serif, - style = MaterialTheme.typography.bodySmall + var expanded by remember { mutableStateOf(false) } + Box { + AppItem( + modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)), + icon = LSPPackageManager.getIcon(it.first), + label = it.first.label, + packageName = it.first.app.packageName, + onClick = { + scope.launch { + scopeApp = it.first.app.packageName + val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet() + navController.currentBackStackEntry!!.setState( + "selected", + SnapshotStateList().apply { + LSPPackageManager.appList.filterTo(this) { activated.contains(it.app.packageName) } + } + ) + navController.navigate(PageList.SelectApps.name + "?multiSelect=true") + } + }, + onLongClick = { + // expanded = true + }, + rightIcon = { if (it.second.useManager) Icon(Icons.Filled.ArrowForwardIos, null) }, + additionalContent = { + val text = buildAnnotatedString { + val (text, color) = + if (it.second.useManager) stringResource(R.string.patch_local) to MaterialTheme.colorScheme.secondary + else stringResource(R.string.patch_portable) to MaterialTheme.colorScheme.tertiary + append(AnnotatedString(text, SpanStyle(color = color))) + append(" ") + append(it.second.lspConfig.VERSION_CODE.toString()) + } + Text( + text = text, + fontWeight = FontWeight.SemiBold, + fontFamily = FontFamily.Serif, + style = MaterialTheme.typography.bodySmall + ) + } ) } + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + /* TODO */ + } } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt index 5c86699..a2321aa 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt @@ -45,6 +45,7 @@ import org.lsposed.lspatch.ui.component.settings.SettingsItem import org.lsposed.lspatch.ui.util.* import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState +import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.ViewAction import org.lsposed.lspatch.util.LSPPackageManager import org.lsposed.lspatch.util.LSPPackageManager.AppInfo import org.lsposed.lspatch.util.ShizukuApi @@ -71,7 +72,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) { runBlocking { LSPPackageManager.getAppInfoFromApks(apks) .onSuccess { - viewModel.configurePatch(it) + viewModel.dispatch(ViewAction.ConfigurePatch(it)) } .onFailure { lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") } @@ -86,7 +87,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) { "storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive")) "applist" -> { entry.savedStateHandle.getLiveData("appInfo").observe(lifecycleOwner) { - viewModel.configurePatch(it) + viewModel.dispatch(ViewAction.ConfigurePatch(it)) } navController.navigate(PageList.SelectApps.name + "?multiSelect=false") } @@ -111,7 +112,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) { ) { innerPadding -> if (viewModel.patchState == PatchState.CONFIGURING) { LaunchedEffect(Unit) { - entry.savedStateHandle.getLiveData>("selected", SnapshotStateList()).observe(lifecycleOwner) { + entry.savedStateHandle.getLiveData("selected", SnapshotStateList()).observe(lifecycleOwner) { viewModel.embeddedModules = it } } @@ -142,7 +143,7 @@ private fun ConfiguringFab() { ExtendedFloatingActionButton( text = { Text(stringResource(R.string.patch_start)) }, icon = { Icon(Icons.Outlined.AutoFixHigh, null) }, - onClick = { viewModel.submitPatch() } + onClick = { viewModel.dispatch(ViewAction.SubmitPatch) } ) } @@ -273,7 +274,7 @@ private fun DoPatchBody(modifier: Modifier) { LaunchedEffect(Unit) { if (viewModel.logs.isEmpty()) { - viewModel.launchPatch() + viewModel.dispatch(ViewAction.LaunchPatch) } } @@ -288,9 +289,7 @@ private fun DoPatchBody(modifier: Modifier) { .animateContentSize(spring(stiffness = Spring.StiffnessLow)) ) { ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) { - CompositionLocalProvider( - LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace) - ) { + ProvideTextStyle(MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)) { val scrollState = rememberLazyListState() LazyColumn( state = scrollState, diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt index cd2d372..deb7b3a 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt @@ -42,7 +42,7 @@ fun SelectAppsPage(multiSelect: Boolean) { val filter: (AppInfo) -> Boolean = { val packageLowerCase = searchPackage.toLowerCase(Locale.current) val contains = it.label.toLowerCase(Locale.current).contains(packageLowerCase) || it.app.packageName.contains(packageLowerCase) - if (multiSelect) contains && it.app.metaData?.get("xposedminversion") != null + if (multiSelect) contains && it.isXposedModule else contains && it.app.flags and ApplicationInfo.FLAG_SYSTEM == 0 } @@ -94,9 +94,13 @@ fun SelectAppsPage(multiSelect: Boolean) { @Composable private fun MultiSelectFab() { val navController = LocalNavController.current - FloatingActionButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Outlined.Done, stringResource(R.string.add)) - } + FloatingActionButton( + onClick = { + navController.previousBackStackEntry!!.setState("isCancelled", false) + navController.popBackStack() + }, + content = { Icon(Icons.Outlined.Done, stringResource(R.string.add)) } + ) } @OptIn(ExperimentalFoundationApi::class) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt index 699dda0..0d185a2 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt @@ -77,7 +77,10 @@ private fun KeyStore() { ) DropdownMenuItem( text = { Text(stringResource(R.string.settings_keystore_custom)) }, - onClick = { showDialog = true } + onClick = { + dropDownExpanded = false + showDialog = true + } ) } } @@ -160,7 +163,10 @@ private fun KeyStore() { ) }, text = { - Column { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { val interactionSource = remember { MutableInteractionSource() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> @@ -178,9 +184,7 @@ private fun KeyStore() { else -> null } Text( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 8.dp), + modifier = Modifier.padding(bottom = 8.dp), text = wrongText ?: stringResource(R.string.settings_keystore_desc), color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified ) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt index 51c8189..98c2f4d 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt @@ -23,6 +23,12 @@ class NewPatchViewModel : ViewModel() { SELECTING, CONFIGURING, PATCHING, FINISHED, ERROR } + sealed class ViewAction { + data class ConfigurePatch(val app: AppInfo) : ViewAction() + object SubmitPatch : ViewAction() + object LaunchPatch : ViewAction() + } + var patchState by mutableStateOf(PatchState.SELECTING) private set @@ -31,10 +37,10 @@ class NewPatchViewModel : ViewModel() { var overrideVersionCode by mutableStateOf(false) val sign = mutableStateListOf(false, true) var sigBypassLevel by mutableStateOf(2) + var embeddedModules = SnapshotStateList() lateinit var patchApp: AppInfo private set - lateinit var embeddedModules: SnapshotStateList lateinit var patchOptions: Patcher.Options private set @@ -58,13 +64,21 @@ class NewPatchViewModel : ViewModel() { } } - fun configurePatch(app: AppInfo) { + fun dispatch(action: ViewAction) { + when (action) { + is ViewAction.ConfigurePatch -> configurePatch(action.app) + is ViewAction.SubmitPatch -> submitPatch() + is ViewAction.LaunchPatch -> launchPatch() + } + } + + private fun configurePatch(app: AppInfo) { Log.d(TAG, "Configuring patch for ${app.app.packageName}") patchApp = app patchState = PatchState.CONFIGURING } - fun submitPatch() { + private fun submitPatch() { Log.d(TAG, "Submit patch") if (useManager) embeddedModules.clear() patchOptions = Patcher.Options( @@ -80,7 +94,7 @@ class NewPatchViewModel : ViewModel() { patchState = PatchState.PATCHING } - fun launchPatch() { + private fun launchPatch() { logger.i("Launch patch") viewModelScope.launch { patchState = try { diff --git a/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt b/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt index 71a378f..b1d13cd 100644 --- a/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt +++ b/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY +import org.lsposed.lspatch.config.ConfigManager import org.lsposed.lspatch.lspApp import org.lsposed.patch.util.ManifestParser import java.io.File @@ -35,7 +36,10 @@ object LSPPackageManager { private const val TAG = "LSPPackageManager" @Parcelize - class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable + class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable { + val isXposedModule: Boolean + get() = app.metaData?.get("xposedminversion") != null + } const val STATUS_USER_CANCELLED = -2 @@ -53,7 +57,11 @@ object LSPPackageManager { collection.add(AppInfo(it, label.toString())) appIcon[it.packageName] = pm.getApplicationIcon(it) } - collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault())) { it.label }) + collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label)) + val modules = buildMap { + collection.forEach { if (it.isXposedModule) put(it.app.packageName, it.app.sourceDir) } + } + ConfigManager.updateModules(modules) appList = collection } } diff --git a/manager/src/main/res/layout/activity_main.xml b/manager/src/main/res/layout/activity_main.xml deleted file mode 100644 index 71f2583..0000000 --- a/manager/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 525b317..079a398 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,7 @@ pluginManagement { plugins { id("com.android.library") version agpVersion id("com.android.application") version agpVersion + id("com.google.devtools.ksp") version "1.6.21-1.0.5" id("dev.rikka.tools.refine") version "3.1.1" } }