Fix root detection for KernelSU

Use libsu's Shell API instead of Runtime.exec("su -c ...") for root
detection. The previous approach assumed su is in PATH, which works
for Magisk but not for KernelSU where su has a different path.
This commit is contained in:
世界
2026-01-14 14:05:07 +08:00
parent 65f6529ff1
commit cd0ae262f1
6 changed files with 10 additions and 26 deletions

View File

@@ -18,16 +18,6 @@ import kotlin.coroutines.resumeWithException
object RootInstaller { object RootInstaller {
suspend fun checkAccess(): Boolean = withContext(Dispatchers.IO) {
try {
val process = Runtime.getRuntime().exec("su -c echo test")
val exitCode = process.waitFor()
exitCode == 0
} catch (e: Exception) {
false
}
}
suspend fun install(apkFile: File) { suspend fun install(apkFile: File) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
bindRootService().use { handle -> bindRootService().use { handle ->

View File

@@ -58,6 +58,7 @@ import androidx.navigation.NavController
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.compose.topbar.OverrideTopBar import io.nekohasekai.sfa.compose.topbar.OverrideTopBar
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.compose.screen.profileoverride.PerAppProxyScanner import io.nekohasekai.sfa.compose.screen.profileoverride.PerAppProxyScanner
@@ -206,18 +207,7 @@ fun ProfileOverrideScreen(navController: NavController) {
onCheckedChange = { checked -> onCheckedChange = { checked ->
if (checked && !autoRedirect) { if (checked && !autoRedirect) {
scope.launch { scope.launch {
val hasRoot = val hasRoot = RootClient.checkRootAvailable()
withContext(Dispatchers.IO) {
try {
val process = Runtime.getRuntime().exec("su -c id")
process.inputStream.close()
process.outputStream.close()
process.errorStream.close()
process.waitFor() == 0
} catch (e: Exception) {
false
}
}
if (hasRoot) { if (hasRoot) {
autoRedirect = true autoRedirect = true
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {

View File

@@ -3,6 +3,7 @@ package io.nekohasekai.sfa.vendor
import android.content.Context import android.content.Context
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.bg.BoxService import io.nekohasekai.sfa.bg.BoxService
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.utils.HookStatusClient import io.nekohasekai.sfa.utils.HookStatusClient
import io.nekohasekai.sfa.xposed.XposedActivation import io.nekohasekai.sfa.xposed.XposedActivation
@@ -62,7 +63,7 @@ object ApkInstaller {
return when (method) { return when (method) {
InstallMethod.PACKAGE_INSTALLER -> canSystemSilentInstall() InstallMethod.PACKAGE_INSTALLER -> canSystemSilentInstall()
InstallMethod.SHIZUKU -> ShizukuInstaller.isAvailable() && ShizukuInstaller.checkPermission() InstallMethod.SHIZUKU -> ShizukuInstaller.isAvailable() && ShizukuInstaller.checkPermission()
InstallMethod.ROOT -> RootInstaller.checkAccess() InstallMethod.ROOT -> RootClient.checkRootAvailable()
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import androidx.camera.core.ImageAnalysis
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.compose.screen.qrscan.QRCodeCropArea import io.nekohasekai.sfa.compose.screen.qrscan.QRCodeCropArea
import io.nekohasekai.sfa.update.UpdateCheckException import io.nekohasekai.sfa.update.UpdateCheckException
@@ -135,7 +136,7 @@ object Vendor : VendorInterface {
} }
true true
} }
"ROOT" -> RootInstaller.checkAccess() "ROOT" -> RootClient.checkRootAvailable()
else -> false else -> false
} }
} }

View File

@@ -2,6 +2,7 @@ package io.nekohasekai.sfa.vendor
import android.content.Context import android.content.Context
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.utils.HookStatusClient import io.nekohasekai.sfa.utils.HookStatusClient
import io.nekohasekai.sfa.xposed.XposedActivation import io.nekohasekai.sfa.xposed.XposedActivation
@@ -43,7 +44,7 @@ object ApkInstaller {
val method = getConfiguredMethod() val method = getConfiguredMethod()
return when (method) { return when (method) {
InstallMethod.PACKAGE_INSTALLER -> canSystemSilentInstall() InstallMethod.PACKAGE_INSTALLER -> canSystemSilentInstall()
InstallMethod.ROOT -> RootInstaller.checkAccess() InstallMethod.ROOT -> RootClient.checkRootAvailable()
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import androidx.camera.core.ImageAnalysis
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.compose.screen.qrscan.QRCodeCropArea import io.nekohasekai.sfa.compose.screen.qrscan.QRCodeCropArea
import io.nekohasekai.sfa.update.UpdateCheckException import io.nekohasekai.sfa.update.UpdateCheckException
@@ -125,7 +126,7 @@ object Vendor : VendorInterface {
"PACKAGE_INSTALLER" -> { "PACKAGE_INSTALLER" -> {
ApkInstaller.canSystemSilentInstall() ApkInstaller.canSystemSilentInstall()
} }
"ROOT" -> RootInstaller.checkAccess() "ROOT" -> RootClient.checkRootAvailable()
else -> false else -> false
} }
} }