Clean up stale self-update state on launch
The install event isn't delivered when the app replaces its own APK, so a self-update could linger in the Updates list as "installing forever" after the update completed and the app restarted. - UpdateHelper: clear a stale self-update row on launch, flavor-aware — nightly by its commit-tagged version name (the version code is static), release/preload by the version code. - DownloadHelper: finalize a self-update download stuck in INSTALLING (the commit killed the process before it could advance) to INSTALLED on launch.
This commit is contained in:
@@ -73,13 +73,31 @@ class DownloadHelper @Inject constructor(
|
||||
*/
|
||||
fun init() {
|
||||
AuroraApp.scope.launch {
|
||||
cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList())
|
||||
val downloads = downloadDao.downloads().firstOrNull() ?: emptyList()
|
||||
cancelFailedDownloads(downloads)
|
||||
finalizeStaleSelfUpdate(downloads)
|
||||
}.invokeOnCompletion {
|
||||
observeDownloads()
|
||||
observeInstalls()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a self-update download left dangling in [DownloadStatus.INSTALLING]. Replacing
|
||||
* the app's own APK kills the process before the installer's "installed" event can advance
|
||||
* the row, so on the next launch it would otherwise show as installing forever. Reaching
|
||||
* INSTALLING means the install was already committed, so mark it installed; if it actually
|
||||
* failed, the periodic update check re-offers and re-enqueues it.
|
||||
*/
|
||||
private suspend fun finalizeStaleSelfUpdate(downloads: List<Download>) {
|
||||
downloads.firstOrNull {
|
||||
it.packageName == context.packageName && it.status == DownloadStatus.INSTALLING
|
||||
}?.let {
|
||||
Log.i(TAG, "Finalizing stale self-update install for ${it.packageName}")
|
||||
downloadDao.updateStatus(it.packageName, DownloadStatus.INSTALLED)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances a download row through the installer phase so its history reflects whether the
|
||||
* app actually installed, not just that the bytes finished downloading:
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.aurora.store.data.room.update.IgnoredUpdateDao
|
||||
import com.aurora.store.data.room.update.Update
|
||||
import com.aurora.store.data.room.update.UpdateDao
|
||||
import com.aurora.store.data.work.UpdateWorker
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
|
||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
|
||||
@@ -253,13 +254,23 @@ class UpdateHelper @Inject constructor(
|
||||
|
||||
private suspend fun deleteInvalidUpdates() {
|
||||
updateDao.updates().firstOrNull()?.forEach { update ->
|
||||
// Nightly builds share a static version code, so the generic "up to date"
|
||||
// check would wrongly drop a freshly found self-update. The worker owns the
|
||||
// self-update lifecycle (re-checked by build timestamp); cleanup happens via
|
||||
// the install event instead.
|
||||
if (update.isSelfUpdate(context) && BuildType.CURRENT == BuildType.NIGHTLY) {
|
||||
if (update.isSelfUpdate(context)) {
|
||||
// Self-updates need a flavor-aware staleness check. Release/preload bump the
|
||||
// version code, so the normal up-to-date check works. Nightly reuses a static
|
||||
// version code (isUpToDate is always true there), so fall back to the
|
||||
// commit-tagged version name. Without this the row lingers until the next
|
||||
// update check, since the install event isn't delivered when the app replaces
|
||||
// itself.
|
||||
val alreadyInstalled = if (BuildType.CURRENT == BuildType.NIGHTLY) {
|
||||
PackageUtil.getInstalledVersionName(context, update.packageName) ==
|
||||
update.versionName
|
||||
} else {
|
||||
update.isUpToDate(context)
|
||||
}
|
||||
if (alreadyInstalled) deleteUpdate(update.packageName)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (!update.isInstalled(context) || update.isUpToDate(context)) {
|
||||
deleteUpdate(update.packageName)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ data class SelfUpdate(
|
||||
val versionCodeRaw: String = "0",
|
||||
@SerialName("download_url")
|
||||
val downloadUrl: String = "",
|
||||
@SerialName("icon_url")
|
||||
val iconUrl: String = "",
|
||||
@SerialName("sha1")
|
||||
val sha1: String = "",
|
||||
@SerialName("sha256")
|
||||
val sha256: String = "",
|
||||
val changelog: String = "",
|
||||
@SerialName("size")
|
||||
val sizeRaw: String = "0",
|
||||
@@ -61,12 +67,14 @@ data class SelfUpdate(
|
||||
updatedOn = updatedOn,
|
||||
displayName = context.getString(R.string.app_name),
|
||||
developerName = "Rahul Kumar Patel",
|
||||
iconArtwork = Artwork(url = ICON_URL),
|
||||
iconArtwork = Artwork(url = iconUrl),
|
||||
fileList = mutableListOf(
|
||||
PlayFile(
|
||||
name = "${context.packageName}.apk",
|
||||
url = downloadUrl,
|
||||
size = size
|
||||
size = size,
|
||||
sha1 = sha1,
|
||||
sha256 = sha256
|
||||
)
|
||||
),
|
||||
isFree = true,
|
||||
@@ -78,11 +86,4 @@ data class SelfUpdate(
|
||||
EncodedCertificateSet(certificateSet = it, sha256 = String())
|
||||
}.toMutableList()
|
||||
)
|
||||
|
||||
companion object {
|
||||
// Kept in sync with the fastlane metadata on the project.
|
||||
private const val ICON_URL =
|
||||
"https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/" +
|
||||
"fastlane/metadata/android/en-US/images/icon.png"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,14 +130,21 @@ class DownloadWorker @AssistedInject constructor(
|
||||
// Fetch required data for download
|
||||
try {
|
||||
download = downloadDao.getDownload(inputData.getString(DownloadHelper.PACKAGE_NAME)!!)
|
||||
} catch (exception: Exception) {
|
||||
return onFailure(exception)
|
||||
}
|
||||
|
||||
// The icon only decorates the progress notification. Fetching it is best-effort: a
|
||||
// failure (unreachable/pinned host, non-HTTP url, undecodable image) must never abort
|
||||
// the download itself.
|
||||
try {
|
||||
val response = (httpClient as HttpClient).call(download.iconURL).body
|
||||
val bitmap = BitmapFactory.decodeStream(
|
||||
withContext(Dispatchers.IO) { response.byteStream() }
|
||||
)
|
||||
icon = bitmap.scale(96, 96)
|
||||
icon = bitmap?.scale(96, 96)
|
||||
} catch (exception: Exception) {
|
||||
return onFailure(exception)
|
||||
Log.w(TAG, "Failed to fetch icon for ${download.packageName}", exception)
|
||||
}
|
||||
|
||||
// Set work/service to foreground on < Android 12.0
|
||||
|
||||
Reference in New Issue
Block a user