diff --git a/manager/app/build.gradle.kts b/manager/app/build.gradle.kts
index b04edb38..2dbbaec6 100644
--- a/manager/app/build.gradle.kts
+++ b/manager/app/build.gradle.kts
@@ -154,4 +154,9 @@ dependencies {
implementation(libs.com.github.topjohnwu.libsu.core)
+ implementation(libs.mmrl.platform)
+ compileOnly(libs.mmrl.hidden.api)
+ implementation(libs.mmrl.webui)
+ implementation(libs.mmrl.ui)
+
}
\ No newline at end of file
diff --git a/manager/app/proguard-rules.pro b/manager/app/proguard-rules.pro
index e69de29b..c44bba64 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.sukisu.ultra.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 59a93490..b317804e 100644
--- a/manager/app/src/main/AndroidManifest.xml
+++ b/manager/app/src/main/AndroidManifest.xml
@@ -39,6 +39,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/sukisu/ultra/ui/MainActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt
index a06e8fa6..7426443d 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt
@@ -14,7 +14,6 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
@@ -34,6 +33,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
import com.sukisu.ultra.ui.util.*
import androidx.core.content.edit
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
+import com.sukisu.ultra.ui.webui.initPlatform
class MainActivity : ComponentActivity() {
private inner class ThemeChangeContentObserver(
@@ -104,6 +104,11 @@ class MainActivity : ComponentActivity() {
val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() }
+ // pre-init platform to faster start WebUI X activities
+ LaunchedEffect(Unit) {
+ initPlatform()
+ }
+
Scaffold(
bottomBar = { BottomBar(navController) },
contentWindowInsets = WindowInsets(0, 0, 0, 0)
@@ -131,7 +136,7 @@ class MainActivity : ComponentActivity() {
override fun onPause() {
super.onPause()
CardConfig.save(applicationContext)
- getSharedPreferences("theme_prefs", MODE_PRIVATE).edit() {
+ getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", true)
}
ThemeConfig.preventBackgroundRefresh = true
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SettingsItem.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SettingsItem.kt
index 16bb5506..17647443 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SettingsItem.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SettingsItem.kt
@@ -11,8 +11,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
+import com.dergoogler.mmrl.ui.component.LabelItem
+import com.dergoogler.mmrl.ui.component.text.TextRow
@Composable
fun SwitchItem(
@@ -21,9 +24,11 @@ fun SwitchItem(
summary: String? = null,
checked: Boolean,
enabled: Boolean = true,
- onCheckedChange: (Boolean) -> Unit
+ beta: Boolean = false,
+ onCheckedChange: (Boolean) -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
+ val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
ListItem(
modifier = Modifier
@@ -36,10 +41,30 @@ fun SwitchItem(
onValueChange = onCheckedChange
),
headlineContent = {
- Text(title)
+ TextRow(
+ leadingContent = if (beta) {
+ {
+ LabelItem(
+ modifier = Modifier.then(stateAlpha),
+ text = "Beta"
+ )
+ }
+ } else null
+ ) {
+ Text(
+ modifier = Modifier.then(stateAlpha),
+ text = title,
+ )
+ }
},
leadingContent = icon?.let {
- { Icon(icon, title) }
+ {
+ Icon(
+ modifier = Modifier.then(stateAlpha),
+ imageVector = icon,
+ contentDescription = title
+ )
+ }
},
trailingContent = {
Switch(
@@ -51,7 +76,10 @@ fun SwitchItem(
},
supportingContent = {
if (summary != null) {
- Text(summary)
+ Text(
+ modifier = Modifier.then(stateAlpha),
+ text = summary
+ )
}
}
)
@@ -71,4 +99,4 @@ fun RadioItem(
RadioButton(selected = selected, onClick = onClick)
}
)
-}
+}
\ No newline at end of file
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt
index ca9b1e83..dc7796a6 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt
@@ -68,9 +68,11 @@ import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.ZipInputStream
import androidx.core.content.edit
-import androidx.core.net.toUri
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
+import com.sukisu.ultra.ui.webui.WebUIXActivity
+import com.dergoogler.mmrl.platform.Platform
+import androidx.core.net.toUri
@OptIn(ExperimentalMaterial3Api::class)
@@ -394,10 +396,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
onClickModule = { id, name, hasWebUi ->
if (hasWebUi) {
webUILauncher.launch(
- Intent(context, WebUIActivity::class.java)
- .setData("kernelsu://webui/$id".toUri())
- .putExtra("id", id)
- .putExtra("name", name)
+ if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
+ Intent(context, WebUIXActivity::class.java)
+ .setData("kernelsu://webuix/$id".toUri())
+ .putExtra("id", id)
+ .putExtra("name", name)
+ } else {
+ Intent(context, WebUIActivity::class.java)
+ .setData("kernelsu://webui/$id".toUri())
+ .putExtra("id", id)
+ .putExtra("name", name)
+ }
)
}
},
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt
index 96c52ed4..fa52473a 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt
@@ -58,6 +58,7 @@ import com.sukisu.ultra.ui.util.getBugreportFile
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import com.sukisu.ultra.ui.component.KsuIsValid
+import com.dergoogler.mmrl.platform.Platform
@OptIn(ExperimentalMaterial3Api::class)
@@ -239,6 +240,43 @@ fun SettingScreen(navigator: DestinationsNavigator) {
)
}
+ var useWebUIX by rememberSaveable {
+ mutableStateOf(
+ prefs.getBoolean("use_webuix", false)
+ )
+ }
+ KsuIsValid {
+ SwitchItem(
+ beta = true,
+ enabled = Platform.isAlive,
+ 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) }
+ useWebUIX = it
+ }
+ }
+ var useWebUIXEruda by rememberSaveable {
+ mutableStateOf(
+ prefs.getBoolean("use_webuix_eruda", false)
+ )
+ }
+ KsuIsValid {
+ SwitchItem(
+ beta = true,
+ enabled = Platform.isAlive && useWebUIX && enableWebDebugging,
+ icon = Icons.Filled.FormatListNumbered,
+ title = stringResource(id = R.string.use_webuix_eruda),
+ summary = stringResource(id = R.string.use_webuix_eruda_summary),
+ checked = useWebUIXEruda
+ ) {
+ prefs.edit().putBoolean("use_webuix_eruda", it).apply()
+ useWebUIXEruda = it
+ }
+ }
+
// 更多设置
SettingItem(
icon = Icons.Filled.Settings,
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt
index 707b6109..00c83c5a 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt
@@ -44,6 +44,11 @@ import androidx.core.content.edit
import androidx.core.net.toUri
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
+import androidx.activity.SystemBarStyle
+import androidx.activity.ComponentActivity
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
/**
* 主题配置对象,管理应用的主题相关状态
@@ -110,6 +115,10 @@ fun KernelSUTheme(
}
}
+ SystemBarStyle(
+ darkMode = darkTheme
+ )
+
// 初始加载配置
LaunchedEffect(Unit) {
context.loadThemeMode()
@@ -535,4 +544,36 @@ fun Context.loadDynamicColorState() {
.getBoolean("use_dynamic_color", true)
ThemeConfig.useDynamicColor = enabled
+}
+
+/**
+ * webui X样式
+ */
+@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/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt
index e7635c98..f1750b9e 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt
@@ -1,11 +1,7 @@
package com.sukisu.ultra.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,22 +10,23 @@ 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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
-import com.sukisu.zako.IKsuInterface
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
-import com.sukisu.ultra.ui.KsuService
import com.sukisu.ultra.ui.util.HanziToPinyin
-import com.sukisu.ultra.ui.util.KsuCli
import java.text.Collator
import java.util.*
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
+import com.dergoogler.mmrl.platform.Platform
+import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
+import com.sukisu.ultra.ui.webui.packageManager
+import com.sukisu.ultra.ui.webui.userManager
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withTimeoutOrNull
class SuperUserViewModel : ViewModel() {
+ val isPlatformAlive get() = Platform.isAlive
companion object {
private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf>(emptyList())
@@ -142,55 +139,28 @@ class SuperUserViewModel : ViewModel() {
fetchAppList() // 刷新列表以显示最新状态
}
- private suspend fun connectKsuService(
- onDisconnect: () -> Unit = {}
- ): Pair = suspendCoroutine { continuation ->
- val connection = object : ServiceConnection {
- override fun onServiceDisconnected(name: ComponentName?) {
- onDisconnect()
- }
-
- override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
- continuation.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) {
+ withTimeoutOrNull(TIMEOUT_MILLIS) {
+ while (!isPlatformAlive) {
+ delay(500)
+ }
+ } ?: return@withContext // Exit early if timeout
val pm = ksuApp.packageManager
val start = SystemClock.elapsedRealtime()
- val binder = result.first
- val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
+ val userInfos = Platform.userManager.getUsers()
+ val packages = mutableListOf()
+ val packageManager = Platform.packageManager
- withContext(Dispatchers.Main) {
- stopKsuService()
+ for (userInfo in userInfos) {
+ Log.i(TAG, "fetchAppList: ${userInfo.id}")
+ packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
}
- val packages = allPackages.list
-
apps = packages.map {
val appInfo = it.applicationInfo
val uid = appInfo!!.uid
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/KsuLibSuProvider.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/KsuLibSuProvider.kt
new file mode 100644
index 00000000..18f5eea7
--- /dev/null
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/KsuLibSuProvider.kt
@@ -0,0 +1,62 @@
+package com.sukisu.ultra.ui.webui
+
+import android.content.ServiceConnection
+import android.util.Log
+import com.dergoogler.mmrl.platform.Platform
+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.sukisu.ultra.ksuApp
+import com.sukisu.ultra.Natives
+import com.topjohnwu.superuser.ipc.RootService
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+
+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)
+ }
+}
+
+// webui x
+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)
+ }
+
+ return@withContext active
+ } catch (e: Exception) {
+ Log.e("KsuLibSu", "Failed to initialize platform", e)
+ return@withContext false
+ }
+}
+
+val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
+val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)
+
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuService.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuService.kt
new file mode 100644
index 00000000..5a421f24
--- /dev/null
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/SuService.kt
@@ -0,0 +1,14 @@
+package com.sukisu.ultra.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)
+ }
+}
\ No newline at end of file
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIActivity.kt
index 62bde78d..ea404c46 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIActivity.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIActivity.kt
@@ -15,9 +15,11 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader
+import com.dergoogler.mmrl.platform.model.ModId
import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell
import java.io.File
+import com.dergoogler.mmrl.webui.interfaces.WXOptions
@SuppressLint("SetJavaScriptEnabled")
class WebUIActivity : ComponentActivity() {
@@ -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)
}
@@ -82,7 +85,7 @@ 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/sukisu/ultra/ui/webui/WebUIXActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIXActivity.kt
new file mode 100644
index 00000000..06576beb
--- /dev/null
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebUIXActivity.kt
@@ -0,0 +1,114 @@
+package com.sukisu.ultra.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.platform.model.ModId
+import com.dergoogler.mmrl.ui.component.Loading
+
+import com.dergoogler.mmrl.webui.screen.WebUIScreen
+import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
+import com.sukisu.ultra.BuildConfig
+import com.sukisu.ultra.ui.theme.KernelSUTheme
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+class WebUIXActivity : ComponentActivity() {
+ private lateinit var webView: WebView
+
+ private val userAgent
+ get(): String {
+ val ksuVersion = BuildConfig.VERSION_CODE
+
+ val platform = Platform.get("Unknown") {
+ platform.name
+ }
+
+ val platformVersion = Platform.get(-1) {
+ moduleManager.versionCode
+ }
+
+ val osVersion = Build.VERSION.RELEASE
+ val deviceModel = Build.MODEL
+
+ return "KernelSU Next/$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
+ }
+
+ 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 erudaInject = prefs.getBoolean("use_webuix_eruda", false)
+ val dark = isSystemInDarkTheme()
+
+ val options = rememberWebUIOptions(
+ modId = ModId(moduleId),
+ debug = webDebugging,
+ appVersionCode = BuildConfig.VERSION_CODE,
+ isDarkMode = dark,
+ enableEruda = erudaInject,
+ cls = WebUIXActivity::class.java,
+ userAgentString = userAgent
+ )
+
+ WebUIScreen(
+ webView = webView,
+ options = options,
+ interfaces = listOf(
+ WebViewInterface.factory()
+ )
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebViewInterface.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebViewInterface.kt
index a3d02a6e..8fca1c71 100644
--- a/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebViewInterface.kt
+++ b/manager/app/src/main/java/com/sukisu/ultra/ui/webui/WebViewInterface.kt
@@ -1,16 +1,17 @@
package com.sukisu.ultra.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
@@ -25,10 +26,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 {
@@ -170,9 +176,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)
}
}
}
diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml
index fd0d2401..e78f6271 100644
--- a/manager/app/src/main/res/values-zh-rCN/strings.xml
+++ b/manager/app/src/main/res/values-zh-rCN/strings.xml
@@ -320,4 +320,8 @@
背景设置成功
已移除自定义背景
需要 root 权限
+ 使用 WebUI X
+ 使用支持更多 API 的 WebUI X 而不是 WebUI
+ 将 Eruda 注入 WebUI X
+ 在 WebUI X 中注入调试控制台,使调试更容易,需要启用 WebView 调试
diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml
index 570ed0d5..c47b2e3d 100644
--- a/manager/app/src/main/res/values/strings.xml
+++ b/manager/app/src/main/res/values/strings.xml
@@ -324,4 +324,8 @@
Background set successfully
Removed custom backgrounds
Requires root privileges
+ Use WebUI X
+ Use WebUI X instead of WebUI which supports more API\'s
+ Inject Eruda into WebUI X
+ Inject a debug console into WebUI X to make debugging easier. Requires web debugging to be on.
diff --git a/manager/gradle/libs.versions.toml b/manager/gradle/libs.versions.toml
index 66052f10..bc9145d2 100644
--- a/manager/gradle/libs.versions.toml
+++ b/manager/gradle/libs.versions.toml
@@ -22,6 +22,7 @@ compose-material3 = "1.3.2"
compose-ui = "1.8.0"
compose-foundation = "1.7.8"
documentfile = "1.0.1"
+mmrl = "v33560"
[plugins]
agp-app = { id = "com.android.application", version.ref = "agp" }
@@ -81,4 +82,10 @@ 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" }
-androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
\ No newline at end of file
+androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
+
+
+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