You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
Improve Platform (#345)
* Add option to use WebUI X - Added WebUI X from MMRL to KernelSU Next * Some improvements * Again some improvements * Fix NPE * Still not fixed Use mappings from the action to decode the stacktrace * bump mmrl does still not work * Works * WORKS * small fixes * manager: only launch webui x when Platform.isAlive is `true` * manager: disable "Use WebUI X" option when Platform.isAlive is `false` * manager: improve app list loading * manager: use system packageManager api * ci: add spoofed manager workflow * ci: add spoofed manager workflow --------- Co-authored-by: rifsxd <rifat.44.azad.rifs@gmail.com> Co-authored-by: Rifat Azad <33044977+rifsxd@users.noreply.github.com>
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
package com.rifsxd.ksunext.ui
|
||||
|
||||
import android.app.ActivityThread
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.IBinder
|
||||
import android.os.UserManager
|
||||
import android.util.Log
|
||||
import com.dergoogler.mmrl.platform.content.IService
|
||||
import com.dergoogler.mmrl.platform.stub.IServiceManager
|
||||
import com.rifsxd.ksunext.IKsuInterface
|
||||
import com.topjohnwu.superuser.ipc.RootService.USER_SERVICE
|
||||
import rikka.parcelablelist.ParcelableListSlice
|
||||
|
||||
class KsuService: IService {
|
||||
override val name: String
|
||||
get() = "ksuService"
|
||||
|
||||
override fun create(manager: IServiceManager): IBinder = KsuServiceImpl()
|
||||
}
|
||||
|
||||
class KsuServiceImpl : IKsuInterface.Stub() {
|
||||
val context: Context
|
||||
get() {
|
||||
var context: Context = ActivityThread.currentApplication()
|
||||
while (context is ContextWrapper) {
|
||||
context = context.baseContext
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
|
||||
override fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||
val list: List<PackageInfo> = getInstalledPackagesAll(flags)
|
||||
Log.i(TAG, "getPackages: " + list.size)
|
||||
return ParcelableListSlice(list)
|
||||
}
|
||||
|
||||
private val userIds: List<Int>
|
||||
get() {
|
||||
val result: MutableList<Int> =
|
||||
ArrayList()
|
||||
val um =
|
||||
context.getSystemService(USER_SERVICE) as UserManager
|
||||
val userProfiles = um.userProfiles
|
||||
for (userProfile in userProfiles) {
|
||||
val userId = userProfile.hashCode()
|
||||
result.add(userProfile.hashCode())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getInstalledPackagesAll(flags: Int): ArrayList<PackageInfo> {
|
||||
val packages = ArrayList<PackageInfo>()
|
||||
for (userId in userIds) {
|
||||
Log.i(TAG, "getInstalledPackagesAll: $userId")
|
||||
packages.addAll(getInstalledPackagesAsUser(flags, userId))
|
||||
}
|
||||
return packages
|
||||
}
|
||||
|
||||
private fun getInstalledPackagesAsUser(flags: Int, userId: Int): List<PackageInfo> {
|
||||
try {
|
||||
val pm = context.packageManager
|
||||
val getInstalledPackagesAsUser = pm.javaClass.getDeclaredMethod(
|
||||
"getInstalledPackagesAsUser",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
|
||||
return getInstalledPackagesAsUser.invoke(pm, flags, userId) as List<PackageInfo>
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "err", e)
|
||||
}
|
||||
|
||||
return ArrayList()
|
||||
}
|
||||
|
||||
private companion object Default {
|
||||
const val TAG = "KsuService"
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||
@@ -298,7 +299,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
onClickModule = { id, name, hasWebUi ->
|
||||
if (hasWebUi) {
|
||||
webUILauncher.launch(
|
||||
if (prefs.getBoolean("use_webuix", false)) {
|
||||
if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
|
||||
Intent(context, WebUIXActivity::class.java)
|
||||
.setData(Uri.parse("kernelsu://webuix/$id"))
|
||||
.putExtra("id", id)
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
@@ -328,6 +329,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
enabled = Platform.isAlive,
|
||||
icon = Icons.Filled.WebAsset,
|
||||
title = stringResource(id = R.string.use_webuix),
|
||||
summary = stringResource(id = R.string.use_webuix_summary),
|
||||
|
||||
@@ -44,116 +44,93 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val listState = rememberLazyListState()
|
||||
var isPlatformAlive by remember { mutableStateOf(false) }
|
||||
|
||||
// Continuously check if Platform.isAlive
|
||||
LaunchedEffect(Unit) {
|
||||
while (!isPlatformAlive) {
|
||||
isPlatformAlive = Platform.isAlive
|
||||
if (!isPlatformAlive) {
|
||||
kotlinx.coroutines.delay(500) // Check every 500ms
|
||||
}
|
||||
LaunchedEffect(navigator) {
|
||||
viewModel.search = ""
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPlatformAlive) {
|
||||
// Show loading screen while Platform.isAlive is false
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
LaunchedEffect(viewModel.search) {
|
||||
if (viewModel.search.isEmpty()) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(key1 = navigator) {
|
||||
viewModel.search = ""
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.refreshOnReturn) {
|
||||
viewModel.fetchAppList()
|
||||
viewModel.refreshOnReturn = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewModel.search) {
|
||||
if (viewModel.search.isEmpty()) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.superuser)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
dropdownContent = {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.refreshOnReturn) {
|
||||
viewModel.fetchAppList()
|
||||
viewModel.refreshOnReturn = false
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = { showDropdown = true },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.superuser)) },
|
||||
searchText = viewModel.search,
|
||||
onSearchTextChange = { viewModel.search = it },
|
||||
onClearClick = { viewModel.search = "" },
|
||||
dropdownContent = {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
onClick = { showDropdown = true },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(stringResource(R.string.refresh))
|
||||
}, onClick = {
|
||||
scope.launch {
|
||||
viewModel.fetchAppList()
|
||||
}
|
||||
showDropdown = false
|
||||
}) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(stringResource(R.string.refresh))
|
||||
}, onClick = {
|
||||
scope.launch {
|
||||
viewModel.fetchAppList()
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(
|
||||
if (viewModel.showSystemApps) {
|
||||
stringResource(R.string.hide_system_apps)
|
||||
} else {
|
||||
stringResource(R.string.show_system_apps)
|
||||
}
|
||||
showDropdown = false
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(
|
||||
if (viewModel.showSystemApps) {
|
||||
stringResource(R.string.hide_system_apps)
|
||||
} else {
|
||||
stringResource(R.string.show_system_apps)
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
)
|
||||
}, onClick = {
|
||||
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||
showDropdown = false
|
||||
})
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { innerPadding ->
|
||||
PullToRefreshBox(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.fetchAppList() }
|
||||
}
|
||||
},
|
||||
isRefreshing = viewModel.isRefreshing
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
) { innerPadding ->
|
||||
PullToRefreshBox(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.fetchAppList() }
|
||||
},
|
||||
isRefreshing = viewModel.isRefreshing
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) {
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
viewModel.refreshOnReturn = true
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||
AppItem(app) {
|
||||
viewModel.refreshOnReturn = true
|
||||
navigator.navigate(AppProfileScreenDestination(app))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,22 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
||||
import com.rifsxd.ksunext.ui.webui.getPackages
|
||||
import com.rifsxd.ksunext.ui.webui.packageManager
|
||||
import com.rifsxd.ksunext.ui.webui.userManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
|
||||
class SuperUserViewModel : ViewModel() {
|
||||
val isPlatformAlive get() = Platform.isAlive
|
||||
|
||||
var refreshOnReturn by mutableStateOf(false)
|
||||
public set
|
||||
@@ -93,11 +98,25 @@ class SuperUserViewModel : ViewModel() {
|
||||
suspend fun fetchAppList() {
|
||||
isRefreshing = true
|
||||
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
||||
while (!isPlatformAlive) {
|
||||
delay(500)
|
||||
}
|
||||
} ?: return@withContext // Exit early if timeout
|
||||
|
||||
val pm = ksuApp.packageManager
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
|
||||
val packages = Platform.getPackages(0).list
|
||||
val userInfos = Platform.userManager.getUsers()
|
||||
val packages = mutableListOf<PackageInfo>()
|
||||
val packageManager = Platform.packageManager
|
||||
|
||||
for (userInfo in userInfos) {
|
||||
Log.i(TAG, "fetchAppList: ${userInfo.id}")
|
||||
packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
|
||||
}
|
||||
|
||||
apps = packages.map {
|
||||
val appInfo = it.applicationInfo
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
package com.rifsxd.ksunext.ui.webui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageInfo
|
||||
import android.util.Log
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import com.dergoogler.mmrl.platform.content.Service
|
||||
import com.dergoogler.mmrl.platform.hiddenApi.HiddenPackageManager
|
||||
import com.dergoogler.mmrl.platform.hiddenApi.HiddenUserManager
|
||||
import com.dergoogler.mmrl.platform.model.IProvider
|
||||
import com.dergoogler.mmrl.platform.model.PlatformIntent
|
||||
import com.rifsxd.ksunext.IKsuInterface
|
||||
import com.rifsxd.ksunext.Natives
|
||||
import com.rifsxd.ksunext.ksuApp
|
||||
import com.rifsxd.ksunext.ui.KsuService
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import rikka.parcelablelist.ParcelableListSlice
|
||||
|
||||
class KsuLibSuProvider : IProvider {
|
||||
override val name = "KsuLibSu"
|
||||
@@ -53,10 +49,6 @@ suspend fun initPlatform() = withContext(Dispatchers.IO) {
|
||||
delay(1000)
|
||||
}
|
||||
|
||||
Platform.mService.addService(
|
||||
Service(KsuService::class.java)
|
||||
)
|
||||
|
||||
return@withContext active
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuLibSu", "Failed to initialize platform", e)
|
||||
@@ -64,12 +56,5 @@ suspend fun initPlatform() = withContext(Dispatchers.IO) {
|
||||
}
|
||||
}
|
||||
|
||||
fun Platform.Companion.getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||
val ksuService: IKsuInterface by lazy {
|
||||
IKsuInterface.Stub.asInterface(
|
||||
this.mService.getService("ksuService")
|
||||
)
|
||||
}
|
||||
|
||||
return ksuService.getPackages(flags)
|
||||
}
|
||||
val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
|
||||
val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)
|
||||
@@ -17,7 +17,7 @@ parcelablelist = "2.0.1"
|
||||
libsu = "6.0.0"
|
||||
apksign = "1.4"
|
||||
cmaker = "1.2"
|
||||
mmrl = "785605169b"
|
||||
mmrl = "b3c7ca55e0"
|
||||
|
||||
[plugins]
|
||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user