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.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||||
@@ -298,7 +299,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
onClickModule = { id, name, hasWebUi ->
|
onClickModule = { id, name, hasWebUi ->
|
||||||
if (hasWebUi) {
|
if (hasWebUi) {
|
||||||
webUILauncher.launch(
|
webUILauncher.launch(
|
||||||
if (prefs.getBoolean("use_webuix", false)) {
|
if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
|
||||||
Intent(context, WebUIXActivity::class.java)
|
Intent(context, WebUIXActivity::class.java)
|
||||||
.setData(Uri.parse("kernelsu://webuix/$id"))
|
.setData(Uri.parse("kernelsu://webuix/$id"))
|
||||||
.putExtra("id", 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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.FileProvider
|
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.Header
|
||||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
import com.maxkeppeker.sheets.core.models.base.IconSource
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||||
@@ -328,6 +329,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
if (ksuVersion != null) {
|
if (ksuVersion != null) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
|
enabled = Platform.isAlive,
|
||||||
icon = Icons.Filled.WebAsset,
|
icon = Icons.Filled.WebAsset,
|
||||||
title = stringResource(id = R.string.use_webuix),
|
title = stringResource(id = R.string.use_webuix),
|
||||||
summary = stringResource(id = R.string.use_webuix_summary),
|
summary = stringResource(id = R.string.use_webuix_summary),
|
||||||
|
|||||||
@@ -44,116 +44,93 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
var isPlatformAlive by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Continuously check if Platform.isAlive
|
LaunchedEffect(navigator) {
|
||||||
LaunchedEffect(Unit) {
|
viewModel.search = ""
|
||||||
while (!isPlatformAlive) {
|
if (viewModel.appList.isEmpty()) {
|
||||||
isPlatformAlive = Platform.isAlive
|
viewModel.fetchAppList()
|
||||||
if (!isPlatformAlive) {
|
|
||||||
kotlinx.coroutines.delay(500) // Check every 500ms
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPlatformAlive) {
|
LaunchedEffect(viewModel.search) {
|
||||||
// Show loading screen while Platform.isAlive is false
|
if (viewModel.search.isEmpty()) {
|
||||||
Box(
|
listState.scrollToItem(0)
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(MaterialTheme.colorScheme.background),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
LaunchedEffect(key1 = navigator) {
|
|
||||||
viewModel.search = ""
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.appList.isEmpty()) {
|
if (viewModel.refreshOnReturn) {
|
||||||
viewModel.fetchAppList()
|
viewModel.fetchAppList()
|
||||||
}
|
viewModel.refreshOnReturn = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(viewModel.search) {
|
Scaffold(
|
||||||
if (viewModel.search.isEmpty()) {
|
topBar = {
|
||||||
listState.scrollToItem(0)
|
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) {
|
IconButton(
|
||||||
if (viewModel.refreshOnReturn) {
|
onClick = { showDropdown = true },
|
||||||
viewModel.fetchAppList()
|
) {
|
||||||
viewModel.refreshOnReturn = false
|
Icon(
|
||||||
}
|
imageVector = Icons.Filled.MoreVert,
|
||||||
}
|
contentDescription = stringResource(id = R.string.settings)
|
||||||
|
)
|
||||||
|
|
||||||
Scaffold(
|
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||||
topBar = {
|
showDropdown = false
|
||||||
SearchAppBar(
|
}) {
|
||||||
title = { Text(stringResource(R.string.superuser)) },
|
DropdownMenuItem(text = {
|
||||||
searchText = viewModel.search,
|
Text(stringResource(R.string.refresh))
|
||||||
onSearchTextChange = { viewModel.search = it },
|
}, onClick = {
|
||||||
onClearClick = { viewModel.search = "" },
|
scope.launch {
|
||||||
dropdownContent = {
|
viewModel.fetchAppList()
|
||||||
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 = {
|
|
||||||
showDropdown = false
|
showDropdown = false
|
||||||
}) {
|
})
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
Text(stringResource(R.string.refresh))
|
Text(
|
||||||
}, onClick = {
|
if (viewModel.showSystemApps) {
|
||||||
scope.launch {
|
stringResource(R.string.hide_system_apps)
|
||||||
viewModel.fetchAppList()
|
} else {
|
||||||
|
stringResource(R.string.show_system_apps)
|
||||||
}
|
}
|
||||||
showDropdown = false
|
)
|
||||||
})
|
}, onClick = {
|
||||||
DropdownMenuItem(text = {
|
viewModel.showSystemApps = !viewModel.showSystemApps
|
||||||
Text(
|
showDropdown = false
|
||||||
if (viewModel.showSystemApps) {
|
})
|
||||||
stringResource(R.string.hide_system_apps)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.show_system_apps)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}, 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(
|
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
|
||||||
state = listState,
|
AppItem(app) {
|
||||||
modifier = Modifier
|
viewModel.refreshOnReturn = true
|
||||||
.fillMaxSize()
|
navigator.navigate(AppProfileScreenDestination(app))
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
|
||||||
) {
|
|
||||||
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.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.rifsxd.ksunext.Natives
|
import com.rifsxd.ksunext.Natives
|
||||||
import com.rifsxd.ksunext.ksuApp
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.ui.util.HanziToPinyin
|
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.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
|
val isPlatformAlive get() = Platform.isAlive
|
||||||
|
|
||||||
var refreshOnReturn by mutableStateOf(false)
|
var refreshOnReturn by mutableStateOf(false)
|
||||||
public set
|
public set
|
||||||
@@ -93,11 +98,25 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
suspend fun fetchAppList() {
|
suspend fun fetchAppList() {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
||||||
|
while (!isPlatformAlive) {
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
} ?: return@withContext // Exit early if timeout
|
||||||
|
|
||||||
val pm = ksuApp.packageManager
|
val pm = ksuApp.packageManager
|
||||||
val start = SystemClock.elapsedRealtime()
|
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 {
|
apps = packages.map {
|
||||||
val appInfo = it.applicationInfo
|
val appInfo = it.applicationInfo
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
package com.rifsxd.ksunext.ui.webui
|
package com.rifsxd.ksunext.ui.webui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
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.IProvider
|
||||||
import com.dergoogler.mmrl.platform.model.PlatformIntent
|
import com.dergoogler.mmrl.platform.model.PlatformIntent
|
||||||
import com.rifsxd.ksunext.IKsuInterface
|
|
||||||
import com.rifsxd.ksunext.Natives
|
import com.rifsxd.ksunext.Natives
|
||||||
import com.rifsxd.ksunext.ksuApp
|
import com.rifsxd.ksunext.ksuApp
|
||||||
import com.rifsxd.ksunext.ui.KsuService
|
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import rikka.parcelablelist.ParcelableListSlice
|
|
||||||
|
|
||||||
class KsuLibSuProvider : IProvider {
|
class KsuLibSuProvider : IProvider {
|
||||||
override val name = "KsuLibSu"
|
override val name = "KsuLibSu"
|
||||||
@@ -53,10 +49,6 @@ suspend fun initPlatform() = withContext(Dispatchers.IO) {
|
|||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.mService.addService(
|
|
||||||
Service(KsuService::class.java)
|
|
||||||
)
|
|
||||||
|
|
||||||
return@withContext active
|
return@withContext active
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("KsuLibSu", "Failed to initialize platform", e)
|
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 Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
|
||||||
val ksuService: IKsuInterface by lazy {
|
val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)
|
||||||
IKsuInterface.Stub.asInterface(
|
|
||||||
this.mService.getService("ksuService")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ksuService.getPackages(flags)
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ parcelablelist = "2.0.1"
|
|||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
apksign = "1.4"
|
apksign = "1.4"
|
||||||
cmaker = "1.2"
|
cmaker = "1.2"
|
||||||
mmrl = "785605169b"
|
mmrl = "b3c7ca55e0"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user