diff --git a/manager/app/build.gradle.kts b/manager/app/build.gradle.kts index 01f7884a..df821b47 100644 --- a/manager/app/build.gradle.kts +++ b/manager/app/build.gradle.kts @@ -133,4 +133,9 @@ dependencies { implementation(libs.androidx.webkit) implementation(libs.lsposed.cxx) + + implementation(libs.mmrl.platform) + compileOnly(libs.mmrl.hidden.api) + implementation(libs.mmrl.ui) + implementation(libs.mmrl.webui) } \ No newline at end of file diff --git a/manager/app/proguard-rules.pro b/manager/app/proguard-rules.pro index e69de29b..5420195e 100644 --- a/manager/app/proguard-rules.pro +++ b/manager/app/proguard-rules.pro @@ -0,0 +1,47 @@ +-verbose +-optimizationpasses 5 + +-dontwarn org.conscrypt.** +-dontwarn kotlinx.serialization.** + +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn com.google.auto.service.AutoService +-dontwarn com.google.j2objc.annotations.RetainedWith +-dontwarn javax.lang.model.SourceVersion +-dontwarn javax.lang.model.element.AnnotationMirror +-dontwarn javax.lang.model.element.AnnotationValue +-dontwarn javax.lang.model.element.Element +-dontwarn javax.lang.model.element.ElementKind +-dontwarn javax.lang.model.element.ElementVisitor +-dontwarn javax.lang.model.element.ExecutableElement +-dontwarn javax.lang.model.element.Modifier +-dontwarn javax.lang.model.element.Name +-dontwarn javax.lang.model.element.PackageElement +-dontwarn javax.lang.model.element.TypeElement +-dontwarn javax.lang.model.element.TypeParameterElement +-dontwarn javax.lang.model.element.VariableElement +-dontwarn javax.lang.model.type.ArrayType +-dontwarn javax.lang.model.type.DeclaredType +-dontwarn javax.lang.model.type.ExecutableType +-dontwarn javax.lang.model.type.TypeKind +-dontwarn javax.lang.model.type.TypeMirror +-dontwarn javax.lang.model.type.TypeVariable +-dontwarn javax.lang.model.type.TypeVisitor +-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8 +-dontwarn javax.lang.model.util.AbstractTypeVisitor8 +-dontwarn javax.lang.model.util.ElementFilter +-dontwarn javax.lang.model.util.Elements +-dontwarn javax.lang.model.util.SimpleElementVisitor8 +-dontwarn javax.lang.model.util.SimpleTypeVisitor7 +-dontwarn javax.lang.model.util.SimpleTypeVisitor8 +-dontwarn javax.lang.model.util.Types +-dontwarn javax.tools.Diagnostic$Kind + + +# MMRL:webui reflection +-keep class com.dergoogler.mmrl.webui.model.ModId { *; } +-keep class com.dergoogler.mmrl.webui.interfaces.** { *; } +-keep class com.rifsxd.ksunext.ui.webui.WebViewInterface { *; } + +-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; } \ No newline at end of file diff --git a/manager/app/src/main/AndroidManifest.xml b/manager/app/src/main/AndroidManifest.xml index 11cda5f2..c3ec0dea 100644 --- a/manager/app/src/main/AndroidManifest.xml +++ b/manager/app/src/main/AndroidManifest.xml @@ -33,6 +33,13 @@ android:exported="false" android:theme="@style/Theme.KernelSU.WebUI" /> + + getPackages(int flags) { - List list = getInstalledPackagesAll(flags); - Log.i(TAG, "getPackages: " + list.size()); - return new ParcelableListSlice<>(list); - } - } - - @Override - public IBinder onBind(@NonNull Intent intent) { - return new Stub(); - } - - List getUserIds() { - List result = new ArrayList<>(); - UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); - List userProfiles = um.getUserProfiles(); - for (UserHandle userProfile : userProfiles) { - int userId = userProfile.hashCode(); - result.add(userProfile.hashCode()); - } - return result; - } - - ArrayList getInstalledPackagesAll(int flags) { - ArrayList packages = new ArrayList<>(); - for (Integer userId : getUserIds()) { - Log.i(TAG, "getInstalledPackagesAll: " + userId); - packages.addAll(getInstalledPackagesAsUser(flags, userId)); - } - return packages; - } - - List getInstalledPackagesAsUser(int flags, int userId) { - try { - PackageManager pm = getPackageManager(); - Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class); - return (List) getInstalledPackagesAsUser.invoke(pm, flags, userId); - } catch (Throwable e) { - Log.e(TAG, "err", e); - } - - return new ArrayList<>(); - } -} 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 new file mode 100644 index 00000000..38af3008 --- /dev/null +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/KsuService.kt @@ -0,0 +1,84 @@ +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/MainActivity.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/MainActivity.kt index 7e4847ca..e127a7ab 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/MainActivity.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/MainActivity.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -38,6 +39,7 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.currentBackStackEntryAsState +import com.dergoogler.mmrl.platform.Platform import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination @@ -52,7 +54,7 @@ import com.rifsxd.ksunext.ui.theme.KernelSUTheme import com.rifsxd.ksunext.ui.util.LocalSnackbarHost import com.rifsxd.ksunext.ui.util.rootAvailable import com.rifsxd.ksunext.ui.util.install -import com.rifsxd.ksunext.ui.util.* +import com.rifsxd.ksunext.ui.webui.initPlatform class MainActivity : ComponentActivity() { @@ -67,7 +69,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) val isManager = Natives.becomeManager(ksuApp.packageName) - if (isManager) install() + if (isManager) install() setContent { KernelSUTheme { @@ -81,6 +83,11 @@ class MainActivity : ComponentActivity() { else -> true } + // pre-init platform to faster start WebUI X activities + LaunchedEffect(Unit) { + initPlatform() + } + Scaffold( bottomBar = { AnimatedVisibility( @@ -118,7 +125,7 @@ class MainActivity : ComponentActivity() { private fun BottomBar(navController: NavHostController) { val navigator = navController.rememberDestinationsNavigator() val isManager = Natives.becomeManager(ksuApp.packageName) - val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() + val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() && Platform.isAlive NavigationBar( tonalElevation = 8.dp, windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only( 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 6aedac43..849aad41 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 @@ -108,6 +108,7 @@ import com.rifsxd.ksunext.ui.util.uninstallModule import com.rifsxd.ksunext.ui.util.restoreModule import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel import com.rifsxd.ksunext.ui.webui.WebUIActivity +import com.rifsxd.ksunext.ui.webui.WebUIXActivity @OptIn(ExperimentalMaterial3Api::class) @Destination @@ -238,15 +239,18 @@ fun ModuleScreen(navigator: DestinationsNavigator) { } // Show confirm dialog with selected zip file(s) name(s) - val moduleNames = uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }.joinToString("") - val confirmContent = context.getString(R.string.module_install_prompt_with_name, moduleNames) + val moduleNames = + uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" } + .joinToString("") + val confirmContent = + context.getString(R.string.module_install_prompt_with_name, moduleNames) zipUris = uris confirmDialog.showConfirm( title = confirmTitle, content = confirmContent, markdown = true ) - + } ExtendedFloatingActionButton( @@ -281,6 +285,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) } } + else -> { ModuleList( navigator, @@ -293,10 +298,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) { onClickModule = { id, name, hasWebUi -> if (hasWebUi) { webUILauncher.launch( - Intent(context, WebUIActivity::class.java) - .setData(Uri.parse("kernelsu://webui/$id")) - .putExtra("id", id) - .putExtra("name", name) + if (prefs.getBoolean("use_webuix", false)) { + Intent(context, WebUIXActivity::class.java) + .setData(Uri.parse("kernelsu://webuix/$id")) + .putExtra("id", id) + .putExtra("name", name) + } else { + Intent(context, WebUIActivity::class.java) + .setData(Uri.parse("kernelsu://webui/$id")) + .putExtra("id", id) + .putExtra("name", name) + } ) } }, @@ -318,7 +330,7 @@ private fun ModuleList( onInstallModule: (Uri) -> Unit, onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit, context: Context, - snackBarHost: SnackbarHostState + snackBarHost: SnackbarHostState, ) { val failedEnable = stringResource(R.string.module_failed_to_enable) val failedDisable = stringResource(R.string.module_failed_to_disable) @@ -342,7 +354,8 @@ private fun ModuleList( val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - val hasShownWarning = rememberSaveable { mutableStateOf(prefs.getBoolean("has_shown_warning", false)) } + val hasShownWarning = + rememberSaveable { mutableStateOf(prefs.getBoolean("has_shown_warning", false)) } var useOverlayFs by rememberSaveable { mutableStateOf( @@ -357,7 +370,7 @@ private fun ModuleList( module: ModuleViewModel.ModuleInfo, changelogUrl: String, downloadUrl: String, - fileName: String + fileName: String, ) { val changelogResult = loadingDialog.withLoading { withContext(Dispatchers.IO) { @@ -570,7 +583,8 @@ private fun ModuleList( reboot() } } else { - val message = if (module.enabled) failedDisable else failedEnable + val message = + if (module.enabled) failedDisable else failedEnable snackBarHost.showSnackbar(message.format(module.name)) } } @@ -611,7 +625,7 @@ fun ModuleItem( onRestore: (ModuleViewModel.ModuleInfo) -> Unit, onCheckChanged: (Boolean) -> Unit, onUpdate: (ModuleViewModel.ModuleInfo) -> Unit, - onClick: (ModuleViewModel.ModuleInfo) -> Unit + onClick: (ModuleViewModel.ModuleInfo) -> Unit, ) { ElevatedCard( modifier = Modifier.fillMaxWidth() @@ -620,7 +634,7 @@ fun ModuleItem( val interactionSource = remember { MutableInteractionSource() } val indication = LocalIndication.current val viewModel = viewModel() - + val context = LocalContext.current val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -862,7 +876,7 @@ fun ModuleItem( fontSize = MaterialTheme.typography.labelMedium.fontSize, text = stringResource(R.string.uninstall) ) - } + } } } } 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 381f5d06..c1ee232f 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 @@ -85,6 +85,7 @@ import com.rifsxd.ksunext.ui.util.getBugreportFile import com.rifsxd.ksunext.ui.util.* import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import androidx.core.content.edit /** * @author weishu @@ -162,7 +163,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { title = stringResource(id = R.string.settings_umount_modules_default), summary = stringResource(id = R.string.settings_umount_modules_default_summary), checked = umountChecked - + ) { if (Natives.setDefaultUmountModules(it)) { umountChecked = it @@ -319,6 +320,23 @@ fun SettingScreen(navigator: DestinationsNavigator) { } } + var useWebUIX by rememberSaveable { + mutableStateOf( + prefs.getBoolean("use_webuix", false) + ) + } + if (ksuVersion != null) { + SwitchItem( + icon = Icons.Filled.WebAsset, + title = stringResource(id = R.string.use_webuix), + summary = stringResource(id = R.string.use_webuix_summary), + checked = useWebUIX + ) { + prefs.edit().putBoolean("use_webuix", it).apply() + useWebUIX = it + } + } + if (ksuVersion != null) { val backupRestore = stringResource(id = R.string.backup_restore) ListItem( @@ -496,7 +514,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { @Composable fun UninstallItem( navigator: DestinationsNavigator, - withLoading: suspend (suspend () -> Unit) -> Unit + withLoading: suspend (suspend () -> Unit) -> Unit, ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -598,7 +616,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TopBar( - scrollBehavior: TopAppBarScrollBehavior? = null + scrollBehavior: TopAppBarScrollBehavior? = null, ) { TopAppBar( title = { Text(stringResource(R.string.settings)) }, diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/theme/Theme.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/theme/Theme.kt index 896aa0eb..751708d8 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/theme/Theme.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/theme/Theme.kt @@ -1,6 +1,9 @@ package com.rifsxd.ksunext.ui.theme import android.os.Build +import androidx.activity.SystemBarStyle +import androidx.activity.ComponentActivity +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme @@ -8,6 +11,9 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext private val DarkColorScheme = darkColorScheme( @@ -38,9 +44,42 @@ fun KernelSUTheme( else -> LightColorScheme } + SystemBarStyle( + darkMode = darkTheme + ) + MaterialTheme( colorScheme = colorScheme, typography = Typography, content = content ) } + +@Composable +private fun SystemBarStyle( + darkMode: Boolean, + statusBarScrim: Color = Color.Transparent, + navigationBarScrim: Color = Color.Transparent, +) { + val context = LocalContext.current + val activity = context as ComponentActivity + + SideEffect { + activity.enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + statusBarScrim.toArgb(), + statusBarScrim.toArgb(), + ) { darkMode }, + navigationBarStyle = when { + darkMode -> SystemBarStyle.dark( + navigationBarScrim.toArgb() + ) + + else -> SystemBarStyle.light( + navigationBarScrim.toArgb(), + navigationBarScrim.toArgb(), + ) + } + ) + } +} \ No newline at end of file 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 f2ce58ce..2b38d172 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 @@ -1,11 +1,7 @@ package com.rifsxd.ksunext.ui.viewmodel -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo -import android.os.IBinder import android.os.Parcelable import android.os.SystemClock import android.util.Log @@ -14,20 +10,16 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import com.topjohnwu.superuser.Shell +import com.dergoogler.mmrl.platform.Platform import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -import com.rifsxd.ksunext.IKsuInterface import com.rifsxd.ksunext.Natives import com.rifsxd.ksunext.ksuApp -import com.rifsxd.ksunext.ui.KsuService import com.rifsxd.ksunext.ui.util.HanziToPinyin -import com.rifsxd.ksunext.ui.util.KsuCli +import com.rifsxd.ksunext.ui.webui.getPackages import java.text.Collator import java.util.* -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine class SuperUserViewModel : ViewModel() { @@ -97,55 +89,15 @@ class SuperUserViewModel : ViewModel() { } } - private suspend inline fun connectKsuService( - crossinline onDisconnect: () -> Unit = {} - ): Pair = suspendCoroutine { - val connection = object : ServiceConnection { - override fun onServiceDisconnected(name: ComponentName?) { - onDisconnect() - } - - override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { - it.resume(binder as IBinder to this) - } - } - - val intent = Intent(ksuApp, KsuService::class.java) - - val task = KsuService.bindOrTask( - intent, - Shell.EXECUTOR, - connection, - ) - val shell = KsuCli.SHELL - task?.let { it1 -> shell.execTask(it1) } - } - - private fun stopKsuService() { - val intent = Intent(ksuApp, KsuService::class.java) - KsuService.stop(intent) - } suspend fun fetchAppList() { - isRefreshing = true - val result = connectKsuService { - Log.w(TAG, "KsuService disconnected") - } - withContext(Dispatchers.IO) { val pm = ksuApp.packageManager val start = SystemClock.elapsedRealtime() - val binder = result.first - val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0) - - withContext(Dispatchers.Main) { - stopKsuService() - } - - val packages = allPackages.list + val packages = Platform.getPackages(0).list 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 new file mode 100644 index 00000000..e534751a --- /dev/null +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/KsuLibSuProvider.kt @@ -0,0 +1,75 @@ +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.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" + + override fun isAvailable() = true + + override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName) + + private val serviceIntent + get() = PlatformIntent( + ksuApp, + Platform.KsuNext, + SuService::class.java + ) + + override fun bind(connection: ServiceConnection) { + RootService.bind(serviceIntent.intent, connection) + } + + override fun unbind(connection: ServiceConnection) { + RootService.stop(serviceIntent.intent) + } +} + +suspend fun initPlatform() = withContext(Dispatchers.IO) { + try { + val active = Platform.init { + this.context = ksuApp + this.platform = Platform.KsuNext + this.provider = from(KsuLibSuProvider()) + } + + while (!active) { + delay(1000) + } + + Platform.mService.addService( + Service(KsuService::class.java) + ) + + return@withContext active + } catch (e: Exception) { + Log.e("KsuLibSu", "Failed to initialize platform", e) + return@withContext false + } +} + +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 diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/SuService.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/SuService.kt new file mode 100644 index 00000000..ab1c3b7a --- /dev/null +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/SuService.kt @@ -0,0 +1,14 @@ +package com.rifsxd.ksunext.ui.webui + +import android.content.Intent +import android.os.IBinder +import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform +import com.dergoogler.mmrl.platform.service.ServiceManager +import com.topjohnwu.superuser.ipc.RootService + +class SuService : RootService() { + override fun onBind(intent: Intent): IBinder { + val mode = intent.getPlatform() + return ServiceManager(mode) + } +} diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIActivity.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIActivity.kt index 372e8fde..f582c2c5 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIActivity.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIActivity.kt @@ -15,6 +15,8 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams import androidx.webkit.WebViewAssetLoader +import com.dergoogler.mmrl.webui.interfaces.WXOptions +import com.dergoogler.mmrl.webui.model.ModId import com.topjohnwu.superuser.Shell import com.rifsxd.ksunext.ui.util.createRootShell import java.io.File @@ -41,7 +43,8 @@ class WebUIActivity : ComponentActivity() { @Suppress("DEPRECATION") setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name")) } else { - val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build() + val taskDescription = + ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build() setTaskDescription(taskDescription) } @@ -62,7 +65,7 @@ class WebUIActivity : ComponentActivity() { val webViewClient = object : WebViewClient() { override fun shouldInterceptRequest( view: WebView, - request: WebResourceRequest + request: WebResourceRequest, ): WebResourceResponse? { return webViewAssetLoader.shouldInterceptRequest(request.url) } @@ -82,7 +85,9 @@ class WebUIActivity : ComponentActivity() { settings.javaScriptEnabled = true settings.domStorageEnabled = true settings.allowFileAccess = false - webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir) + webviewInterface = WebViewInterface( + WXOptions(this@WebUIActivity, this, ModId(moduleId)) + ) addJavascriptInterface(webviewInterface, "ksu") setWebViewClient(webViewClient) loadUrl("https://mui.kernelsu.org/index.html") diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIXActivity.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIXActivity.kt new file mode 100644 index 00000000..24b56cfb --- /dev/null +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebUIXActivity.kt @@ -0,0 +1,92 @@ +package com.rifsxd.ksunext.ui.webui + +import android.app.ActivityManager +import android.os.Build +import android.os.Bundle +import android.webkit.WebView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.lifecycleScope +import com.dergoogler.mmrl.platform.Platform +import com.dergoogler.mmrl.ui.component.Loading +import com.dergoogler.mmrl.webui.interfaces.WXOptions +import com.dergoogler.mmrl.webui.model.ModId +import com.dergoogler.mmrl.webui.screen.WebUIScreen +import com.dergoogler.mmrl.webui.util.rememberWebUIOptions +import com.rifsxd.ksunext.BuildConfig +import com.rifsxd.ksunext.ui.theme.KernelSUTheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class WebUIXActivity : ComponentActivity() { + private lateinit var webView: WebView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + webView = WebView(this) + + lifecycleScope.launch { + initPlatform() + } + + val moduleId = intent.getStringExtra("id")!! + val name = intent.getStringExtra("name")!! + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + @Suppress("DEPRECATION") + setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name")) + } else { + val taskDescription = + ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build() + setTaskDescription(taskDescription) + } + + val prefs = getSharedPreferences("settings", MODE_PRIVATE) + + setContent { + KernelSUTheme { + var isLoading by remember { mutableStateOf(true) } + + LaunchedEffect(Platform.isAlive) { + while (!Platform.isAlive) { + delay(1000) + } + + isLoading = false + } + + if (isLoading) { + Loading() + + return@KernelSUTheme + } + + val webDebugging = prefs.getBoolean("enable_web_debugging", false) + val dark = isSystemInDarkTheme() + + val options = rememberWebUIOptions( + modId = ModId(moduleId), + debug = webDebugging, + appVersionCode = BuildConfig.VERSION_CODE, + isDarkMode = dark, + ) + + WebUIScreen( + webView = webView, + options = options, + interfaces = listOf( + WebViewInterface.factory() + ) + ) + } + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebViewInterface.kt b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebViewInterface.kt index 649c71c6..a33c1e09 100644 --- a/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebViewInterface.kt +++ b/manager/app/src/main/java/com/rifsxd/ksunext/ui/webui/WebViewInterface.kt @@ -1,16 +1,17 @@ package com.rifsxd.ksunext.ui.webui import android.app.Activity -import android.content.Context import android.os.Handler import android.os.Looper import android.text.TextUtils import android.view.Window import android.webkit.JavascriptInterface -import android.webkit.WebView import android.widget.Toast import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat +import com.dergoogler.mmrl.webui.interfaces.WXOptions +import com.dergoogler.mmrl.webui.interfaces.WebUIInterface +import com.dergoogler.mmrl.webui.model.JavaScriptInterface import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.internal.UiThreadHandler @@ -23,10 +24,15 @@ import java.io.File import java.util.concurrent.CompletableFuture class WebViewInterface( - val context: Context, - private val webView: WebView, - private val modDir: String -) { + wxOptions: WXOptions, +) : WebUIInterface(wxOptions) { + override var name: String = "ksu" + + companion object { + fun factory() = JavaScriptInterface(WebViewInterface::class.java) + } + + private val modDir get() = "/data/adb/modules/${modId.id}" @JavascriptInterface fun exec(cmd: String): String { @@ -59,7 +65,7 @@ class WebViewInterface( fun exec( cmd: String, options: String?, - callbackFunc: String + callbackFunc: String, ) { val finalCommand = StringBuilder() processOptions(finalCommand, options) @@ -168,9 +174,9 @@ class WebViewInterface( if (context is Activity) { Handler(Looper.getMainLooper()).post { if (enable) { - hideSystemUI(context.window) + hideSystemUI(activity.window) } else { - showSystemUI(context.window) + showSystemUI(activity.window) } } } @@ -202,8 +208,12 @@ class WebViewInterface( fun hideSystemUI(window: Window) = WindowInsetsControllerCompat(window, window.decorView).let { controller -> controller.hide(WindowInsetsCompat.Type.systemBars()) - controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } fun showSystemUI(window: Window) = - WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars()) + WindowInsetsControllerCompat( + window, + window.decorView + ).show(WindowInsetsCompat.Type.systemBars()) diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index b34d6299..b9f26970 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -194,4 +194,6 @@ Disable su compatibility Temporarily disable the ability of any app to gain root privileges via the ⁠su command (existing root processes won\'t be affected). Language + Use WebUI X + Use WebUI X instead of WebUI which supports more API\'s diff --git a/manager/gradle/libs.versions.toml b/manager/gradle/libs.versions.toml index b35619c8..6c603d20 100644 --- a/manager/gradle/libs.versions.toml +++ b/manager/gradle/libs.versions.toml @@ -17,6 +17,7 @@ parcelablelist = "2.0.1" libsu = "6.0.0" apksign = "1.4" cmaker = "1.2" +mmrl = "785605169b" [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } @@ -71,4 +72,9 @@ sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs" markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } -lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } \ No newline at end of file +lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } + +mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" } +mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" } +mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" } +mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" } \ No newline at end of file