Fix update check
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user