From a2b3b846e09f192fa1f5ccd36051d6b99c6110cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 Jan 2026 10:30:22 +0800 Subject: [PATCH] Fix update check --- .../sfa/vendor/GitHubUpdateChecker.kt | 10 +-- .../nekohasekai/sfa/vendor/RootInstaller.kt | 8 +- .../screen/settings/AppSettingsScreen.kt | 81 ++----------------- .../sfa/vendor/PrivilegedServiceUtils.kt | 52 +++++------- .../nekohasekai/sfa/vendor/VendorInterface.kt | 6 -- app/src/main/res/values/strings.xml | 1 - .../java/io/nekohasekai/sfa/vendor/Vendor.kt | 7 -- 7 files changed, 36 insertions(+), 129 deletions(-) diff --git a/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt b/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt index ca2d16b..6a0c818 100644 --- a/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt +++ b/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt @@ -27,14 +27,6 @@ class GitHubUpdateChecker : Closeable { private val json = Json { ignoreUnknownKeys = true } fun checkUpdate(track: UpdateTrack): UpdateInfo? { - return getLatestUpdate(track, checkVersion = true) - } - - fun forceGetLatestUpdate(track: UpdateTrack): UpdateInfo? { - return getLatestUpdate(track, checkVersion = false) - } - - private fun getLatestUpdate(track: UpdateTrack, checkVersion: Boolean): UpdateInfo? { val includePrerelease = track == UpdateTrack.BETA val release = getLatestRelease(includePrerelease) ?: return null @@ -44,7 +36,7 @@ class GitHubUpdateChecker : Closeable { val metadata = downloadMetadata(release)!! - if (checkVersion && metadata.versionCode <= BuildConfig.VERSION_CODE) { + if (metadata.versionCode <= BuildConfig.VERSION_CODE) { return null } diff --git a/app/src/github/java/io/nekohasekai/sfa/vendor/RootInstaller.kt b/app/src/github/java/io/nekohasekai/sfa/vendor/RootInstaller.kt index 4b9c760..051a876 100644 --- a/app/src/github/java/io/nekohasekai/sfa/vendor/RootInstaller.kt +++ b/app/src/github/java/io/nekohasekai/sfa/vendor/RootInstaller.kt @@ -2,7 +2,9 @@ package io.nekohasekai.sfa.vendor import android.content.Intent import android.content.ServiceConnection +import android.os.Handler import android.os.IBinder +import android.os.Looper import android.os.ParcelFileDescriptor import com.topjohnwu.superuser.ipc.RootService import io.nekohasekai.sfa.Application @@ -77,12 +79,14 @@ object RootInstaller { } } - private data class RootServiceHandle( + private class RootServiceHandle( val connection: ServiceConnection, val service: IRootService ) : java.io.Closeable { override fun close() { - RootService.unbind(connection) + Handler(Looper.getMainLooper()).post { + RootService.unbind(connection) + } } } } diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt index 843f0a5..e2ae02b 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt @@ -3,8 +3,8 @@ package io.nekohasekai.sfa.compose.screen.settings import android.content.Intent import android.net.Uri import android.os.Build +import android.util.Log import android.provider.Settings as AndroidSettings -import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -249,6 +249,7 @@ fun AppSettingsScreen(navController: NavController) { } showDownloadDialog = false } catch (e: Exception) { + Log.e("AppSettingsScreen", "Error downloading update", e) downloadError = e.message } } @@ -497,7 +498,7 @@ fun AppSettingsScreen(navController: NavController) { ), ) - if (silentInstallEnabled && !xposedActivated) { + if (silentInstallEnabled) { ListItem( headlineContent = { Text( @@ -507,7 +508,9 @@ fun AppSettingsScreen(navController: NavController) { }, supportingContent = { Text( - when (silentInstallMethod) { + if (xposedActivated) { + stringResource(R.string.install_method_root) + } else when (silentInstallMethod) { "PACKAGE_INSTALLER" -> stringResource(R.string.install_method_package_installer) "SHIZUKU" -> stringResource(R.string.install_method_shizuku) "ROOT" -> stringResource(R.string.install_method_root) @@ -525,7 +528,7 @@ fun AppSettingsScreen(navController: NavController) { }, modifier = updateItemModifier() - .clickable { showInstallMethodMenu = true }, + .let { if (!xposedActivated) it.clickable { showInstallMethodMenu = true } else it }, colors = ListItemDefaults.colors( containerColor = Color.Transparent, @@ -731,76 +734,6 @@ fun AppSettingsScreen(navController: NavController) { ), ) - if (BuildConfig.DEBUG && Vendor.supportsTrackSelection()) { - var isForceDownloading by remember { mutableStateOf(false) } - ListItem( - headlineContent = { - Text( - stringResource(R.string.force_download_install), - style = MaterialTheme.typography.bodyLarge, - ) - }, - leadingContent = { - Icon( - imageVector = Icons.Outlined.SystemUpdateAlt, - contentDescription = null, - tint = MaterialTheme.colorScheme.error, - ) - }, - trailingContent = { - if (isForceDownloading) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - strokeWidth = 2.dp, - ) - } - }, - modifier = - Modifier - .clip( - if (hasUpdate) { - RoundedCornerShape(0.dp) - } else { - RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp) - }, - ) - .clickable(enabled = !isForceDownloading) { - isForceDownloading = true - scope.launch { - try { - val latestUpdate = withContext(Dispatchers.IO) { - Vendor.forceGetLatestUpdate() - } - if (latestUpdate != null) { - showDownloadDialog = true - downloadError = null - downloadJob = scope.launch { - try { - withContext(Dispatchers.IO) { - Vendor.downloadAndInstall(context, latestUpdate.downloadUrl) - } - showDownloadDialog = false - } catch (e: Exception) { - downloadError = e.message - } - } - } else { - showErrorDialog = R.string.no_updates_available - } - } catch (_: UpdateCheckException.TrackNotSupported) { - showErrorDialog = R.string.update_track_not_supported - } catch (_: Exception) { - } - isForceDownloading = false - } - }, - colors = - ListItemDefaults.colors( - containerColor = Color.Transparent, - ), - ) - } - if (hasUpdate && updateInfo != null) { ListItem( headlineContent = { diff --git a/app/src/main/java/io/nekohasekai/sfa/vendor/PrivilegedServiceUtils.kt b/app/src/main/java/io/nekohasekai/sfa/vendor/PrivilegedServiceUtils.kt index 78ab528..85e3fe8 100644 --- a/app/src/main/java/io/nekohasekai/sfa/vendor/PrivilegedServiceUtils.kt +++ b/app/src/main/java/io/nekohasekai/sfa/vendor/PrivilegedServiceUtils.kt @@ -19,15 +19,11 @@ import java.io.IOException object PrivilegedServiceUtils { - const val SYSTEM_SERVICE_NAME = "sfa_privileged" - private fun getPackageManager(): Any { - val binder = SystemServiceHelperCompat.getSystemService("package") - ?: throw IllegalStateException("package service not available") + val binder = SystemServiceHelperCompat.getSystemService("package") ?: throw IllegalStateException("package service not available") val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub") val asInterface = stubClass.getMethod("asInterface", IBinder::class.java) - return asInterface.invoke(null, binder) - ?: throw IllegalStateException("IPackageManager is null") + return asInterface.invoke(null, binder) ?: throw IllegalStateException("IPackageManager is null") } fun getInstalledPackages(flags: Int, userId: Int): List { @@ -37,14 +33,14 @@ object PrivilegedServiceUtils { val method = iPackageManagerClass.getMethod( "getInstalledPackages", Long::class.javaPrimitiveType, - Int::class.javaPrimitiveType + Int::class.javaPrimitiveType, ) method.invoke(iPackageManager, flags.toLong(), userId) } else { val method = iPackageManagerClass.getMethod( "getInstalledPackages", Int::class.javaPrimitiveType, - Int::class.javaPrimitiveType + Int::class.javaPrimitiveType, ) method.invoke(iPackageManager, flags, userId) } @@ -61,14 +57,19 @@ object PrivilegedServiceUtils { iPackageInstaller, installerPackageName, null, - targetUserId + targetUserId, ) val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + params.setAppPackageName(BuildConfig.APPLICATION_ID) + // Set INSTALL_REPLACE_EXISTING flag (value = 2) + val installFlagsField = PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags") + installFlagsField.isAccessible = true + installFlagsField.setInt(params, installFlagsField.getInt(params) or 2) val sessionId = packageInstaller.createSession(params) val iSession = IPackageInstallerSession.Stub.asInterface( - iPackageInstaller.openSession(sessionId).asBinder() + iPackageInstaller.openSession(sessionId).asBinder(), ) val session = createSession(iSession) @@ -91,8 +92,7 @@ object PrivilegedServiceUtils { session.commit(intentSender) latch.await(60, TimeUnit.SECONDS) - val intent = resultIntent[0] - ?: throw IOException("Installation timed out") + val intent = resultIntent[0] ?: throw IOException("Installation timed out") val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) if (status != PackageInstaller.STATUS_SUCCESS) { @@ -116,32 +116,26 @@ object PrivilegedServiceUtils { installer: IPackageInstaller, installerPackageName: String, installerAttributionTag: String?, - userId: Int + userId: Int, ): PackageInstaller { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PackageInstaller::class.java - .getConstructor( + PackageInstaller::class.java.getConstructor( IPackageInstaller::class.java, String::class.java, String::class.java, - Int::class.javaPrimitiveType - ) - .newInstance(installer, installerPackageName, installerAttributionTag, userId) + Int::class.javaPrimitiveType, + ).newInstance(installer, installerPackageName, installerAttributionTag, userId) } else { - PackageInstaller::class.java - .getConstructor( + PackageInstaller::class.java.getConstructor( IPackageInstaller::class.java, String::class.java, - Int::class.javaPrimitiveType - ) - .newInstance(installer, installerPackageName, userId) + Int::class.javaPrimitiveType, + ).newInstance(installer, installerPackageName, userId) } } private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session { - return PackageInstaller.Session::class.java - .getConstructor(IPackageInstallerSession::class.java) - .newInstance(session) + return PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java).newInstance(session) } private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender { @@ -153,14 +147,12 @@ object PrivilegedServiceUtils { whitelistToken: android.os.IBinder?, finishedReceiver: android.content.IIntentReceiver?, requiredPermission: String?, - options: Bundle? + options: Bundle?, ) { onResult(intent) } } - return IntentSender::class.java - .getConstructor(IIntentSender::class.java) - .newInstance(sender) + return IntentSender::class.java.getConstructor(IIntentSender::class.java).newInstance(sender) } @Suppress("UNCHECKED_CAST") diff --git a/app/src/main/java/io/nekohasekai/sfa/vendor/VendorInterface.kt b/app/src/main/java/io/nekohasekai/sfa/vendor/VendorInterface.kt index 2c1b2d9..402f79f 100644 --- a/app/src/main/java/io/nekohasekai/sfa/vendor/VendorInterface.kt +++ b/app/src/main/java/io/nekohasekai/sfa/vendor/VendorInterface.kt @@ -35,12 +35,6 @@ interface VendorInterface { */ fun checkUpdateAsync(): UpdateInfo? = null - /** - * Force get latest update (ignores version check) - * @return UpdateInfo of the latest release, null if unavailable - */ - fun forceGetLatestUpdate(): UpdateInfo? = null - /** * Check if silent install feature is available * @return true if silent install is supported (Other flavor only) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b6266a..cd0d77b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -259,7 +259,6 @@ Check Update - Force Download and Install Automatic Update Check Would you like to enable automatic update checking from **Play Store**? Would you like to enable automatic update checking from **GitHub**? diff --git a/app/src/other/java/io/nekohasekai/sfa/vendor/Vendor.kt b/app/src/other/java/io/nekohasekai/sfa/vendor/Vendor.kt index e422d6b..3408751 100644 --- a/app/src/other/java/io/nekohasekai/sfa/vendor/Vendor.kt +++ b/app/src/other/java/io/nekohasekai/sfa/vendor/Vendor.kt @@ -108,13 +108,6 @@ object Vendor : VendorInterface { } } - override fun forceGetLatestUpdate(): UpdateInfo? { - val track = UpdateTrack.fromString(Settings.updateTrack) - return GitHubUpdateChecker().use { checker -> - checker.forceGetLatestUpdate(track) - } - } - override fun supportsSilentInstall(): Boolean { return true }