From 044dd9471f081ce7c75dbf024ff5b3dec9861836 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Wed, 9 Jul 2025 01:33:00 +0800 Subject: [PATCH] manager: Refactor the way information is fetched in the home screen to avoid null pointers - add an auto-refresh cache feature --- .../java/com/sukisu/ultra/ui/screen/Home.kt | 2 +- .../ultra/ui/viewmodel/HomeViewModel.kt | 411 ++++++++++++++---- 2 files changed, 334 insertions(+), 79 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index 5108aab4..4ba9f3ce 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -726,7 +726,7 @@ private fun InfoCard( ) // 活跃管理器 - if (systemInfo.isDynamicSignEnabled && systemInfo.managersList != null) { + if (!isSimpleMode && systemInfo.isDynamicSignEnabled && systemInfo.managersList != null) { val signatureMap = systemInfo.managersList.managers.groupBy { it.signatureIndex } val managersText = buildString { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt index 50abca4f..20364d4b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.dergoogler.mmrl.platform.Platform.Companion.context import com.google.gson.Gson +import com.google.gson.JsonSyntaxException import com.sukisu.ultra.KernelVersion import com.sukisu.ultra.Natives import com.sukisu.ultra.getKernelVersion @@ -31,6 +32,8 @@ class HomeViewModel : ViewModel() { private const val KEY_SYSTEM_INFO = "system_info" private const val KEY_VERSION_INFO = "version_info" private const val KEY_LAST_UPDATE = "last_update_time" + private const val KEY_ERROR_COUNT = "error_count" + private const val MAX_ERROR_COUNT = 2 } // 系统状态 @@ -93,16 +96,67 @@ class HomeViewModel : ViewModel() { var showKpmInfo by mutableStateOf(false) private set + private fun clearAllCache() { + try { + prefs.edit { clear() } + Log.i(TAG, "All cache cleared successfully") + } catch (e: Exception) { + Log.e(TAG, "Error clearing cache", e) + } + } + + private fun resetToDefaults() { + systemStatus = SystemStatus() + systemInfo = SystemInfo() + latestVersionInfo = LatestVersionInfo() + isSimpleMode = false + isKernelSimpleMode = false + isHideVersion = false + isHideOtherInfo = false + isHideSusfsStatus = false + isHideLinkCard = false + showKpmInfo = false + } + + private fun handleError(error: Exception, operation: String) { + Log.e(TAG, "Error in $operation", error) + + val errorCount = prefs.getInt(KEY_ERROR_COUNT, 0) + val newErrorCount = errorCount + 1 + + if (newErrorCount >= MAX_ERROR_COUNT) { + Log.w(TAG, "Too many errors ($newErrorCount), clearing cache and resetting") + clearAllCache() + resetToDefaults() + } else { + prefs.edit { + putInt(KEY_ERROR_COUNT, newErrorCount) + } + } + } + + private fun String?.orSafe(default: String = ""): String { + return if (this.isNullOrBlank()) default else this + } + + private fun Pair?.orSafe(default: Pair): Pair { + return if (this?.first == null || this.second == null) default else Pair(this.first!!, this.second!!) + } + fun loadUserSettings(context: Context) { viewModelScope.launch(Dispatchers.IO) { - val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - isSimpleMode = prefs.getBoolean("is_simple_mode", false) - isKernelSimpleMode = prefs.getBoolean("is_kernel_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", false) + try { + val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false) + isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false) + isHideVersion = settingsPrefs.getBoolean("is_hide_version", false) + isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false) + isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false) + isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false) + showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false) + } catch (e: Exception) { + handleError(e, "loadUserSettings") + } } } @@ -110,42 +164,79 @@ class HomeViewModel : ViewModel() { viewModelScope.launch { try { loadCachedData() + // 成功加载后重置错误计数 + prefs.edit { + putInt(KEY_ERROR_COUNT, 0) + } } catch(e: Exception) { - Log.e(TAG, "Error when reading cached data", e) + handleError(e, "initializeData") } } } private fun loadCachedData() { - prefs.getString(KEY_SYSTEM_STATUS, null)?.let { - systemStatus = gson.fromJson(it, SystemStatus::class.java) - } - prefs.getString(KEY_SYSTEM_INFO, null)?.let { - systemInfo = gson.fromJson(it, SystemInfo::class.java) - } - prefs.getString(KEY_VERSION_INFO, null)?.let { - latestVersionInfo = gson.fromJson(it, LatestVersionInfo::class.java) + try { + prefs.getString(KEY_SYSTEM_STATUS, null)?.let { statusJson -> + try { + val cachedStatus = gson.fromJson(statusJson, SystemStatus::class.java) + if (cachedStatus != null) { + systemStatus = cachedStatus + } + } catch (e: JsonSyntaxException) { + Log.w(TAG, "Invalid system status JSON, using defaults", e) + } + } + + prefs.getString(KEY_SYSTEM_INFO, null)?.let { infoJson -> + try { + val cachedInfo = gson.fromJson(infoJson, SystemInfo::class.java) + if (cachedInfo != null) { + systemInfo = cachedInfo + } + } catch (e: JsonSyntaxException) { + Log.w(TAG, "Invalid system info JSON, using defaults", e) + } + } + + prefs.getString(KEY_VERSION_INFO, null)?.let { versionJson -> + try { + val cachedVersion = gson.fromJson(versionJson, LatestVersionInfo::class.java) + if (cachedVersion != null) { + latestVersionInfo = cachedVersion + } + } catch (e: JsonSyntaxException) { + Log.w(TAG, "Invalid version info JSON, using defaults", e) + } + } + } catch (e: Exception) { + Log.e(TAG, "Error loading cached data", e) + throw e } } private suspend fun fetchAndSaveData() { - fetchSystemStatus() - fetchSystemInfo() - withContext(Dispatchers.IO) { - prefs.edit { - putString(KEY_SYSTEM_STATUS, gson.toJson(systemStatus)) - putString(KEY_SYSTEM_INFO, gson.toJson(systemInfo)) - putString(KEY_VERSION_INFO, gson.toJson(latestVersionInfo)) - putLong(KEY_LAST_UPDATE, System.currentTimeMillis()) + try { + fetchSystemStatus() + fetchSystemInfo() + withContext(Dispatchers.IO) { + prefs.edit { + putString(KEY_SYSTEM_STATUS, gson.toJson(systemStatus)) + putString(KEY_SYSTEM_INFO, gson.toJson(systemInfo)) + putString(KEY_VERSION_INFO, gson.toJson(latestVersionInfo)) + putLong(KEY_LAST_UPDATE, System.currentTimeMillis()) + putInt(KEY_ERROR_COUNT, 0) + } } + } catch (e: Exception) { + handleError(e, "fetchAndSaveData") } } fun checkForUpdates(context: Context) { viewModelScope.launch(Dispatchers.IO) { try { - val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("check_update", true) + val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + val checkUpdate = settingsPrefs.getBoolean("check_update", true) if (checkUpdate) { val newVersionInfo = checkNewVersion() @@ -156,7 +247,7 @@ class HomeViewModel : ViewModel() { } } } catch (e: Exception) { - Log.e(TAG, "Error checking for updates", e) + handleError(e, "checkForUpdates") } } } @@ -167,7 +258,7 @@ class HomeViewModel : ViewModel() { fetchAndSaveData() checkForUpdates(context) } catch (e: Exception) { - Log.e(TAG, "Error refreshing data", e) + handleError(e, "refreshAllData") } } } @@ -176,21 +267,46 @@ class HomeViewModel : ViewModel() { withContext(Dispatchers.IO) { try { val kernelVersion = getKernelVersion() - val isManager = Natives.becomeManager(ksuApp.packageName) - val ksuVersion = if (isManager) Natives.version else null - val fullVersion = Natives.getFullVersion() + val isManager = try { + Natives.becomeManager(ksuApp.packageName.orSafe("com.sukisu.ultra")) + } catch (e: Exception) { + Log.w(TAG, "Failed to become manager", e) + false + } + + val ksuVersion = if (isManager) { + try { + Natives.version + } catch (e: Exception) { + Log.w(TAG, "Failed to get KSU version", e) + null + } + } else null + + val fullVersion = try { + Natives.getFullVersion().orSafe("Unknown") + } catch (e: Exception) { + Log.w(TAG, "Failed to get full version", e) + "Unknown" + } + val ksuFullVersion = if (isKernelSimpleMode) { - val startIndex = fullVersion.indexOf('v') - if (startIndex >= 0) { - val endIndex = fullVersion.indexOf('-', startIndex) - val versionStr = if (endIndex > startIndex) { - fullVersion.substring(startIndex, endIndex) + try { + val startIndex = fullVersion.indexOf('v') + if (startIndex >= 0) { + val endIndex = fullVersion.indexOf('-', startIndex) + val versionStr = if (endIndex > startIndex) { + fullVersion.substring(startIndex, endIndex) + } else { + fullVersion.substring(startIndex) + } + val numericVersion = "v" + (Regex("""\d+(\.\d+)*""").find(versionStr)?.value ?: versionStr) + numericVersion } else { - fullVersion.substring(startIndex) + fullVersion } - val numericVersion = "v" + (Regex("""\d+(\.\d+)*""").find(versionStr)?.value ?: versionStr) - numericVersion - } else { + } catch (e: Exception) { + Log.w(TAG, "Failed to process full version", e) fullVersion } } else { @@ -198,7 +314,35 @@ class HomeViewModel : ViewModel() { } val lkmMode = ksuVersion?.let { - if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null + try { + if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) { + Natives.isLkmMode + } else null + } catch (e: Exception) { + Log.w(TAG, "Failed to get LKM mode", e) + null + } + } + + val isRootAvailable = try { + rootAvailable() + } catch (e: Exception) { + Log.w(TAG, "Failed to check root availability", e) + false + } + + val isKpmConfigured = try { + Natives.isKPMEnabled() + } catch (e: Exception) { + Log.w(TAG, "Failed to check KPM status", e) + false + } + + val requireNewKernel = try { + isManager && Natives.requireNewKernel() + } catch (e: Exception) { + Log.w(TAG, "Failed to check kernel requirement", e) + false } systemStatus = SystemStatus( @@ -207,12 +351,13 @@ class HomeViewModel : ViewModel() { ksuFullVersion = ksuFullVersion, lkmMode = lkmMode, kernelVersion = kernelVersion, - isRootAvailable = rootAvailable(), - isKpmConfigured = Natives.isKPMEnabled(), - requireNewKernel = isManager && Natives.requireNewKernel() + isRootAvailable = isRootAvailable, + isKpmConfigured = isKpmConfigured, + requireNewKernel = requireNewKernel ) } catch (e: Exception) { Log.e(TAG, "Error fetching system status", e) + throw e } } } @@ -221,24 +366,61 @@ class HomeViewModel : ViewModel() { private suspend fun fetchSystemInfo() { withContext(Dispatchers.IO) { try { - val uname = Os.uname() - val kpmVersion = getKpmVersion() - val suSFS = getSuSFS() + val uname = try { + Os.uname() + } catch (e: Exception) { + Log.w(TAG, "Failed to get uname", e) + null + } + + val kpmVersion = try { + getKpmVersion().orSafe("Unknown") + } catch (e: Exception) { + Log.w(TAG, "Failed to get kpm version", e) + "Unknown" + } + + val suSFS = try { + getSuSFS().orSafe("Unknown") + } catch (e: Exception) { + Log.w(TAG, "Failed to get SuSFS", e) + "Unknown" + } + var suSFSVersion = "" var suSFSVariant = "" var suSFSFeatures = "" var susSUMode = "" if (suSFS == "Supported") { - suSFSVersion = getSuSFSVersion() + suSFSVersion = try { + getSuSFSVersion().orSafe("") + } catch (e: Exception) { + Log.w(TAG, "Failed to get SuSFS version", e) + "" + } + if (suSFSVersion.isNotEmpty()) { - suSFSVariant = getSuSFSVariant() - suSFSFeatures = getSuSFSFeatures() + suSFSVariant = try { + getSuSFSVariant().orSafe("") + } catch (e: Exception) { + Log.w(TAG, "Failed to get SuSFS variant", e) + "" + } + + suSFSFeatures = try { + getSuSFSFeatures().orSafe("") + } catch (e: Exception) { + Log.w(TAG, "Failed to get SuSFS features", e) + "" + } + val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU" if (isSUS_SU) { susSUMode = try { - susfsSUS_SU_Mode().toString() - } catch (_: Exception) { + susfsSUS_SU_Mode() + } catch (e: Exception) { + Log.w(TAG, "Failed to get SUS SU mode", e) "" } } @@ -253,7 +435,13 @@ class HomeViewModel : ViewModel() { null } - val isDynamicSignEnabled = dynamicSignConfig?.isValid() == true + val isDynamicSignEnabled = try { + dynamicSignConfig?.isValid() == true + } catch (e: Exception) { + Log.w(TAG, "Failed to check dynamic sign validity", e) + false + } + val managersList = if (isDynamicSignEnabled) { try { Natives.getManagersList() @@ -265,39 +453,101 @@ class HomeViewModel : ViewModel() { null } + val deviceModel = try { + getDeviceModel().orSafe("Unknown") + } catch (e: Exception) { + Log.w(TAG, "Failed to get device model", e) + "Unknown" + } + + val managerVersion = try { + getManagerVersion(ksuApp.applicationContext).orSafe(Pair("Unknown", 0L)) + } catch (e: Exception) { + Log.w(TAG, "Failed to get manager version", e) + Pair("Unknown", 0L) + } + + val seLinuxStatus = try { + getSELinuxStatus(context).orSafe("Unknown") + } catch (e: Exception) { + Log.w(TAG, "Failed to get SELinux status", e) + "Unknown" + } + + val superuserCount = try { + getSuperuserCount() + } catch (e: Exception) { + Log.w(TAG, "Failed to get superuser count", e) + 0 + } + + val moduleCount = try { + getModuleCount() + } catch (e: Exception) { + Log.w(TAG, "Failed to get module count", e) + 0 + } + + val kpmModuleCount = try { + getKpmModuleCount() + } catch (e: Exception) { + Log.w(TAG, "Failed to get kpm module count", e) + 0 + } + + val zygiskImplement = try { + getZygiskImplement().orSafe("None") + } catch (e: Exception) { + Log.w(TAG, "Failed to get Zygisk implement", e) + "None" + } + systemInfo = SystemInfo( - kernelRelease = uname.release, - androidVersion = Build.VERSION.RELEASE, - deviceModel = getDeviceModel(), - managerVersion = getManagerVersion(ksuApp.applicationContext), - seLinuxStatus = getSELinuxStatus(context), + kernelRelease = uname?.release.orSafe("Unknown"), + androidVersion = Build.VERSION.RELEASE.orSafe("Unknown"), + deviceModel = deviceModel, + managerVersion = managerVersion, + seLinuxStatus = seLinuxStatus, kpmVersion = kpmVersion, suSFSStatus = suSFS, suSFSVersion = suSFSVersion, suSFSVariant = suSFSVariant, suSFSFeatures = suSFSFeatures, susSUMode = susSUMode, - superuserCount = getSuperuserCount(), - moduleCount = getModuleCount(), - kpmModuleCount = getKpmModuleCount(), + superuserCount = superuserCount, + moduleCount = moduleCount, + kpmModuleCount = kpmModuleCount, managersList = managersList, isDynamicSignEnabled = isDynamicSignEnabled, - zygiskImplement = getZygiskImplement() + zygiskImplement = zygiskImplement ) } catch (e: Exception) { Log.e(TAG, "Error fetching system info", e) + throw e } } } private fun getDeviceInfo(): String { - var manufacturer = - Build.MANUFACTURER[0].uppercaseChar().toString() + Build.MANUFACTURER.substring(1) - if (!Build.BRAND.equals(Build.MANUFACTURER, ignoreCase = true)) { - manufacturer += " " + Build.BRAND[0].uppercaseChar() + Build.BRAND.substring(1) + return try { + var manufacturer = Build.MANUFACTURER.orSafe("Unknown") + manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1) + + val brand = Build.BRAND.orSafe("") + if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) { + manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1) + } + + val model = Build.MODEL.orSafe("") + if (model.isNotEmpty()) { + manufacturer += " $model " + } + + manufacturer + } catch (e: Exception) { + Log.w(TAG, "Failed to get device info", e) + "Unknown Device" } - manufacturer += " " + Build.MODEL + " " - return manufacturer } @SuppressLint("PrivateApi") @@ -313,27 +563,32 @@ class HomeViewModel : ViewModel() { ) var result = getDeviceInfo() for (key in marketNameKeys) { - val marketName = getMethod.invoke(null, key, "") as String - if (marketName.isNotEmpty()) { - result = marketName - break + try { + val marketName = getMethod.invoke(null, key, "") as String + if (marketName.isNotEmpty()) { + result = marketName + break + } + } catch (e: Exception) { + Log.w(TAG, "Failed to get market name for key: $key", e) } } result } catch (e: Exception) { - Log.e(TAG, "Error getting device model", e) + Log.w(TAG, "Error getting device model", e) getDeviceInfo() } } private fun getManagerVersion(context: Context): Pair { return try { - val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)!! + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo) - Pair(packageInfo.versionName!!, versionCode) + val versionName = packageInfo.versionName.orSafe("Unknown") + Pair(versionName, versionCode) } catch (e: Exception) { - Log.e(TAG, "Error getting manager version", e) - Pair("", 0L) + Log.w(TAG, "Error getting manager version", e) + Pair("Unknown", 0L) } } } \ No newline at end of file