You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
Add WebUI X (#316)
* Add option to use WebUI X - Added WebUI X from MMRL to KernelSU Next * Some improvements * Again some improvements * Fix NPE * Still not fixed Use mappings from the action to decode the stacktrace * bump mmrl does still not work * Works * WORKS * small fixes
This commit is contained in:
@@ -133,4 +133,9 @@ dependencies {
|
|||||||
implementation(libs.androidx.webkit)
|
implementation(libs.androidx.webkit)
|
||||||
|
|
||||||
implementation(libs.lsposed.cxx)
|
implementation(libs.lsposed.cxx)
|
||||||
|
|
||||||
|
implementation(libs.mmrl.platform)
|
||||||
|
compileOnly(libs.mmrl.hidden.api)
|
||||||
|
implementation(libs.mmrl.ui)
|
||||||
|
implementation(libs.mmrl.webui)
|
||||||
}
|
}
|
||||||
Vendored
+47
@@ -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 { *; }
|
||||||
@@ -33,6 +33,13 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.webui.WebUIXActivity"
|
||||||
|
android:autoRemoveFromRecents="true"
|
||||||
|
android:documentLaunchMode="intoExisting"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
@@ -21,6 +22,8 @@ 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(
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
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<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<PackageInfo> {
|
||||||
|
val list: List<PackageInfo> = getInstalledPackagesAll(flags)
|
||||||
|
Log.i(TAG, "getPackages: " + list.size)
|
||||||
|
return ParcelableListSlice(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val userIds: List<Int>
|
||||||
|
get() {
|
||||||
|
val result: MutableList<Int> =
|
||||||
|
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<PackageInfo> {
|
||||||
|
val packages = ArrayList<PackageInfo>()
|
||||||
|
for (userId in userIds) {
|
||||||
|
Log.i(TAG, "getInstalledPackagesAll: $userId")
|
||||||
|
packages.addAll(getInstalledPackagesAsUser(flags, userId))
|
||||||
|
}
|
||||||
|
return packages
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInstalledPackagesAsUser(flags: Int, userId: Int): List<PackageInfo> {
|
||||||
|
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<PackageInfo>
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "err", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArrayList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object Default {
|
||||||
|
const val TAG = "KsuService"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -38,6 +39,7 @@ import androidx.navigation.NavBackStackEntry
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
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.LocalSnackbarHost
|
||||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||||
import com.rifsxd.ksunext.ui.util.install
|
import com.rifsxd.ksunext.ui.util.install
|
||||||
import com.rifsxd.ksunext.ui.util.*
|
import com.rifsxd.ksunext.ui.webui.initPlatform
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
if (isManager) install()
|
if (isManager) install()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
KernelSUTheme {
|
KernelSUTheme {
|
||||||
@@ -81,6 +83,11 @@ class MainActivity : ComponentActivity() {
|
|||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pre-init platform to faster start WebUI X activities
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
initPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@@ -118,7 +125,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
private fun BottomBar(navController: NavHostController) {
|
private fun BottomBar(navController: NavHostController) {
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
val navigator = navController.rememberDestinationsNavigator()
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
|
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() && Platform.isAlive
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
tonalElevation = 8.dp,
|
tonalElevation = 8.dp,
|
||||||
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import com.rifsxd.ksunext.ui.util.uninstallModule
|
|||||||
import com.rifsxd.ksunext.ui.util.restoreModule
|
import com.rifsxd.ksunext.ui.util.restoreModule
|
||||||
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
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@@ -238,15 +239,18 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show confirm dialog with selected zip file(s) name(s)
|
// Show confirm dialog with selected zip file(s) name(s)
|
||||||
val moduleNames = uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }.joinToString("")
|
val moduleNames =
|
||||||
val confirmContent = context.getString(R.string.module_install_prompt_with_name, 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
|
zipUris = uris
|
||||||
confirmDialog.showConfirm(
|
confirmDialog.showConfirm(
|
||||||
title = confirmTitle,
|
title = confirmTitle,
|
||||||
content = confirmContent,
|
content = confirmContent,
|
||||||
markdown = true
|
markdown = true
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
@@ -281,6 +285,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
ModuleList(
|
ModuleList(
|
||||||
navigator,
|
navigator,
|
||||||
@@ -293,10 +298,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
onClickModule = { id, name, hasWebUi ->
|
onClickModule = { id, name, hasWebUi ->
|
||||||
if (hasWebUi) {
|
if (hasWebUi) {
|
||||||
webUILauncher.launch(
|
webUILauncher.launch(
|
||||||
Intent(context, WebUIActivity::class.java)
|
if (prefs.getBoolean("use_webuix", false)) {
|
||||||
.setData(Uri.parse("kernelsu://webui/$id"))
|
Intent(context, WebUIXActivity::class.java)
|
||||||
.putExtra("id", id)
|
.setData(Uri.parse("kernelsu://webuix/$id"))
|
||||||
.putExtra("name", name)
|
.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,
|
onInstallModule: (Uri) -> Unit,
|
||||||
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit,
|
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
snackBarHost: SnackbarHostState
|
snackBarHost: SnackbarHostState,
|
||||||
) {
|
) {
|
||||||
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||||
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
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 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 {
|
var useOverlayFs by rememberSaveable {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -357,7 +370,7 @@ private fun ModuleList(
|
|||||||
module: ModuleViewModel.ModuleInfo,
|
module: ModuleViewModel.ModuleInfo,
|
||||||
changelogUrl: String,
|
changelogUrl: String,
|
||||||
downloadUrl: String,
|
downloadUrl: String,
|
||||||
fileName: String
|
fileName: String,
|
||||||
) {
|
) {
|
||||||
val changelogResult = loadingDialog.withLoading {
|
val changelogResult = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@@ -570,7 +583,8 @@ private fun ModuleList(
|
|||||||
reboot()
|
reboot()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val message = if (module.enabled) failedDisable else failedEnable
|
val message =
|
||||||
|
if (module.enabled) failedDisable else failedEnable
|
||||||
snackBarHost.showSnackbar(message.format(module.name))
|
snackBarHost.showSnackbar(message.format(module.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -611,7 +625,7 @@ fun ModuleItem(
|
|||||||
onRestore: (ModuleViewModel.ModuleInfo) -> Unit,
|
onRestore: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||||
onCheckChanged: (Boolean) -> Unit,
|
onCheckChanged: (Boolean) -> Unit,
|
||||||
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
|
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||||
onClick: (ModuleViewModel.ModuleInfo) -> Unit
|
onClick: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
@@ -620,7 +634,7 @@ fun ModuleItem(
|
|||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val indication = LocalIndication.current
|
val indication = LocalIndication.current
|
||||||
val viewModel = viewModel<ModuleViewModel>()
|
val viewModel = viewModel<ModuleViewModel>()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
@@ -862,7 +876,7 @@ fun ModuleItem(
|
|||||||
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||||
text = stringResource(R.string.uninstall)
|
text = stringResource(R.string.uninstall)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ import com.rifsxd.ksunext.ui.util.getBugreportFile
|
|||||||
import com.rifsxd.ksunext.ui.util.*
|
import com.rifsxd.ksunext.ui.util.*
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -162,7 +163,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||||
checked = umountChecked
|
checked = umountChecked
|
||||||
|
|
||||||
) {
|
) {
|
||||||
if (Natives.setDefaultUmountModules(it)) {
|
if (Natives.setDefaultUmountModules(it)) {
|
||||||
umountChecked = 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) {
|
if (ksuVersion != null) {
|
||||||
val backupRestore = stringResource(id = R.string.backup_restore)
|
val backupRestore = stringResource(id = R.string.backup_restore)
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -496,7 +514,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun UninstallItem(
|
fun UninstallItem(
|
||||||
navigator: DestinationsNavigator,
|
navigator: DestinationsNavigator,
|
||||||
withLoading: suspend (suspend () -> Unit) -> Unit
|
withLoading: suspend (suspend () -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -598,7 +616,7 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.settings)) },
|
title = { Text(stringResource(R.string.settings)) },
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.rifsxd.ksunext.ui.theme
|
package com.rifsxd.ksunext.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
@@ -8,6 +11,9 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
|||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
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
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
@@ -38,9 +44,42 @@ fun KernelSUTheme(
|
|||||||
else -> LightColorScheme
|
else -> LightColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemBarStyle(
|
||||||
|
darkMode = darkTheme
|
||||||
|
)
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
package com.rifsxd.ksunext.ui.viewmodel
|
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.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
|
||||||
@@ -14,20 +10,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.topjohnwu.superuser.Shell
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
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.util.KsuCli
|
import com.rifsxd.ksunext.ui.webui.getPackages
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
|
|
||||||
@@ -97,55 +89,15 @@ 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
|
||||||
|
|
||||||
val result = connectKsuService {
|
|
||||||
Log.w(TAG, "KsuService disconnected")
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val pm = ksuApp.packageManager
|
val pm = ksuApp.packageManager
|
||||||
val start = SystemClock.elapsedRealtime()
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
val binder = result.first
|
val packages = Platform.getPackages(0).list
|
||||||
val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
stopKsuService()
|
|
||||||
}
|
|
||||||
|
|
||||||
val packages = allPackages.list
|
|
||||||
|
|
||||||
apps = packages.map {
|
apps = packages.map {
|
||||||
val appInfo = it.applicationInfo
|
val appInfo = it.applicationInfo
|
||||||
|
|||||||
@@ -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<PackageInfo> {
|
||||||
|
val ksuService: IKsuInterface by lazy {
|
||||||
|
IKsuInterface.Stub.asInterface(
|
||||||
|
this.mService.getService("ksuService")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ksuService.getPackages(flags)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ 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.webui.interfaces.WXOptions
|
||||||
|
import com.dergoogler.mmrl.webui.model.ModId
|
||||||
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,7 +43,8 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
||||||
} else {
|
} else {
|
||||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
val taskDescription =
|
||||||
|
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||||
setTaskDescription(taskDescription)
|
setTaskDescription(taskDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +65,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)
|
||||||
}
|
}
|
||||||
@@ -82,7 +85,9 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.allowFileAccess = false
|
settings.allowFileAccess = false
|
||||||
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
webviewInterface = WebViewInterface(
|
||||||
|
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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
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.WXOptions
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WebUIInterface
|
||||||
|
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
|
||||||
@@ -23,10 +24,15 @@ import java.io.File
|
|||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class WebViewInterface(
|
class WebViewInterface(
|
||||||
val context: Context,
|
wxOptions: WXOptions,
|
||||||
private val webView: WebView,
|
) : WebUIInterface(wxOptions) {
|
||||||
private val modDir: String
|
override var name: String = "ksu"
|
||||||
) {
|
|
||||||
|
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 {
|
||||||
@@ -59,7 +65,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)
|
||||||
@@ -168,9 +174,9 @@ class WebViewInterface(
|
|||||||
if (context is Activity) {
|
if (context is Activity) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
hideSystemUI(context.window)
|
hideSystemUI(activity.window)
|
||||||
} else {
|
} else {
|
||||||
showSystemUI(context.window)
|
showSystemUI(activity.window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,8 +208,12 @@ 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 = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
controller.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSystemUI(window: Window) =
|
fun showSystemUI(window: Window) =
|
||||||
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars())
|
WindowInsetsControllerCompat(
|
||||||
|
window,
|
||||||
|
window.decorView
|
||||||
|
).show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
|||||||
@@ -194,4 +194,6 @@
|
|||||||
<string name="settings_disable_su">Disable su compatibility</string>
|
<string name="settings_disable_su">Disable su compatibility</string>
|
||||||
<string name="settings_disable_su_summary">Temporarily disable the ability of any app to gain root privileges via the su command (existing root processes won\'t be affected).</string>
|
<string name="settings_disable_su_summary">Temporarily disable the ability of any app to gain root privileges via the su command (existing root processes won\'t be affected).</string>
|
||||||
<string name="settings_language">Language</string>
|
<string name="settings_language">Language</string>
|
||||||
|
<string name="use_webuix">Use WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">Use WebUI X instead of WebUI which supports more API\'s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ parcelablelist = "2.0.1"
|
|||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
apksign = "1.4"
|
apksign = "1.4"
|
||||||
cmaker = "1.2"
|
cmaker = "1.2"
|
||||||
|
mmrl = "785605169b"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
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" }
|
markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" }
|
||||||
|
|
||||||
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
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" }
|
||||||
Reference in New Issue
Block a user