diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/KsuService.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/KsuService.kt deleted file mode 100644 index 38af3008..00000000 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/KsuService.kt +++ /dev/null @@ -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 { - val list: List = getInstalledPackagesAll(flags) - Log.i(TAG, "getPackages: " + list.size) - return ParcelableListSlice(list) - } - - private val userIds: List - get() { - val result: MutableList = - 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 { - val packages = ArrayList() - for (userId in userIds) { - Log.i(TAG, "getInstalledPackagesAll: $userId") - packages.addAll(getInstalledPackagesAsUser(flags, userId)) - } - return packages - } - - private fun getInstalledPackagesAsUser(flags: Int, userId: Int): List { - 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 - } catch (e: Throwable) { - Log.e(TAG, "err", e) - } - - return ArrayList() - } - - private companion object Default { - const val TAG = "KsuService" - } -} diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Module.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Module.kt index 849aad41..35dd7b19 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Module.kt @@ -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) diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt index 3ef43c0d..5fafcd9c 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/Settings.kt @@ -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), diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/SuperUser.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/SuperUser.kt index 78534981..be369b1b 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/SuperUser.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/screen/SuperUser.kt @@ -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)) } } } diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/viewmodel/SuperUserViewModel.kt index 2b38d172..2288fd5f 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/viewmodel/SuperUserViewModel.kt @@ -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() + 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 diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/KsuLibSuProvider.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/KsuLibSuProvider.kt index e534751a..abd43be9 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/KsuLibSuProvider.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/KsuLibSuProvider.kt @@ -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 { - val ksuService: IKsuInterface by lazy { - IKsuInterface.Stub.asInterface( - this.mService.getService("ksuService") - ) - } - - return ksuService.getPackages(flags) -} \ No newline at end of file +val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService) +val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService) \ No newline at end of file diff --git a/manager/gradle/libs.versions.toml b/manager/gradle/libs.versions.toml index 6c603d20..108e30b9 100644 --- a/manager/gradle/libs.versions.toml +++ b/manager/gradle/libs.versions.toml @@ -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" }