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