Support module scope
This commit is contained in:
parent
66804c425d
commit
290989b0e6
|
|
@ -7,6 +7,7 @@ val coreVerName: String by rootProject.extra
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
id("dev.rikka.tools.refine")
|
id("dev.rikka.tools.refine")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
kotlin("android")
|
kotlin("android")
|
||||||
|
|
@ -73,6 +74,8 @@ dependencies {
|
||||||
implementation(projects.share.android)
|
implementation(projects.share.android)
|
||||||
implementation(projects.share.java)
|
implementation(projects.share.java)
|
||||||
|
|
||||||
|
val roomVersion = "2.4.2"
|
||||||
|
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||||
implementation("androidx.core:core-ktx:1.7.0")
|
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.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01")
|
||||||
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
|
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
|
||||||
implementation("androidx.preference:preference:1.2.0")
|
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-drawablepainter:0.24.9-beta")
|
||||||
implementation("com.google.accompanist:accompanist-navigation-animation: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")
|
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:api:12.1.0")
|
||||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||||
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
|
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
|
||||||
|
ksp("androidx.room:room-compiler:$roomVersion")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
import org.lsposed.lspatch.util.LSPPackageManager
|
||||||
import org.lsposed.lspatch.util.ShizukuApi
|
import org.lsposed.lspatch.util.ShizukuApi
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
@ -29,5 +31,6 @@ class LSPApplication : Application() {
|
||||||
tmpApkDir.mkdirs()
|
tmpApkDir.mkdirs()
|
||||||
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
ShizukuApi.init()
|
ShizukuApi.init()
|
||||||
|
globalScope.launch { LSPPackageManager.fetchAppList() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -1,19 +1,31 @@
|
||||||
package org.lsposed.lspatch.manager
|
package org.lsposed.lspatch.manager
|
||||||
|
|
||||||
|
import android.os.Binder
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.ParcelFileDescriptor
|
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.models.Module
|
||||||
import org.lsposed.lspd.service.ILSPApplicationService
|
import org.lsposed.lspd.service.ILSPApplicationService
|
||||||
|
|
||||||
object ManagerService : ILSPApplicationService.Stub() {
|
object ManagerService : ILSPApplicationService.Stub() {
|
||||||
|
|
||||||
|
private const val TAG = "ManagerService"
|
||||||
|
|
||||||
override fun requestModuleBinder(name: String): IBinder {
|
override fun requestModuleBinder(name: String): IBinder {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getModulesList(): List<Module> {
|
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 {
|
override fun getPrefsPath(packageName: String): String {
|
||||||
|
|
|
||||||
|
|
@ -2,55 +2,31 @@ package org.lsposed.lspatch.manager
|
||||||
|
|
||||||
import android.content.ContentProvider
|
import android.content.ContentProvider
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.lsposed.lspatch.TAG
|
import org.lsposed.lspatch.TAG
|
||||||
import org.lsposed.lspatch.util.ModuleLoader
|
import org.lsposed.lspatch.lspApp
|
||||||
import org.lsposed.lspd.models.Module
|
|
||||||
|
|
||||||
class ModuleProvider : ContentProvider() {
|
class ModuleProvider : ContentProvider() {
|
||||||
companion object {
|
|
||||||
lateinit var allModules: List<Module>
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(): Boolean {
|
override fun onCreate(): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
||||||
val app = context!!.packageManager.getNameForUid(Binder.getCallingUid())
|
val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid())
|
||||||
Log.d(TAG, "$app calls binder")
|
Log.d(TAG, "$app requests ModuleProvider")
|
||||||
when (method) {
|
return when (method) {
|
||||||
"getBinder" -> {
|
"getBinder" -> Bundle().apply {
|
||||||
loadAllModules()
|
putBinder("binder", ManagerService.asBinder())
|
||||||
return Bundle().apply {
|
|
||||||
putBinder("binder", ManagerService.asBinder())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("Invalid method name")
|
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? {
|
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import android.graphics.drawable.GradientDrawable
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -27,8 +29,11 @@ fun AppItem(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
checked: Boolean? = null,
|
checked: Boolean? = null,
|
||||||
|
rightIcon: (@Composable () -> Unit)? = null,
|
||||||
additionalContent: (@Composable () -> Unit)? = null,
|
additionalContent: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
|
if (checked != null && rightIcon != null)
|
||||||
|
throw IllegalArgumentException("`checked` and `rightIcon` should not be both set")
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -67,6 +72,9 @@ fun AppItem(
|
||||||
modifier = Modifier.padding(start = 20.dp)
|
modifier = Modifier.padding(start = 20.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (rightIcon != null) {
|
||||||
|
rightIcon()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +90,8 @@ private fun AppItemPreview() {
|
||||||
icon = shape,
|
icon = shape,
|
||||||
label = "Sample App",
|
label = "Sample App",
|
||||||
packageName = "org.lsposed.sample",
|
packageName = "org.lsposed.sample",
|
||||||
onClick = {}
|
onClick = {},
|
||||||
|
rightIcon = { Icon(Icons.Filled.ArrowForwardIos, null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ fun HomePage() {
|
||||||
ShizukuCard()
|
ShizukuCard()
|
||||||
InfoCard()
|
InfoCard()
|
||||||
SupportCard()
|
SupportCard()
|
||||||
|
Spacer(Modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,11 +85,6 @@ private fun ShizukuCard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier.clickable {
|
|
||||||
if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) {
|
|
||||||
Shizuku.requestPermission(114514)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||||
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer
|
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer
|
||||||
else MaterialTheme.colorScheme.errorContainer
|
else MaterialTheme.colorScheme.errorContainer
|
||||||
|
|
@ -97,6 +93,11 @@ private fun ShizukuCard() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) {
|
||||||
|
Shizuku.requestPermission(114514)
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.ArrowForwardIos
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -29,20 +32,24 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||||
import org.lsposed.lspatch.R
|
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.lspApp
|
||||||
import org.lsposed.lspatch.ui.component.AppItem
|
import org.lsposed.lspatch.ui.component.AppItem
|
||||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
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.ui.viewmodel.ManageViewModel
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager
|
import org.lsposed.lspatch.util.LSPPackageManager
|
||||||
|
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
private const val TAG = "ManagePage"
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ManagePage() {
|
fun ManagePage() {
|
||||||
|
|
@ -198,14 +205,8 @@ private fun Fab() {
|
||||||
@Composable
|
@Composable
|
||||||
private fun Body() {
|
private fun Body() {
|
||||||
val viewModel = viewModel<ManageViewModel>()
|
val viewModel = viewModel<ManageViewModel>()
|
||||||
|
val navController = LocalNavController.current
|
||||||
LaunchedEffect(Unit) {
|
val scope = rememberCoroutineScope()
|
||||||
if (LSPPackageManager.appList.isEmpty()) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
LSPPackageManager.fetchAppList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel.appList.isEmpty()) {
|
if (viewModel.appList.isEmpty()) {
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
|
@ -220,33 +221,73 @@ private fun Body() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
LazyColumn {
|
||||||
items(
|
items(
|
||||||
items = viewModel.appList,
|
items = viewModel.appList,
|
||||||
key = { it.first.app.packageName }
|
key = { it.first.app.packageName }
|
||||||
) {
|
) {
|
||||||
AppItem(
|
var expanded by remember { mutableStateOf(false) }
|
||||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
Box {
|
||||||
icon = LSPPackageManager.getIcon(it.first),
|
AppItem(
|
||||||
label = it.first.label,
|
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||||
packageName = it.first.app.packageName,
|
icon = LSPPackageManager.getIcon(it.first),
|
||||||
onClick = {}
|
label = it.first.label,
|
||||||
) {
|
packageName = it.first.app.packageName,
|
||||||
val text = buildAnnotatedString {
|
onClick = {
|
||||||
val (text, color) =
|
scope.launch {
|
||||||
if (it.second.useManager) stringResource(R.string.patch_local) to MaterialTheme.colorScheme.secondary
|
scopeApp = it.first.app.packageName
|
||||||
else stringResource(R.string.patch_portable) to MaterialTheme.colorScheme.tertiary
|
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
|
||||||
append(AnnotatedString(text, SpanStyle(color = color)))
|
navController.currentBackStackEntry!!.setState(
|
||||||
append(" ")
|
"selected",
|
||||||
append(it.second.lspConfig.VERSION_CODE.toString())
|
SnapshotStateList<AppInfo>().apply {
|
||||||
}
|
LSPPackageManager.appList.filterTo(this) { activated.contains(it.app.packageName) }
|
||||||
Text(
|
}
|
||||||
text = text,
|
)
|
||||||
fontWeight = FontWeight.SemiBold,
|
navController.navigate(PageList.SelectApps.name + "?multiSelect=true")
|
||||||
fontFamily = FontFamily.Serif,
|
}
|
||||||
style = MaterialTheme.typography.bodySmall
|
},
|
||||||
|
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 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||||
import org.lsposed.lspatch.ui.util.*
|
import org.lsposed.lspatch.ui.util.*
|
||||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
|
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
|
||||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
|
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
|
||||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||||
import org.lsposed.lspatch.util.ShizukuApi
|
import org.lsposed.lspatch.util.ShizukuApi
|
||||||
|
|
@ -71,7 +72,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
LSPPackageManager.getAppInfoFromApks(apks)
|
LSPPackageManager.getAppInfoFromApks(apks)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
viewModel.configurePatch(it)
|
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") }
|
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"))
|
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||||
"applist" -> {
|
"applist" -> {
|
||||||
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
|
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
|
||||||
viewModel.configurePatch(it)
|
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||||
}
|
}
|
||||||
navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
|
navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +112,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
if (viewModel.patchState == PatchState.CONFIGURING) {
|
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
entry.savedStateHandle.getLiveData<SnapshotStateList<AppInfo>>("selected", SnapshotStateList()).observe(lifecycleOwner) {
|
entry.savedStateHandle.getLiveData("selected", SnapshotStateList<AppInfo>()).observe(lifecycleOwner) {
|
||||||
viewModel.embeddedModules = it
|
viewModel.embeddedModules = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +143,7 @@ private fun ConfiguringFab() {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(stringResource(R.string.patch_start)) },
|
text = { Text(stringResource(R.string.patch_start)) },
|
||||||
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
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) {
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.logs.isEmpty()) {
|
if (viewModel.logs.isEmpty()) {
|
||||||
viewModel.launchPatch()
|
viewModel.dispatch(ViewAction.LaunchPatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,9 +289,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
||||||
.animateContentSize(spring(stiffness = Spring.StiffnessLow))
|
.animateContentSize(spring(stiffness = Spring.StiffnessLow))
|
||||||
) {
|
) {
|
||||||
ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) {
|
ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) {
|
||||||
CompositionLocalProvider(
|
ProvideTextStyle(MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)) {
|
||||||
LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)
|
|
||||||
) {
|
|
||||||
val scrollState = rememberLazyListState()
|
val scrollState = rememberLazyListState()
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = scrollState,
|
state = scrollState,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
||||||
val filter: (AppInfo) -> Boolean = {
|
val filter: (AppInfo) -> Boolean = {
|
||||||
val packageLowerCase = searchPackage.toLowerCase(Locale.current)
|
val packageLowerCase = searchPackage.toLowerCase(Locale.current)
|
||||||
val contains = it.label.toLowerCase(Locale.current).contains(packageLowerCase) || it.app.packageName.contains(packageLowerCase)
|
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
|
else contains && it.app.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,9 +94,13 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
||||||
@Composable
|
@Composable
|
||||||
private fun MultiSelectFab() {
|
private fun MultiSelectFab() {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
FloatingActionButton(onClick = { navController.popBackStack() }) {
|
FloatingActionButton(
|
||||||
Icon(Icons.Outlined.Done, stringResource(R.string.add))
|
onClick = {
|
||||||
}
|
navController.previousBackStackEntry!!.setState("isCancelled", false)
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
content = { Icon(Icons.Outlined.Done, stringResource(R.string.add)) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,10 @@ private fun KeyStore() {
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.settings_keystore_custom)) },
|
text = { Text(stringResource(R.string.settings_keystore_custom)) },
|
||||||
onClick = { showDialog = true }
|
onClick = {
|
||||||
|
dropDownExpanded = false
|
||||||
|
showDialog = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +163,10 @@ private fun KeyStore() {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
LaunchedEffect(interactionSource) {
|
LaunchedEffect(interactionSource) {
|
||||||
interactionSource.interactions.collect { interaction ->
|
interactionSource.interactions.collect { interaction ->
|
||||||
|
|
@ -178,9 +184,7 @@ private fun KeyStore() {
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(bottom = 8.dp),
|
|
||||||
text = wrongText ?: stringResource(R.string.settings_keystore_desc),
|
text = wrongText ?: stringResource(R.string.settings_keystore_desc),
|
||||||
color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified
|
color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ class NewPatchViewModel : ViewModel() {
|
||||||
SELECTING, CONFIGURING, PATCHING, FINISHED, ERROR
|
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)
|
var patchState by mutableStateOf(PatchState.SELECTING)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -31,10 +37,10 @@ class NewPatchViewModel : ViewModel() {
|
||||||
var overrideVersionCode by mutableStateOf(false)
|
var overrideVersionCode by mutableStateOf(false)
|
||||||
val sign = mutableStateListOf(false, true)
|
val sign = mutableStateListOf(false, true)
|
||||||
var sigBypassLevel by mutableStateOf(2)
|
var sigBypassLevel by mutableStateOf(2)
|
||||||
|
var embeddedModules = SnapshotStateList<AppInfo>()
|
||||||
|
|
||||||
lateinit var patchApp: AppInfo
|
lateinit var patchApp: AppInfo
|
||||||
private set
|
private set
|
||||||
lateinit var embeddedModules: SnapshotStateList<AppInfo>
|
|
||||||
lateinit var patchOptions: Patcher.Options
|
lateinit var patchOptions: Patcher.Options
|
||||||
private set
|
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}")
|
Log.d(TAG, "Configuring patch for ${app.app.packageName}")
|
||||||
patchApp = app
|
patchApp = app
|
||||||
patchState = PatchState.CONFIGURING
|
patchState = PatchState.CONFIGURING
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitPatch() {
|
private fun submitPatch() {
|
||||||
Log.d(TAG, "Submit patch")
|
Log.d(TAG, "Submit patch")
|
||||||
if (useManager) embeddedModules.clear()
|
if (useManager) embeddedModules.clear()
|
||||||
patchOptions = Patcher.Options(
|
patchOptions = Patcher.Options(
|
||||||
|
|
@ -80,7 +94,7 @@ class NewPatchViewModel : ViewModel() {
|
||||||
patchState = PatchState.PATCHING
|
patchState = PatchState.PATCHING
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchPatch() {
|
private fun launchPatch() {
|
||||||
logger.i("Launch patch")
|
logger.i("Launch patch")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
patchState = try {
|
patchState = try {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
||||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||||
|
import org.lsposed.lspatch.config.ConfigManager
|
||||||
import org.lsposed.lspatch.lspApp
|
import org.lsposed.lspatch.lspApp
|
||||||
import org.lsposed.patch.util.ManifestParser
|
import org.lsposed.patch.util.ManifestParser
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -35,7 +36,10 @@ object LSPPackageManager {
|
||||||
private const val TAG = "LSPPackageManager"
|
private const val TAG = "LSPPackageManager"
|
||||||
|
|
||||||
@Parcelize
|
@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
|
const val STATUS_USER_CANCELLED = -2
|
||||||
|
|
||||||
|
|
@ -53,7 +57,11 @@ object LSPPackageManager {
|
||||||
collection.add(AppInfo(it, label.toString()))
|
collection.add(AppInfo(it, label.toString()))
|
||||||
appIcon[it.packageName] = pm.getApplicationIcon(it)
|
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
|
appList = collection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -10,6 +10,7 @@ pluginManagement {
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library") version agpVersion
|
id("com.android.library") version agpVersion
|
||||||
id("com.android.application") 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"
|
id("dev.rikka.tools.refine") version "3.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue