Fix update check

This commit is contained in:
世界
2026-01-12 10:30:22 +08:00
parent 828be4aaf3
commit a2b3b846e0
7 changed files with 36 additions and 129 deletions

View File

@@ -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
}

View File

@@ -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)
}
}
}
}

View File

@@ -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 = {

View File

@@ -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<PackageInfo> {
@@ -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")

View File

@@ -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)

View File

@@ -259,7 +259,6 @@
<!-- Updates -->
<string name="check_update">Check Update</string>
<string name="force_download_install">Force Download and Install</string>
<string name="check_update_automatic">Automatic Update Check</string>
<string name="check_update_prompt_play">Would you like to enable automatic update checking from **Play Store**?</string>
<string name="check_update_prompt_github">Would you like to enable automatic update checking from **GitHub**?</string>

View File

@@ -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
}