manager: removed deprecated webuix and allow superuser and module management capabilities even with su compat disabled

This commit is contained in:
Rifat Azad
2025-06-28 17:17:16 +06:00
parent 4270fd8b1e
commit d80a3ebcda
17 changed files with 167 additions and 355 deletions

View File

@@ -34,6 +34,7 @@ android {
} }
buildFeatures { buildFeatures {
aidl = true
buildConfig = true buildConfig = true
compose = true compose = true
prefab = true prefab = true

View File

@@ -0,0 +1,9 @@
// IKsuInterface.aidl
package com.rifsxd.ksunext;
import android.content.pm.PackageInfo;
import rikka.parcelablelist.ParcelableListSlice;
interface IKsuInterface {
ParcelableListSlice<PackageInfo> getPackages(int flags);
}

View File

@@ -4,7 +4,6 @@ import android.app.Application
import android.system.Os import android.system.Os
import coil.Coil import coil.Coil
import coil.ImageLoader import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform
import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import okhttp3.Cache import okhttp3.Cache
@@ -22,8 +21,6 @@ class KernelSUApplication : Application() {
super.onCreate() super.onCreate()
ksuApp = this ksuApp = this
Platform.setHiddenApiExemptions()
val context = this val context = this
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
Coil.setImageLoader( Coil.setImageLoader(

View File

@@ -0,0 +1,77 @@
package com.rifsxd.ksunext.ui;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.topjohnwu.superuser.ipc.RootService;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.rifsxd.ksunext.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice;
/**
* @author weishu
* @date 2023/4/18.
*/
public class KsuService extends RootService {
private static final String TAG = "KsuService";
class Stub extends IKsuInterface.Stub {
@Override
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
List<PackageInfo> list = getInstalledPackagesAll(flags);
Log.i(TAG, "getPackages: " + list.size());
return new ParcelableListSlice<>(list);
}
}
@Override
public IBinder onBind(@NonNull Intent intent) {
return new Stub();
}
List<Integer> getUserIds() {
List<Integer> result = new ArrayList<>();
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
List<UserHandle> userProfiles = um.getUserProfiles();
for (UserHandle userProfile : userProfiles) {
int userId = userProfile.hashCode();
result.add(userProfile.hashCode());
}
return result;
}
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
ArrayList<PackageInfo> packages = new ArrayList<>();
for (Integer userId : getUserIds()) {
Log.i(TAG, "getInstalledPackagesAll: " + userId);
packages.addAll(getInstalledPackagesAsUser(flags, userId));
}
return packages;
}
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
try {
PackageManager pm = getPackageManager();
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
} catch (Throwable e) {
Log.e(TAG, "err", e);
}
return new ArrayList<>();
}
}

View File

@@ -64,7 +64,7 @@ import com.rifsxd.ksunext.ui.util.install
import com.rifsxd.ksunext.ui.util.isSuCompatDisabled import com.rifsxd.ksunext.ui.util.isSuCompatDisabled
import com.rifsxd.ksunext.ui.screen.FlashIt import com.rifsxd.ksunext.ui.screen.FlashIt
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
import com.rifsxd.ksunext.ui.webui.initPlatform import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -115,6 +115,7 @@ class MainActivity : ComponentActivity() {
val amoledMode = prefs.getBoolean("enable_amoled", false) val amoledMode = prefs.getBoolean("enable_amoled", false)
val moduleViewModel: ModuleViewModel = viewModel() val moduleViewModel: ModuleViewModel = viewModel()
val superUserViewModel: SuperUserViewModel = viewModel()
val moduleUpdateCount = moduleViewModel.moduleList.count { val moduleUpdateCount = moduleViewModel.moduleList.count {
moduleViewModel.checkUpdate(it).first.isNotEmpty() moduleViewModel.checkUpdate(it).first.isNotEmpty()
} }
@@ -139,17 +140,22 @@ class MainActivity : ComponentActivity() {
} }
} }
LaunchedEffect(Unit) {
if (superUserViewModel.appList.isEmpty()) {
superUserViewModel.fetchAppList()
}
if (moduleViewModel.moduleList.isEmpty()) {
moduleViewModel.fetchModuleList()
}
}
val showBottomBar = when (currentDestination?.route) { val showBottomBar = when (currentDestination?.route) {
FlashScreenDestination.route -> false // Hide for FlashScreenDestination FlashScreenDestination.route -> false // Hide for FlashScreenDestination
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
else -> true else -> true
} }
// pre-init platform to faster start WebUI X activities
LaunchedEffect(Unit) {
initPlatform()
}
Scaffold( Scaffold(
bottomBar = { bottomBar = {
AnimatedVisibility( AnimatedVisibility(
@@ -199,17 +205,6 @@ private fun BottomBar(navController: NavHostController, moduleUpdateCount: Int)
) )
) { ) {
BottomBarDestination.entries BottomBarDestination.entries
.filter {
// Hide SuperUser and Module when su compat is disabled
if (suCompatDisabled) {
if (suSFS == "Supported" && susSUMode == "2") {
true
} else {
// hide SuperUser and Module
it != BottomBarDestination.SuperUser && it != BottomBarDestination.Module
}
} else true
}
.forEach { destination -> .forEach { destination ->
if (!fullFeatured && destination.rootRequired) return@forEach if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)

View File

@@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.dergoogler.mmrl.platform.Platform
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -102,25 +101,6 @@ fun DeveloperScreen(navigator: DestinationsNavigator) {
} }
} }
var useWebUIX by rememberSaveable {
mutableStateOf(
prefs.getBoolean("use_webuix", true)
)
}
if (ksuVersion != null) {
SwitchItem(
beta = false,
enabled = Platform.isAlive && developerOptionsEnabled,
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
}
}
var enableWebDebugging by rememberSaveable { var enableWebDebugging by rememberSaveable {
mutableStateOf( mutableStateOf(
prefs.getBoolean("enable_web_debugging", false) prefs.getBoolean("enable_web_debugging", false)
@@ -138,25 +118,6 @@ fun DeveloperScreen(navigator: DestinationsNavigator) {
enableWebDebugging = it enableWebDebugging = it
} }
} }
var useWebUIXEruda by rememberSaveable {
mutableStateOf(
prefs.getBoolean("use_webuix_eruda", false)
)
}
if (ksuVersion != null) {
SwitchItem(
beta = false,
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
}
}
} }
} }
} }

View File

@@ -54,8 +54,6 @@ import com.rifsxd.ksunext.R
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
import com.rifsxd.ksunext.ui.util.* import com.rifsxd.ksunext.ui.util.*
import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo import com.rifsxd.ksunext.ui.util.module.LatestVersionInfo
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
import com.rifsxd.ksunext.ui.viewmodel.SuperUserViewModel
import java.util.* import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -96,20 +94,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
val lkmMode = ksuVersion?.let { val lkmMode = ksuVersion?.let {
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null
} }
val superUserViewModel: SuperUserViewModel = viewModel()
val moduleViewModel: ModuleViewModel = viewModel()
LaunchedEffect(Unit) {
if (superUserViewModel.appList.isEmpty()) {
superUserViewModel.fetchAppList()
}
if (moduleViewModel.moduleList.isEmpty()) {
moduleViewModel.fetchModuleList()
}
}
StatusCard(kernelVersion, ksuVersion, lkmMode) { StatusCard(kernelVersion, ksuVersion, lkmMode) {
navigator.navigate(InstallScreenDestination) navigator.navigate(InstallScreenDestination)

View File

@@ -95,7 +95,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.dergoogler.mmrl.platform.Platform
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
@@ -126,7 +125,6 @@ import com.rifsxd.ksunext.ui.util.restoreModule
import com.rifsxd.ksunext.ui.util.zygiskRequired import com.rifsxd.ksunext.ui.util.zygiskRequired
import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel import com.rifsxd.ksunext.ui.viewmodel.ModuleViewModel
import com.rifsxd.ksunext.ui.webui.WebUIActivity import com.rifsxd.ksunext.ui.webui.WebUIActivity
import com.rifsxd.ksunext.ui.webui.WebUIXActivity
import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItem
import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFile
@@ -363,22 +361,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
}, },
onClickModule = { id, name, hasWebUi -> onClickModule = { id, name, hasWebUi ->
if (hasWebUi) { if (hasWebUi) {
val wxEngine = Intent(context, WebUIXActivity::class.java)
.setData("kernelsu://webuix/$id".toUri())
.putExtra("id", id)
.putExtra("name", name)
val ksuEngine = Intent(context, WebUIActivity::class.java)
.setData("kernelsu://webui/$id".toUri())
.putExtra("id", id)
.putExtra("name", name)
webUILauncher.launch( webUILauncher.launch(
if (prefs.getBoolean("use_webuix", true) && Platform.isAlive) { Intent(context, WebUIActivity::class.java)
wxEngine .setData(Uri.parse("kernelsu://webui/$id"))
} else { .putExtra("id", id)
ksuEngine .putExtra("name", name)
}
) )
} }
}, },

View File

@@ -54,7 +54,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.dergoogler.mmrl.platform.Platform
import com.maxkeppeker.sheets.core.models.base.Header import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.IconSource import com.maxkeppeker.sheets.core.models.base.IconSource
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState

View File

@@ -25,7 +25,6 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItem
import com.dergoogler.mmrl.ui.component.LabelItemDefaults import com.dergoogler.mmrl.ui.component.LabelItemDefaults
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination

View File

@@ -9,8 +9,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -111,21 +109,9 @@ class ModuleViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.Main) { isRefreshing = true
isRefreshing = true
}
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
withTimeoutOrNull(TIMEOUT_MILLIS) {
while (!Platform.isAlive) {
delay(500)
}
} ?: run {
isRefreshing = false
Log.e(TAG, "Platform is not alive, aborting fetchModuleList")
return@withContext
}
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val oldModuleList = modules val oldModuleList = modules

View File

@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.IBinder
import android.os.Parcelable import android.os.Parcelable
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
@@ -15,15 +16,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.dergoogler.mmrl.platform.Platform import com.topjohnwu.superuser.Shell
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.rifsxd.ksunext.IKsuInterface
import com.rifsxd.ksunext.Natives import com.rifsxd.ksunext.Natives
import com.rifsxd.ksunext.ksuApp import com.rifsxd.ksunext.ksuApp
import com.rifsxd.ksunext.ui.KsuService
import com.rifsxd.ksunext.ui.util.HanziToPinyin import com.rifsxd.ksunext.ui.util.HanziToPinyin
import com.rifsxd.ksunext.ui.webui.getInstalledPackagesAll import com.rifsxd.ksunext.ui.util.KsuCli
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import java.text.Collator import java.text.Collator
@@ -33,7 +35,6 @@ import kotlin.coroutines.suspendCoroutine
import androidx.core.content.edit import androidx.core.content.edit
class SuperUserViewModel : ViewModel() { class SuperUserViewModel : ViewModel() {
val isPlatformAlive get() = Platform.isAlive
companion object { companion object {
private const val TAG = "SuperUserViewModel" private const val TAG = "SuperUserViewModel"
@@ -117,24 +118,56 @@ class SuperUserViewModel : ViewModel() {
} }
} }
private suspend inline fun connectKsuService(
crossinline onDisconnect: () -> Unit = {}
): Pair<IBinder, ServiceConnection> = 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() { suspend fun fetchAppList() {
isRefreshing = true isRefreshing = true
withContext(Dispatchers.IO) { val result = connectKsuService {
withTimeoutOrNull(TIMEOUT_MILLIS) { Log.w(TAG, "KsuService disconnected")
while (!isPlatformAlive) { }
delay(500)
}
} ?: return@withContext // Exit early if timeout
withContext(Dispatchers.IO) {
val pm = ksuApp.packageManager val pm = ksuApp.packageManager
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val packages = Platform.getInstalledPackagesAll { val binder = result.first
Log.e(TAG, "getInstalledPackagesAll:", it) val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
Toast.makeText(ksuApp, "Something went wrong, check logs", Toast.LENGTH_SHORT).show()
withContext(Dispatchers.Main) {
stopKsuService()
} }
val packages = allPackages.list
apps = packages.map { apps = packages.map {
val appInfo = it.applicationInfo val appInfo = it.applicationInfo
val uid = appInfo!!.uid val uid = appInfo!!.uid
@@ -145,7 +178,6 @@ class SuperUserViewModel : ViewModel() {
profile = profile, profile = profile,
) )
}.filter { it.packageName != ksuApp.packageName } }.filter { it.packageName != ksuApp.packageName }
profileOverrides = emptyMap()
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}") Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")
} }
} }

View File

@@ -1,73 +0,0 @@
package com.rifsxd.ksunext.ui.webui
import android.content.ServiceConnection
import android.content.pm.PackageInfo
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.rifsxd.ksunext.Natives
import com.rifsxd.ksunext.ksuApp
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)
}
}
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
}
}
fun Platform.Companion.getInstalledPackagesAll(catch: (Exception) -> Unit = {}): List<PackageInfo> =
try {
val packages = mutableListOf<PackageInfo>()
val userInfos = userManager.getUsers()
for (userInfo in userInfos) {
packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
}
packages
} catch (e: Exception) {
catch(e)
packageManager.getInstalledPackages(0, userManager.myUserId)
}

View File

@@ -1,14 +0,0 @@
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)
}
}

View File

@@ -15,8 +15,6 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.dergoogler.mmrl.platform.model.ModId
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.rifsxd.ksunext.ui.util.createRootShell import com.rifsxd.ksunext.ui.util.createRootShell
import java.io.File import java.io.File
@@ -41,10 +39,9 @@ class WebUIActivity : ComponentActivity() {
val name = intent.getStringExtra("name")!! val name = intent.getStringExtra("name")!!
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("KernelSU Next - $name")) setTaskDescription(ActivityManager.TaskDescription("KSUNEXT - $name"))
} else { } else {
val taskDescription = val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KSUNEXT - $name").build()
ActivityManager.TaskDescription.Builder().setLabel("KernelSU Next - $name").build()
setTaskDescription(taskDescription) setTaskDescription(taskDescription)
} }
@@ -65,7 +62,7 @@ class WebUIActivity : ComponentActivity() {
val webViewClient = object : WebViewClient() { val webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest( override fun shouldInterceptRequest(
view: WebView, view: WebView,
request: WebResourceRequest, request: WebResourceRequest
): WebResourceResponse? { ): WebResourceResponse? {
return webViewAssetLoader.shouldInterceptRequest(request.url) return webViewAssetLoader.shouldInterceptRequest(request.url)
} }
@@ -85,9 +82,7 @@ class WebUIActivity : ComponentActivity() {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.domStorageEnabled = true settings.domStorageEnabled = true
settings.allowFileAccess = false settings.allowFileAccess = false
webviewInterface = WebViewInterface( webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
WXOptions(this@WebUIActivity, this, ModId(moduleId))
)
addJavascriptInterface(webviewInterface, "ksu") addJavascriptInterface(webviewInterface, "ksu")
setWebViewClient(webViewClient) setWebViewClient(webViewClient)
loadUrl("https://mui.kernelsu.org/index.html") loadUrl("https://mui.kernelsu.org/index.html")

View File

@@ -1,113 +0,0 @@
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.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.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
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 Next - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("KernelSU Next - $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()
)
)
}
}
}
}

View File

@@ -1,17 +1,16 @@
package com.rifsxd.ksunext.ui.webui package com.rifsxd.ksunext.ui.webui
import android.app.Activity import android.app.Activity
import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.TextUtils import android.text.TextUtils
import android.view.Window import android.view.Window
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import com.dergoogler.mmrl.webui.interfaces.WXInterface
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
@@ -24,15 +23,10 @@ import java.io.File
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
class WebViewInterface( class WebViewInterface(
wxOptions: WXOptions, val context: Context,
) : WXInterface(wxOptions) { private val webView: WebView,
override var name: String = "ksu" private val modDir: String
) {
companion object {
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
}
private val modDir get() = "/data/adb/modules/${modId.id}"
@JavascriptInterface @JavascriptInterface
fun exec(cmd: String): String { fun exec(cmd: String): String {
@@ -65,7 +59,7 @@ class WebViewInterface(
fun exec( fun exec(
cmd: String, cmd: String,
options: String?, options: String?,
callbackFunc: String, callbackFunc: String
) { ) {
val finalCommand = StringBuilder() val finalCommand = StringBuilder()
processOptions(finalCommand, options) processOptions(finalCommand, options)
@@ -174,9 +168,9 @@ class WebViewInterface(
if (context is Activity) { if (context is Activity) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (enable) { if (enable) {
hideSystemUI(activity.window) hideSystemUI(context.window)
} else { } else {
showSystemUI(activity.window) showSystemUI(context.window)
} }
} }
} }
@@ -185,7 +179,7 @@ class WebViewInterface(
@JavascriptInterface @JavascriptInterface
fun moduleInfo(): String { fun moduleInfo(): String {
val moduleInfos = JSONArray(listModules()) val moduleInfos = JSONArray(listModules())
val currentModuleInfo = JSONObject() var currentModuleInfo = JSONObject()
currentModuleInfo.put("moduleDir", modDir) currentModuleInfo.put("moduleDir", modDir)
val moduleId = File(modDir).getName() val moduleId = File(modDir).getName()
for (i in 0 until moduleInfos.length()) { for (i in 0 until moduleInfos.length()) {
@@ -195,7 +189,7 @@ class WebViewInterface(
continue continue
} }
val keys = currentInfo.keys() var keys = currentInfo.keys()
for (key in keys) { for (key in keys) {
currentModuleInfo.put(key, currentInfo.get(key)) currentModuleInfo.put(key, currentInfo.get(key))
} }
@@ -208,12 +202,8 @@ class WebViewInterface(
fun hideSystemUI(window: Window) = fun hideSystemUI(window: Window) =
WindowInsetsControllerCompat(window, window.decorView).let { controller -> WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars()) controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} }
fun showSystemUI(window: Window) = fun showSystemUI(window: Window) =
WindowInsetsControllerCompat( WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())
window,
window.decorView
).show(WindowInsetsCompat.Type.systemBars())