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() {
|
fun init() {
|
||||||
AuroraApp.scope.launch {
|
AuroraApp.scope.launch {
|
||||||
cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList())
|
val downloads = downloadDao.downloads().firstOrNull() ?: emptyList()
|
||||||
|
cancelFailedDownloads(downloads)
|
||||||
|
finalizeStaleSelfUpdate(downloads)
|
||||||
}.invokeOnCompletion {
|
}.invokeOnCompletion {
|
||||||
observeDownloads()
|
observeDownloads()
|
||||||
observeInstalls()
|
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
|
* Advances a download row through the installer phase so its history reflects whether the
|
||||||
* app actually installed, not just that the bytes finished downloading:
|
* 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.Update
|
||||||
import com.aurora.store.data.room.update.UpdateDao
|
import com.aurora.store.data.room.update.UpdateDao
|
||||||
import com.aurora.store.data.work.UpdateWorker
|
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
|
||||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
|
||||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
|
||||||
@@ -253,13 +254,23 @@ class UpdateHelper @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun deleteInvalidUpdates() {
|
private suspend fun deleteInvalidUpdates() {
|
||||||
updateDao.updates().firstOrNull()?.forEach { update ->
|
updateDao.updates().firstOrNull()?.forEach { update ->
|
||||||
// Nightly builds share a static version code, so the generic "up to date"
|
if (update.isSelfUpdate(context)) {
|
||||||
// check would wrongly drop a freshly found self-update. The worker owns the
|
// Self-updates need a flavor-aware staleness check. Release/preload bump the
|
||||||
// self-update lifecycle (re-checked by build timestamp); cleanup happens via
|
// version code, so the normal up-to-date check works. Nightly reuses a static
|
||||||
// the install event instead.
|
// version code (isUpToDate is always true there), so fall back to the
|
||||||
if (update.isSelfUpdate(context) && BuildType.CURRENT == BuildType.NIGHTLY) {
|
// 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
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!update.isInstalled(context) || update.isUpToDate(context)) {
|
if (!update.isInstalled(context) || update.isUpToDate(context)) {
|
||||||
deleteUpdate(update.packageName)
|
deleteUpdate(update.packageName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ data class SelfUpdate(
|
|||||||
val versionCodeRaw: String = "0",
|
val versionCodeRaw: String = "0",
|
||||||
@SerialName("download_url")
|
@SerialName("download_url")
|
||||||
val downloadUrl: String = "",
|
val downloadUrl: String = "",
|
||||||
|
@SerialName("icon_url")
|
||||||
|
val iconUrl: String = "",
|
||||||
|
@SerialName("sha1")
|
||||||
|
val sha1: String = "",
|
||||||
|
@SerialName("sha256")
|
||||||
|
val sha256: String = "",
|
||||||
val changelog: String = "",
|
val changelog: String = "",
|
||||||
@SerialName("size")
|
@SerialName("size")
|
||||||
val sizeRaw: String = "0",
|
val sizeRaw: String = "0",
|
||||||
@@ -61,12 +67,14 @@ data class SelfUpdate(
|
|||||||
updatedOn = updatedOn,
|
updatedOn = updatedOn,
|
||||||
displayName = context.getString(R.string.app_name),
|
displayName = context.getString(R.string.app_name),
|
||||||
developerName = "Rahul Kumar Patel",
|
developerName = "Rahul Kumar Patel",
|
||||||
iconArtwork = Artwork(url = ICON_URL),
|
iconArtwork = Artwork(url = iconUrl),
|
||||||
fileList = mutableListOf(
|
fileList = mutableListOf(
|
||||||
PlayFile(
|
PlayFile(
|
||||||
name = "${context.packageName}.apk",
|
name = "${context.packageName}.apk",
|
||||||
url = downloadUrl,
|
url = downloadUrl,
|
||||||
size = size
|
size = size,
|
||||||
|
sha1 = sha1,
|
||||||
|
sha256 = sha256
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
isFree = true,
|
isFree = true,
|
||||||
@@ -78,11 +86,4 @@ data class SelfUpdate(
|
|||||||
EncodedCertificateSet(certificateSet = it, sha256 = String())
|
EncodedCertificateSet(certificateSet = it, sha256 = String())
|
||||||
}.toMutableList()
|
}.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
|
// Fetch required data for download
|
||||||
try {
|
try {
|
||||||
download = downloadDao.getDownload(inputData.getString(DownloadHelper.PACKAGE_NAME)!!)
|
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 response = (httpClient as HttpClient).call(download.iconURL).body
|
||||||
val bitmap = BitmapFactory.decodeStream(
|
val bitmap = BitmapFactory.decodeStream(
|
||||||
withContext(Dispatchers.IO) { response.byteStream() }
|
withContext(Dispatchers.IO) { response.byteStream() }
|
||||||
)
|
)
|
||||||
icon = bitmap.scale(96, 96)
|
icon = bitmap?.scale(96, 96)
|
||||||
} catch (exception: Exception) {
|
} 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
|
// Set work/service to foreground on < Android 12.0
|
||||||
|
|||||||
Reference in New Issue
Block a user