Support module scope

This commit is contained in:
Nullptr 2022-05-30 16:20:18 +08:00
parent 66804c425d
commit 290989b0e6
No known key found for this signature in database
GPG Key ID: 0B9D02052FF536BD
20 changed files with 321 additions and 105 deletions

View File

@ -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")
}

View File

@ -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() }
}
}

View File

@ -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<Module, org.lsposed.lspd.models.Module>()
suspend fun updateModules(newModules: Map<String, String>) =
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<Module> =
withContext(dispatcher) {
return@withContext scopeDao.getModulesForApp(pkgName)
}
suspend fun getModuleFilesForApp(pkgName: String): List<org.lsposed.lspd.models.Module> =
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)
}
}
}
}
}

View File

@ -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
}

View File

@ -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<Module>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(module: Module)
@Delete
suspend fun delete(module: Module)
}

View File

@ -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<Module>
@Insert
suspend fun insert(scope: Scope)
@Delete
suspend fun delete(scope: Scope)
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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<Module> {
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 {

View File

@ -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<Module>
}
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 {
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<Module>()
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<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
return null
}

View File

@ -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) }
)
}
}

View File

@ -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
) {

View File

@ -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<ManageViewModel>()
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,18 +221,53 @@ private fun Body() {
)
}
} else {
var scopeApp by rememberSaveable { mutableStateOf("") }
val isCancelled by navController.currentBackStackEntry!!.observeState<Boolean>("isCancelled")
LaunchedEffect(isCancelled) {
if (isCancelled == false) {
val selected = navController.currentBackStackEntry!!
.savedStateHandle.getLiveData<SnapshotStateList<AppInfo>>("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 }
) {
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 = {}
) {
onClick = {
scope.launch {
scopeApp = it.first.app.packageName
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
navController.currentBackStackEntry!!.setState(
"selected",
SnapshotStateList<AppInfo>().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
@ -247,6 +283,11 @@ private fun Body() {
style = MaterialTheme.typography.bodySmall
)
}
)
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
/* TODO */
}
}
}
}

View File

@ -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>("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<SnapshotStateList<AppInfo>>("selected", SnapshotStateList()).observe(lifecycleOwner) {
entry.savedStateHandle.getLiveData("selected", SnapshotStateList<AppInfo>()).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,

View File

@ -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)

View File

@ -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
)

View File

@ -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<AppInfo>()
lateinit var patchApp: AppInfo
private set
lateinit var embeddedModules: SnapshotStateList<AppInfo>
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 {

View File

@ -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
}
}

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stub!"
android:textSize="50sp" />
</LinearLayout>

View File

@ -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"
}
}