Track the install phase with INSTALLING/INSTALLED download states

A download row previously stopped at COMPLETED and never reflected whether the
app actually installed. Add INSTALLING/INSTALLED states driven by a central
installer-event observer in DownloadHelper:
- COMPLETED -> INSTALLING on the installer's first progress event
- -> INSTALLED on success (row kept so the APK can still be exported)
- a failed install reverts INSTALLING -> COMPLETED so it can be re-installed
  without re-downloading

Consumers that branched on COMPLETED are updated (App Details state, MicroG
status mapping, Downloads list icon). downloadStatus is stored as TEXT so no
schema migration is needed.
This commit is contained in:
Rahul Patel
2026-05-30 00:37:05 +05:30
parent 7e8746dbcb
commit 03638ca84c
6 changed files with 46 additions and 5 deletions

View File

@@ -61,7 +61,7 @@ fun DownloadListItem(modifier: Modifier = Modifier, download: Download, onClick:
) )
}, },
trailing = when (download.status) { trailing = when (download.status) {
DownloadStatus.COMPLETED -> { DownloadStatus.COMPLETED, DownloadStatus.INSTALLED -> {
{ {
Icon( Icon(
painter = painterResource(R.drawable.ic_check), painter = painterResource(R.drawable.ic_check),

View File

@@ -14,6 +14,7 @@ import androidx.work.WorkRequest
import com.aurora.extensions.TAG import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp import com.aurora.store.AuroraApp
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download import com.aurora.store.data.room.download.Download
@@ -75,9 +76,42 @@ class DownloadHelper @Inject constructor(
cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList()) cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList())
}.invokeOnCompletion { }.invokeOnCompletion {
observeDownloads() observeDownloads()
observeInstalls()
} }
} }
/**
* Advances a download row through the installer phase so its history reflects whether the
* app actually installed, not just that the bytes finished downloading:
* - [InstallerEvent.Installing] moves a [DownloadStatus.COMPLETED] row to
* [DownloadStatus.INSTALLING];
* - [InstallerEvent.Installed] marks it [DownloadStatus.INSTALLED] (kept so the user can
* still export the APK);
* - [InstallerEvent.Failed] reverts an in-progress install back to
* [DownloadStatus.COMPLETED] so the downloaded files can be re-installed without
* re-downloading.
*/
private fun observeInstalls() {
AuroraApp.events.installerEvent.onEach { event ->
val existing = getDownload(event.packageName) ?: return@onEach
when (event) {
is InstallerEvent.Installing -> if (existing.status == DownloadStatus.COMPLETED) {
downloadDao.updateStatus(event.packageName, DownloadStatus.INSTALLING)
}
is InstallerEvent.Installed -> if (existing.status != DownloadStatus.INSTALLED) {
downloadDao.updateStatus(event.packageName, DownloadStatus.INSTALLED)
}
is InstallerEvent.Failed -> if (existing.status == DownloadStatus.INSTALLING) {
downloadDao.updateStatus(event.packageName, DownloadStatus.COMPLETED)
}
else -> {}
}
}.launchIn(AuroraApp.scope)
}
private fun observeDownloads() { private fun observeDownloads() {
downloadDao.downloads().onEach { list -> downloadDao.downloads().onEach { list ->
try { try {

View File

@@ -11,10 +11,12 @@ enum class DownloadStatus(@StringRes val localized: Int) {
QUEUED(R.string.status_queued), QUEUED(R.string.status_queued),
UNAVAILABLE(R.string.status_unavailable), UNAVAILABLE(R.string.status_unavailable),
VERIFYING(R.string.status_verifying), VERIFYING(R.string.status_verifying),
PURCHASING(R.string.preparing_to_install); PURCHASING(R.string.preparing_to_install),
INSTALLING(R.string.status_installing),
INSTALLED(R.string.status_installed);
companion object { companion object {
val finished = listOf(FAILED, CANCELLED, COMPLETED) val finished = listOf(FAILED, CANCELLED, COMPLETED, INSTALLED)
val running = listOf(QUEUED, PURCHASING, DOWNLOADING) val running = listOf(QUEUED, PURCHASING, DOWNLOADING)
/** /**

View File

@@ -314,7 +314,8 @@ class AppDetailsViewModel @Inject constructor(
DownloadStatus.VERIFYING -> AppState.Verifying DownloadStatus.VERIFYING -> AppState.Verifying
DownloadStatus.COMPLETED -> if (isInstalled) defaultAppState else AppState.Installing(0F) DownloadStatus.COMPLETED,
DownloadStatus.INSTALLING -> if (isInstalled) defaultAppState else AppState.Installing(0F)
else -> defaultAppState else -> defaultAppState
} }

View File

@@ -213,7 +213,9 @@ class MicroGViewModel @Inject constructor(
download == null -> InstallStatus.PENDING download == null -> InstallStatus.PENDING
download.status == DownloadStatus.FAILED -> InstallStatus.FAILED download.status == DownloadStatus.FAILED -> InstallStatus.FAILED
download.status == DownloadStatus.CANCELLED -> InstallStatus.PENDING download.status == DownloadStatus.CANCELLED -> InstallStatus.PENDING
download.status == DownloadStatus.COMPLETED -> InstallStatus.INSTALLING download.status == DownloadStatus.INSTALLED -> InstallStatus.INSTALLED
download.status == DownloadStatus.COMPLETED ||
download.status == DownloadStatus.INSTALLING -> InstallStatus.INSTALLING
else -> InstallStatus.DOWNLOADING else -> InstallStatus.DOWNLOADING
} }

View File

@@ -520,6 +520,8 @@
<string name="status_queued">Queued</string> <string name="status_queued">Queued</string>
<string name="status_unavailable">Unavailable</string> <string name="status_unavailable">Unavailable</string>
<string name="status_verifying">Verifying</string> <string name="status_verifying">Verifying</string>
<string name="status_installing">Installing</string>
<string name="status_installed">Installed</string>
<!-- UnarchivePackageReceiver --> <!-- UnarchivePackageReceiver -->
<string name="authentication_required_title">Authentication required</string> <string name="authentication_required_title">Authentication required</string>