manager: Optimizing Home Performance
- Reorganize Home structure using MVVM architecture pattern to separate UI and data logic
This commit is contained in:
@@ -27,6 +27,11 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
data class FlashState(
|
data class FlashState(
|
||||||
val isFlashing: Boolean = false,
|
val isFlashing: Boolean = false,
|
||||||
val isCompleted: Boolean = false,
|
val isCompleted: Boolean = false,
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ import java.io.File
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
enum class FlashingStatus {
|
enum class FlashingStatus {
|
||||||
FLASHING,
|
FLASHING,
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
|
|||||||
@@ -58,17 +58,16 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
@@ -83,16 +82,14 @@ 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 androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
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.InstallScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.sukisu.ultra.KernelVersion
|
import com.sukisu.ultra.KernelVersion
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.Natives.isKPMEnabled
|
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.getKernelVersion
|
|
||||||
import com.sukisu.ultra.ksuApp
|
|
||||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
@@ -102,65 +99,43 @@ import com.sukisu.ultra.ui.util.checkNewVersion
|
|||||||
import com.sukisu.ultra.ui.util.getKpmModuleCount
|
import com.sukisu.ultra.ui.util.getKpmModuleCount
|
||||||
import com.sukisu.ultra.ui.util.getKpmVersion
|
import com.sukisu.ultra.ui.util.getKpmVersion
|
||||||
import com.sukisu.ultra.ui.util.getModuleCount
|
import com.sukisu.ultra.ui.util.getModuleCount
|
||||||
import com.sukisu.ultra.ui.util.getSELinuxStatus
|
|
||||||
import com.sukisu.ultra.ui.util.getSuSFS
|
|
||||||
import com.sukisu.ultra.ui.util.getSuSFSFeatures
|
|
||||||
import com.sukisu.ultra.ui.util.getSuSFSVariant
|
|
||||||
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
|
||||||
import com.sukisu.ultra.ui.util.getSuperuserCount
|
import com.sukisu.ultra.ui.util.getSuperuserCount
|
||||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||||
import com.sukisu.ultra.ui.util.reboot
|
import com.sukisu.ultra.ui.util.reboot
|
||||||
import com.sukisu.ultra.ui.util.rootAvailable
|
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||||
import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>(start = true)
|
@Destination<RootGraph>(start = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var isSimpleMode by rememberSaveable { mutableStateOf(false) }
|
val viewModel = viewModel<HomeViewModel>()
|
||||||
var isHideVersion by rememberSaveable { mutableStateOf(false) }
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var isHideLinkCard by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var showKpmInfo by rememberSaveable { mutableStateOf(true) }
|
|
||||||
|
|
||||||
// 从 SharedPreferences 加载简洁模式状态
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
// 初始化加载用户设置
|
||||||
.getBoolean("is_simple_mode", false)
|
viewModel.loadUserSettings(context)
|
||||||
|
// 初始化数据
|
||||||
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
viewModel.initializeData()
|
||||||
.getBoolean("is_hide_version", false)
|
// 检查更新
|
||||||
|
viewModel.checkForUpdates(context)
|
||||||
isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("is_hide_other_info", false)
|
|
||||||
|
|
||||||
isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("is_hide_susfs_status", false)
|
|
||||||
|
|
||||||
isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("is_hide_link_card", false)
|
|
||||||
|
|
||||||
showKpmInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("show_kpm_info", true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val kernelVersion = getKernelVersion()
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val debounceTime = 100L
|
|
||||||
var lastScrollTime by remember { mutableLongStateOf(0L) }
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
kernelVersion,
|
kernelVersion = viewModel.systemStatus.kernelVersion,
|
||||||
onInstallClick = { navigator.navigate(InstallScreenDestination) },
|
onInstallClick = { navigator.navigate(InstallScreenDestination) },
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
@@ -169,6 +144,14 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||||
)
|
)
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
PullToRefreshBox(
|
||||||
|
onRefresh = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.refreshAllData(context)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isRefreshing = viewModel.isRefreshing
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
@@ -179,41 +162,46 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
StatusCard(
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
systemStatus = viewModel.systemStatus,
|
||||||
val lkmMode = ksuVersion?.let {
|
onClickInstall = {
|
||||||
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusCard(kernelVersion, ksuVersion, lkmMode) {
|
|
||||||
navigator.navigate(InstallScreenDestination)
|
navigator.navigate(InstallScreenDestination)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (isManager && Natives.requireNewKernel()) {
|
if (viewModel.systemStatus.requireNewKernel) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL
|
viewModel.systemStatus.ksuVersion,
|
||||||
|
Natives.MINIMAL_SUPPORTED_KERNEL
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ksuVersion != null && !rootAvailable()) {
|
if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.grant_root_failed)
|
stringResource(id = R.string.grant_root_failed)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val checkUpdate =
|
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("check_update", true)
|
.getBoolean("check_update", true)
|
||||||
if (checkUpdate) {
|
if (checkUpdate) {
|
||||||
UpdateCard()
|
UpdateCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoCard()
|
InfoCard(
|
||||||
|
systemInfo = viewModel.systemInfo,
|
||||||
|
isSimpleMode = viewModel.isSimpleMode,
|
||||||
|
isHideVersion = viewModel.isHideVersion,
|
||||||
|
isHideOtherInfo = viewModel.isHideOtherInfo,
|
||||||
|
isHideSusfsStatus = viewModel.isHideSusfsStatus,
|
||||||
|
showKpmInfo = viewModel.showKpmInfo,
|
||||||
|
lkmMode = viewModel.systemStatus.lkmMode,
|
||||||
|
)
|
||||||
|
|
||||||
if (!isSimpleMode) {
|
if (!viewModel.isSimpleMode) {
|
||||||
if (!isHideLinkCard) {
|
if (!viewModel.isHideLinkCard) {
|
||||||
ContributionCard()
|
ContributionCard()
|
||||||
DonateCard()
|
DonateCard()
|
||||||
LearnMoreCard()
|
LearnMoreCard()
|
||||||
@@ -222,18 +210,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(scrollState) {
|
|
||||||
snapshotFlow { scrollState.isScrollInProgress }
|
|
||||||
.debounce(debounceTime)
|
|
||||||
.collect { isScrolling ->
|
|
||||||
if (isScrolling) {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastScrollTime > debounceTime) {
|
|
||||||
lastScrollTime = currentTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,9 +333,7 @@ private fun TopBar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StatusCard(
|
private fun StatusCard(
|
||||||
kernelVersion: KernelVersion,
|
systemStatus: HomeViewModel.SystemStatus,
|
||||||
ksuVersion: Int?,
|
|
||||||
lkmMode: Boolean?,
|
|
||||||
onClickInstall: () -> Unit = {}
|
onClickInstall: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
@@ -378,7 +352,7 @@ private fun StatusCard(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
if (rootAvailable() || kernelVersion.isGKI()) {
|
if (systemStatus.isRootAvailable || systemStatus.kernelVersion.isGKI()) {
|
||||||
onClickInstall()
|
onClickInstall()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,12 +360,12 @@ private fun StatusCard(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
ksuVersion != null -> {
|
systemStatus.ksuVersion != null -> {
|
||||||
|
|
||||||
val workingModeText = when {
|
val workingModeText = when {
|
||||||
lkmMode == true -> "LKM"
|
systemStatus.lkmMode == true -> "LKM"
|
||||||
lkmMode == null && kernelVersion.isGKI1() -> "GKI1.0"
|
systemStatus.lkmMode == null && systemStatus.kernelVersion.isGKI1() -> "GKI1.0"
|
||||||
lkmMode == false || kernelVersion.isGKI() -> "GKI2.0"
|
systemStatus.lkmMode == false || systemStatus.kernelVersion.isGKI() -> "GKI2.0"
|
||||||
else -> "N-GKI"
|
else -> "N-GKI"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +438,7 @@ private fun StatusCard(
|
|||||||
if (!isHideVersion) {
|
if (!isHideVersion) {
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
text = stringResource(R.string.home_working_version, systemStatus.ksuVersion),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -498,7 +472,7 @@ private fun StatusCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kernelVersion.isGKI() -> {
|
systemStatus.kernelVersion.isGKI() -> {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Warning,
|
Icons.Outlined.Warning,
|
||||||
contentDescription = stringResource(R.string.home_not_installed),
|
contentDescription = stringResource(R.string.home_not_installed),
|
||||||
@@ -717,14 +691,15 @@ fun DonateCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoCard() {
|
private fun InfoCard(
|
||||||
val lkmMode = Natives.isLkmMode
|
systemInfo: HomeViewModel.SystemInfo,
|
||||||
val context = LocalContext.current
|
isSimpleMode: Boolean,
|
||||||
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
isHideVersion: Boolean,
|
||||||
.getBoolean("is_simple_mode", false)
|
isHideOtherInfo: Boolean,
|
||||||
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
isHideSusfsStatus: Boolean,
|
||||||
.getBoolean("show_kpm_info", true)
|
showKpmInfo: Boolean,
|
||||||
|
lkmMode: Boolean?
|
||||||
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainer),
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainer),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
|
||||||
@@ -741,17 +716,13 @@ private fun InfoCard() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
|
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||||
) withContext@{
|
) {
|
||||||
val contents = StringBuilder()
|
|
||||||
val uname = Os.uname()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InfoCardItem(
|
fun InfoCardItem(
|
||||||
label: String,
|
label: String,
|
||||||
content: String,
|
content: String,
|
||||||
icon: ImageVector = Icons.Default.Info
|
icon: ImageVector = Icons.Default.Info
|
||||||
) {
|
) {
|
||||||
contents.appendLine(label).appendLine(content).appendLine()
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -787,55 +758,49 @@ private fun InfoCard() {
|
|||||||
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_kernel),
|
stringResource(R.string.home_kernel),
|
||||||
uname.release,
|
systemInfo.kernelRelease,
|
||||||
icon = Icons.Default.Memory,
|
icon = Icons.Default.Memory,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isSimpleMode) {
|
if (!isSimpleMode) {
|
||||||
val androidVersion = Build.VERSION.RELEASE
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_android_version),
|
stringResource(R.string.home_android_version),
|
||||||
androidVersion,
|
systemInfo.androidVersion,
|
||||||
icon = Icons.Default.Android,
|
icon = Icons.Default.Android,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val deviceModel = getDeviceModel()
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_device_model),
|
stringResource(R.string.home_device_model),
|
||||||
deviceModel,
|
systemInfo.deviceModel,
|
||||||
icon = Icons.Default.PhoneAndroid,
|
icon = Icons.Default.PhoneAndroid,
|
||||||
)
|
)
|
||||||
|
|
||||||
val managerVersion = getManagerVersion(context)
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_manager_version),
|
stringResource(R.string.home_manager_version),
|
||||||
"${managerVersion.first} (${managerVersion.second})",
|
"${systemInfo.managerVersion.first} (${systemInfo.managerVersion.second})",
|
||||||
icon = Icons.Default.SettingsSuggest,
|
icon = Icons.Default.SettingsSuggest,
|
||||||
)
|
)
|
||||||
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_selinux_status),
|
stringResource(R.string.home_selinux_status),
|
||||||
getSELinuxStatus(),
|
systemInfo.seLinuxStatus,
|
||||||
icon = Icons.Default.Security,
|
icon = Icons.Default.Security,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isSimpleMode) {
|
if (!isSimpleMode) {
|
||||||
if (lkmMode != true) {
|
if (lkmMode != true) {
|
||||||
val kpmVersion = getKpmVersion()
|
|
||||||
val isKpmConfigured = checkKPMEnabled()
|
|
||||||
|
|
||||||
// 根据showKpmInfo决定是否显示KPM信息
|
// 根据showKpmInfo决定是否显示KPM信息
|
||||||
if (showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
|
if (showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
|
||||||
val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
val displayVersion = if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
|
||||||
val statusText = if (isKpmConfigured) {
|
val statusText = if (Natives.isKPMEnabled()) {
|
||||||
stringResource(R.string.kernel_patched)
|
stringResource(R.string.kernel_patched)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.kernel_not_enabled)
|
stringResource(R.string.kernel_not_enabled)
|
||||||
}
|
}
|
||||||
"${stringResource(R.string.not_supported)} ($statusText)"
|
"${stringResource(R.string.not_supported)} ($statusText)"
|
||||||
} else {
|
} else {
|
||||||
"${stringResource(R.string.supported)} ($kpmVersion)"
|
"${stringResource(R.string.supported)} (${systemInfo.kpmVersion})"
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
@@ -847,22 +812,16 @@ private fun InfoCard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("is_hide_susfs_status", false)
|
|
||||||
|
|
||||||
if ((!isSimpleMode) && (!isHideSusfsStatus)) {
|
if ((!isSimpleMode) && (!isHideSusfsStatus)) {
|
||||||
val suSFS = getSuSFS()
|
if (systemInfo.suSFSStatus == "Supported") {
|
||||||
if (suSFS == "Supported") {
|
if (systemInfo.suSFSVersion.isNotEmpty()) {
|
||||||
val suSFSVersion = getSuSFSVersion()
|
val isSUS_SU = systemInfo.suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||||
if (suSFSVersion.isNotEmpty()) {
|
|
||||||
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
|
|
||||||
val infoText = buildString {
|
val infoText = buildString {
|
||||||
append(suSFSVersion)
|
append(systemInfo.suSFSVersion)
|
||||||
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})")
|
append(if (isSUS_SU) " (${systemInfo.suSFSVariant})" else " (${stringResource(R.string.manual_hook)})")
|
||||||
if (isSUS_SU) {
|
if (isSUS_SU) {
|
||||||
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" }
|
if (systemInfo.susSUMode.isNotEmpty()) {
|
||||||
if (susSUMode.isNotEmpty()) {
|
append(" ${stringResource(R.string.sus_su_mode)} ${systemInfo.susSUMode}")
|
||||||
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -889,10 +848,45 @@ fun getManagerVersion(context: Context): Pair<String, Long> {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun StatusCardPreview() {
|
private fun StatusCardPreview() {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
StatusCard(KernelVersion(5, 10, 101), 1, null)
|
StatusCard(
|
||||||
StatusCard(KernelVersion(5, 10, 101), 20000, true)
|
HomeViewModel.SystemStatus(
|
||||||
StatusCard(KernelVersion(5, 10, 101), null, true)
|
isManager = true,
|
||||||
StatusCard(KernelVersion(4, 10, 101), null, false)
|
ksuVersion = 1,
|
||||||
|
lkmMode = null,
|
||||||
|
kernelVersion = KernelVersion(5, 10, 101),
|
||||||
|
isRootAvailable = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
StatusCard(
|
||||||
|
HomeViewModel.SystemStatus(
|
||||||
|
isManager = true,
|
||||||
|
ksuVersion = 20000,
|
||||||
|
lkmMode = true,
|
||||||
|
kernelVersion = KernelVersion(5, 10, 101),
|
||||||
|
isRootAvailable = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
StatusCard(
|
||||||
|
HomeViewModel.SystemStatus(
|
||||||
|
isManager = false,
|
||||||
|
ksuVersion = null,
|
||||||
|
lkmMode = true,
|
||||||
|
kernelVersion = KernelVersion(5, 10, 101),
|
||||||
|
isRootAvailable = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
StatusCard(
|
||||||
|
HomeViewModel.SystemStatus(
|
||||||
|
isManager = false,
|
||||||
|
ksuVersion = null,
|
||||||
|
lkmMode = false,
|
||||||
|
kernelVersion = KernelVersion(4, 10, 101),
|
||||||
|
isRootAvailable = false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,47 +902,6 @@ private fun WarningCardPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object DeviceModelCache {
|
|
||||||
@Volatile
|
|
||||||
private var cachedModel: String? = null
|
|
||||||
|
|
||||||
fun getDeviceModel(): String {
|
|
||||||
return cachedModel ?: synchronized(this) {
|
|
||||||
cachedModel ?: try {
|
|
||||||
val systemProperties = Class.forName("android.os.SystemProperties")
|
|
||||||
val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java)
|
|
||||||
val marketNameKeys = listOf(
|
|
||||||
"ro.product.marketname", // Xiaomi
|
|
||||||
"ro.vendor.oplus.market.name", // Oppo, OnePlus, Realme
|
|
||||||
"ro.vivo.market.name", // Vivo
|
|
||||||
"ro.config.marketing_name" // Huawei
|
|
||||||
)
|
|
||||||
var result = Build.DEVICE
|
|
||||||
for (key in marketNameKeys) {
|
|
||||||
val marketName = getMethod.invoke(null, key, "") as String
|
|
||||||
if (marketName.isNotEmpty()) {
|
|
||||||
result = marketName
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
} catch (_: Exception) {
|
|
||||||
Build.DEVICE
|
|
||||||
}.also { cachedModel = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取设备型号
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
private fun getDeviceModel(): String {
|
|
||||||
return DeviceModelCache.getDeviceModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查KPM是否存在
|
|
||||||
private fun checkKPMEnabled(): Boolean {
|
|
||||||
return isKPMEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnnecessaryComposedModifier")
|
@SuppressLint("UnnecessaryComposedModifier")
|
||||||
fun Modifier.disableOverscroll(): Modifier = composed {
|
fun Modifier.disableOverscroll(): Modifier = composed {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ import com.sukisu.ultra.ui.util.rootAvailable
|
|||||||
import com.sukisu.ultra.getKernelVersion
|
import com.sukisu.ultra.getKernelVersion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author ShirkNeko
|
||||||
* @date 2024/3/12.
|
* @date 2025/5/31.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ import androidx.compose.ui.input.key.Key
|
|||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
private object KernelFlashStateHolder {
|
private object KernelFlashStateHolder {
|
||||||
var currentState: HorizonKernelState? = null
|
var currentState: HorizonKernelState? = null
|
||||||
var currentUri: Uri? = null
|
var currentUri: Uri? = null
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ import androidx.core.net.toUri
|
|||||||
import com.dergoogler.mmrl.platform.model.ModuleConfig
|
import com.dergoogler.mmrl.platform.model.ModuleConfig
|
||||||
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ import kotlinx.coroutines.launch
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
fun saveCardConfig(context: Context) {
|
fun saveCardConfig(context: Context) {
|
||||||
CardConfig.save(context)
|
CardConfig.save(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ import com.sukisu.ultra.ui.util.getBugreportFile
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
|||||||
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.sukisu.ultra.ui.util
|
package com.sukisu.ultra.ui.util
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import android.content.Context
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
@Composable
|
fun getSELinuxStatus(context: Context): String {
|
||||||
fun getSELinuxStatus(): String {
|
|
||||||
val shell = Shell.Builder.create().build("sh")
|
val shell = Shell.Builder.create().build("sh")
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
|
|
||||||
@@ -18,16 +16,16 @@ fun getSELinuxStatus(): String {
|
|||||||
|
|
||||||
return if (result.isSuccess) {
|
return if (result.isSuccess) {
|
||||||
when (output) {
|
when (output) {
|
||||||
"Enforcing" -> stringResource(R.string.selinux_status_enforcing)
|
"Enforcing" -> context.getString(R.string.selinux_status_enforcing)
|
||||||
"Permissive" -> stringResource(R.string.selinux_status_permissive)
|
"Permissive" -> context.getString(R.string.selinux_status_permissive)
|
||||||
"Disabled" -> stringResource(R.string.selinux_status_disabled)
|
"Disabled" -> context.getString(R.string.selinux_status_disabled)
|
||||||
else -> stringResource(R.string.selinux_status_unknown)
|
else -> context.getString(R.string.selinux_status_unknown)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (output.contains("Permission denied")) {
|
if (output.contains("Permission denied")) {
|
||||||
stringResource(R.string.selinux_status_enforcing)
|
context.getString(R.string.selinux_status_enforcing)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.selinux_status_unknown)
|
context.getString(R.string.selinux_status_unknown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package com.sukisu.ultra.ui.viewmodel
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.system.Os
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.sukisu.ultra.KernelVersion
|
||||||
|
import com.sukisu.ultra.Natives
|
||||||
|
import com.sukisu.ultra.getKernelVersion
|
||||||
|
import com.sukisu.ultra.ksuApp
|
||||||
|
import com.sukisu.ultra.ui.util.checkNewVersion
|
||||||
|
import com.sukisu.ultra.ui.util.getKpmModuleCount
|
||||||
|
import com.sukisu.ultra.ui.util.getKpmVersion
|
||||||
|
import com.sukisu.ultra.ui.util.getModuleCount
|
||||||
|
import com.sukisu.ultra.ui.util.getSELinuxStatus
|
||||||
|
import com.sukisu.ultra.ui.util.getSuSFS
|
||||||
|
import com.sukisu.ultra.ui.util.getSuSFSFeatures
|
||||||
|
import com.sukisu.ultra.ui.util.getSuSFSVariant
|
||||||
|
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
||||||
|
import com.sukisu.ultra.ui.util.getSuperuserCount
|
||||||
|
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||||
|
import com.sukisu.ultra.ui.util.rootAvailable
|
||||||
|
import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import com.topjohnwu.superuser.internal.Utils.context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
|
class HomeViewModel : ViewModel() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HomeViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统状态
|
||||||
|
data class SystemStatus(
|
||||||
|
val isManager: Boolean = false,
|
||||||
|
val ksuVersion: Int? = null,
|
||||||
|
val lkmMode: Boolean? = null,
|
||||||
|
val kernelVersion: KernelVersion = getKernelVersion(),
|
||||||
|
val isRootAvailable: Boolean = false,
|
||||||
|
val isKpmConfigured: Boolean = false,
|
||||||
|
val requireNewKernel: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// 系统信息
|
||||||
|
data class SystemInfo(
|
||||||
|
val kernelRelease: String = "",
|
||||||
|
val androidVersion: String = "",
|
||||||
|
val deviceModel: String = "",
|
||||||
|
val managerVersion: Pair<String, Long> = Pair("", 0L),
|
||||||
|
val seLinuxStatus: String = "",
|
||||||
|
val kpmVersion: String = "",
|
||||||
|
val suSFSStatus: String = "",
|
||||||
|
val suSFSVersion: String = "",
|
||||||
|
val suSFSVariant: String = "",
|
||||||
|
val suSFSFeatures: String = "",
|
||||||
|
val susSUMode: String = "",
|
||||||
|
val superuserCount: Int = 0,
|
||||||
|
val moduleCount: Int = 0,
|
||||||
|
val kpmModuleCount: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// UI状态
|
||||||
|
var isRefreshing by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 系统状态信息
|
||||||
|
var systemStatus by mutableStateOf(SystemStatus())
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 系统详细信息
|
||||||
|
var systemInfo by mutableStateOf(SystemInfo())
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 更新信息
|
||||||
|
var latestVersionInfo by mutableStateOf(LatestVersionInfo())
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 用户设置
|
||||||
|
var isSimpleMode by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var isHideVersion by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var isHideOtherInfo by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var isHideSusfsStatus by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var isHideLinkCard by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var showKpmInfo by mutableStateOf(true)
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 加载用户设置
|
||||||
|
fun loadUserSettings(context: Context) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
isSimpleMode = prefs.getBoolean("is_simple_mode", false)
|
||||||
|
isHideVersion = prefs.getBoolean("is_hide_version", false)
|
||||||
|
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false)
|
||||||
|
isHideSusfsStatus = prefs.getBoolean("is_hide_susfs_status", false)
|
||||||
|
isHideLinkCard = prefs.getBoolean("is_hide_link_card", false)
|
||||||
|
showKpmInfo = prefs.getBoolean("show_kpm_info", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
fun initializeData() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
fetchSystemStatus()
|
||||||
|
fetchSystemInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
fun checkForUpdates(context: Context) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("check_update", true)
|
||||||
|
|
||||||
|
if (checkUpdate) {
|
||||||
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
latestVersionInfo = checkNewVersion()
|
||||||
|
Log.i(TAG, "Update check completed in ${SystemClock.elapsedRealtime() - start}ms")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error checking for updates", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新所有数据
|
||||||
|
fun refreshAllData(context: Context) {
|
||||||
|
isRefreshing = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
fetchSystemStatus()
|
||||||
|
fetchSystemInfo()
|
||||||
|
checkForUpdates(context)
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取系统状态
|
||||||
|
private suspend fun fetchSystemStatus() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val kernelVersion = getKernelVersion()
|
||||||
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
|
val ksuVersion = if (isManager) Natives.version else null
|
||||||
|
val lkmMode = ksuVersion?.let {
|
||||||
|
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null
|
||||||
|
}
|
||||||
|
|
||||||
|
systemStatus = SystemStatus(
|
||||||
|
isManager = isManager,
|
||||||
|
ksuVersion = ksuVersion,
|
||||||
|
lkmMode = lkmMode,
|
||||||
|
kernelVersion = kernelVersion,
|
||||||
|
isRootAvailable = rootAvailable(),
|
||||||
|
isKpmConfigured = Natives.isKPMEnabled(),
|
||||||
|
requireNewKernel = isManager && Natives.requireNewKernel()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error fetching system status", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取系统信息
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private suspend fun fetchSystemInfo() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val uname = Os.uname()
|
||||||
|
val kpmVersion = getKpmVersion()
|
||||||
|
val suSFS = getSuSFS()
|
||||||
|
var suSFSVersion = ""
|
||||||
|
var suSFSVariant = ""
|
||||||
|
var suSFSFeatures = ""
|
||||||
|
var susSUMode = ""
|
||||||
|
|
||||||
|
if (suSFS == "Supported") {
|
||||||
|
suSFSVersion = getSuSFSVersion()
|
||||||
|
if (suSFSVersion.isNotEmpty()) {
|
||||||
|
suSFSVariant = getSuSFSVariant()
|
||||||
|
suSFSFeatures = getSuSFSFeatures()
|
||||||
|
val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||||
|
if (isSUS_SU) {
|
||||||
|
susSUMode = try {
|
||||||
|
susfsSUS_SU_Mode().toString()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemInfo = SystemInfo(
|
||||||
|
kernelRelease = uname.release,
|
||||||
|
androidVersion = Build.VERSION.RELEASE,
|
||||||
|
deviceModel = getDeviceModel(),
|
||||||
|
managerVersion = getManagerVersion(ksuApp.applicationContext),
|
||||||
|
seLinuxStatus = getSELinuxStatus(context),
|
||||||
|
kpmVersion = kpmVersion,
|
||||||
|
suSFSStatus = suSFS,
|
||||||
|
suSFSVersion = suSFSVersion,
|
||||||
|
suSFSVariant = suSFSVariant,
|
||||||
|
suSFSFeatures = suSFSFeatures,
|
||||||
|
susSUMode = susSUMode,
|
||||||
|
superuserCount = getSuperuserCount(),
|
||||||
|
moduleCount = getModuleCount(),
|
||||||
|
kpmModuleCount = getKpmModuleCount()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error fetching system info", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备型号
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
private fun getDeviceModel(): String {
|
||||||
|
return try {
|
||||||
|
val systemProperties = Class.forName("android.os.SystemProperties")
|
||||||
|
val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java)
|
||||||
|
val marketNameKeys = listOf(
|
||||||
|
"ro.product.marketname", // Xiaomi
|
||||||
|
"ro.vendor.oplus.market.name", // Oppo, OnePlus, Realme
|
||||||
|
"ro.vivo.market.name", // Vivo
|
||||||
|
"ro.config.marketing_name" // Huawei
|
||||||
|
)
|
||||||
|
var result = Build.DEVICE
|
||||||
|
for (key in marketNameKeys) {
|
||||||
|
val marketName = getMethod.invoke(null, key, "") as String
|
||||||
|
if (marketName.isNotEmpty()) {
|
||||||
|
result = marketName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting device model", e)
|
||||||
|
Build.DEVICE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取管理器版本
|
||||||
|
private fun getManagerVersion(context: Context): Pair<String, Long> {
|
||||||
|
return try {
|
||||||
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)!!
|
||||||
|
val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo)
|
||||||
|
Pair(packageInfo.versionName!!, versionCode)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting manager version", e)
|
||||||
|
Pair("", 0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,10 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
class KpmViewModel : ViewModel() {
|
class KpmViewModel : ViewModel() {
|
||||||
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ import java.text.Collator
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
class ModuleViewModel : ViewModel() {
|
class ModuleViewModel : ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ import com.sukisu.ultra.ui.webui.getInstalledPackagesAll
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/5/31.
|
||||||
|
*/
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
val isPlatformAlive get() = Platform.isAlive
|
val isPlatformAlive get() = Platform.isAlive
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
Reference in New Issue
Block a user