Fix update check
This commit is contained in:
@@ -27,14 +27,6 @@ class GitHubUpdateChecker : Closeable {
|
|||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
fun checkUpdate(track: UpdateTrack): UpdateInfo? {
|
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 includePrerelease = track == UpdateTrack.BETA
|
||||||
val release = getLatestRelease(includePrerelease) ?: return null
|
val release = getLatestRelease(includePrerelease) ?: return null
|
||||||
|
|
||||||
@@ -44,7 +36,7 @@ class GitHubUpdateChecker : Closeable {
|
|||||||
|
|
||||||
val metadata = downloadMetadata(release)!!
|
val metadata = downloadMetadata(release)!!
|
||||||
|
|
||||||
if (checkVersion && metadata.versionCode <= BuildConfig.VERSION_CODE) {
|
if (metadata.versionCode <= BuildConfig.VERSION_CODE) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package io.nekohasekai.sfa.vendor
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.Looper
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
import io.nekohasekai.sfa.Application
|
import io.nekohasekai.sfa.Application
|
||||||
@@ -77,12 +79,14 @@ object RootInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class RootServiceHandle(
|
private class RootServiceHandle(
|
||||||
val connection: ServiceConnection,
|
val connection: ServiceConnection,
|
||||||
val service: IRootService
|
val service: IRootService
|
||||||
) : java.io.Closeable {
|
) : java.io.Closeable {
|
||||||
override fun close() {
|
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.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import android.provider.Settings as AndroidSettings
|
import android.provider.Settings as AndroidSettings
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -249,6 +249,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
}
|
}
|
||||||
showDownloadDialog = false
|
showDownloadDialog = false
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("AppSettingsScreen", "Error downloading update", e)
|
||||||
downloadError = e.message
|
downloadError = e.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,7 +498,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (silentInstallEnabled && !xposedActivated) {
|
if (silentInstallEnabled) {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
@@ -507,7 +508,9 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Text(
|
Text(
|
||||||
when (silentInstallMethod) {
|
if (xposedActivated) {
|
||||||
|
stringResource(R.string.install_method_root)
|
||||||
|
} else when (silentInstallMethod) {
|
||||||
"PACKAGE_INSTALLER" -> stringResource(R.string.install_method_package_installer)
|
"PACKAGE_INSTALLER" -> stringResource(R.string.install_method_package_installer)
|
||||||
"SHIZUKU" -> stringResource(R.string.install_method_shizuku)
|
"SHIZUKU" -> stringResource(R.string.install_method_shizuku)
|
||||||
"ROOT" -> stringResource(R.string.install_method_root)
|
"ROOT" -> stringResource(R.string.install_method_root)
|
||||||
@@ -525,7 +528,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
updateItemModifier()
|
updateItemModifier()
|
||||||
.clickable { showInstallMethodMenu = true },
|
.let { if (!xposedActivated) it.clickable { showInstallMethodMenu = true } else it },
|
||||||
colors =
|
colors =
|
||||||
ListItemDefaults.colors(
|
ListItemDefaults.colors(
|
||||||
containerColor = Color.Transparent,
|
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) {
|
if (hasUpdate && updateInfo != null) {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|||||||
@@ -19,15 +19,11 @@ import java.io.IOException
|
|||||||
|
|
||||||
object PrivilegedServiceUtils {
|
object PrivilegedServiceUtils {
|
||||||
|
|
||||||
const val SYSTEM_SERVICE_NAME = "sfa_privileged"
|
|
||||||
|
|
||||||
private fun getPackageManager(): Any {
|
private fun getPackageManager(): Any {
|
||||||
val binder = SystemServiceHelperCompat.getSystemService("package")
|
val binder = SystemServiceHelperCompat.getSystemService("package") ?: throw IllegalStateException("package service not available")
|
||||||
?: throw IllegalStateException("package service not available")
|
|
||||||
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
|
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
|
||||||
val asInterface = stubClass.getMethod("asInterface", IBinder::class.java)
|
val asInterface = stubClass.getMethod("asInterface", IBinder::class.java)
|
||||||
return asInterface.invoke(null, binder)
|
return asInterface.invoke(null, binder) ?: throw IllegalStateException("IPackageManager is null")
|
||||||
?: throw IllegalStateException("IPackageManager is null")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
|
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
|
||||||
@@ -37,14 +33,14 @@ object PrivilegedServiceUtils {
|
|||||||
val method = iPackageManagerClass.getMethod(
|
val method = iPackageManagerClass.getMethod(
|
||||||
"getInstalledPackages",
|
"getInstalledPackages",
|
||||||
Long::class.javaPrimitiveType,
|
Long::class.javaPrimitiveType,
|
||||||
Int::class.javaPrimitiveType
|
Int::class.javaPrimitiveType,
|
||||||
)
|
)
|
||||||
method.invoke(iPackageManager, flags.toLong(), userId)
|
method.invoke(iPackageManager, flags.toLong(), userId)
|
||||||
} else {
|
} else {
|
||||||
val method = iPackageManagerClass.getMethod(
|
val method = iPackageManagerClass.getMethod(
|
||||||
"getInstalledPackages",
|
"getInstalledPackages",
|
||||||
Int::class.javaPrimitiveType,
|
Int::class.javaPrimitiveType,
|
||||||
Int::class.javaPrimitiveType
|
Int::class.javaPrimitiveType,
|
||||||
)
|
)
|
||||||
method.invoke(iPackageManager, flags, userId)
|
method.invoke(iPackageManager, flags, userId)
|
||||||
}
|
}
|
||||||
@@ -61,14 +57,19 @@ object PrivilegedServiceUtils {
|
|||||||
iPackageInstaller,
|
iPackageInstaller,
|
||||||
installerPackageName,
|
installerPackageName,
|
||||||
null,
|
null,
|
||||||
targetUserId
|
targetUserId,
|
||||||
)
|
)
|
||||||
|
|
||||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
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 sessionId = packageInstaller.createSession(params)
|
||||||
|
|
||||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||||
iPackageInstaller.openSession(sessionId).asBinder()
|
iPackageInstaller.openSession(sessionId).asBinder(),
|
||||||
)
|
)
|
||||||
val session = createSession(iSession)
|
val session = createSession(iSession)
|
||||||
|
|
||||||
@@ -91,8 +92,7 @@ object PrivilegedServiceUtils {
|
|||||||
session.commit(intentSender)
|
session.commit(intentSender)
|
||||||
latch.await(60, TimeUnit.SECONDS)
|
latch.await(60, TimeUnit.SECONDS)
|
||||||
|
|
||||||
val intent = resultIntent[0]
|
val intent = resultIntent[0] ?: throw IOException("Installation timed out")
|
||||||
?: throw IOException("Installation timed out")
|
|
||||||
|
|
||||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
|
||||||
if (status != PackageInstaller.STATUS_SUCCESS) {
|
if (status != PackageInstaller.STATUS_SUCCESS) {
|
||||||
@@ -116,32 +116,26 @@ object PrivilegedServiceUtils {
|
|||||||
installer: IPackageInstaller,
|
installer: IPackageInstaller,
|
||||||
installerPackageName: String,
|
installerPackageName: String,
|
||||||
installerAttributionTag: String?,
|
installerAttributionTag: String?,
|
||||||
userId: Int
|
userId: Int,
|
||||||
): PackageInstaller {
|
): PackageInstaller {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
PackageInstaller::class.java
|
PackageInstaller::class.java.getConstructor(
|
||||||
.getConstructor(
|
|
||||||
IPackageInstaller::class.java,
|
IPackageInstaller::class.java,
|
||||||
String::class.java,
|
String::class.java,
|
||||||
String::class.java,
|
String::class.java,
|
||||||
Int::class.javaPrimitiveType
|
Int::class.javaPrimitiveType,
|
||||||
)
|
).newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
||||||
.newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
|
||||||
} else {
|
} else {
|
||||||
PackageInstaller::class.java
|
PackageInstaller::class.java.getConstructor(
|
||||||
.getConstructor(
|
|
||||||
IPackageInstaller::class.java,
|
IPackageInstaller::class.java,
|
||||||
String::class.java,
|
String::class.java,
|
||||||
Int::class.javaPrimitiveType
|
Int::class.javaPrimitiveType,
|
||||||
)
|
).newInstance(installer, installerPackageName, userId)
|
||||||
.newInstance(installer, installerPackageName, userId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
||||||
return PackageInstaller.Session::class.java
|
return PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java).newInstance(session)
|
||||||
.getConstructor(IPackageInstallerSession::class.java)
|
|
||||||
.newInstance(session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
||||||
@@ -153,14 +147,12 @@ object PrivilegedServiceUtils {
|
|||||||
whitelistToken: android.os.IBinder?,
|
whitelistToken: android.os.IBinder?,
|
||||||
finishedReceiver: android.content.IIntentReceiver?,
|
finishedReceiver: android.content.IIntentReceiver?,
|
||||||
requiredPermission: String?,
|
requiredPermission: String?,
|
||||||
options: Bundle?
|
options: Bundle?,
|
||||||
) {
|
) {
|
||||||
onResult(intent)
|
onResult(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return IntentSender::class.java
|
return IntentSender::class.java.getConstructor(IIntentSender::class.java).newInstance(sender)
|
||||||
.getConstructor(IIntentSender::class.java)
|
|
||||||
.newInstance(sender)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|||||||
@@ -35,12 +35,6 @@ interface VendorInterface {
|
|||||||
*/
|
*/
|
||||||
fun checkUpdateAsync(): UpdateInfo? = null
|
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
|
* Check if silent install feature is available
|
||||||
* @return true if silent install is supported (Other flavor only)
|
* @return true if silent install is supported (Other flavor only)
|
||||||
|
|||||||
@@ -259,7 +259,6 @@
|
|||||||
|
|
||||||
<!-- Updates -->
|
<!-- Updates -->
|
||||||
<string name="check_update">Check Update</string>
|
<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_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_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>
|
<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 {
|
override fun supportsSilentInstall(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user