diff --git a/app/src/main/java/com/bitchat/android/util/ApkDownloader.kt b/app/src/main/java/com/bitchat/android/util/ApkDownloader.kt index b3f342c1..8e16620f 100644 --- a/app/src/main/java/com/bitchat/android/util/ApkDownloader.kt +++ b/app/src/main/java/com/bitchat/android/util/ApkDownloader.kt @@ -54,6 +54,13 @@ interface ApkDownloader { */ enum class DownloadPhase { AwaitingConnectivity, + /** + * Waiting out the backoff before another attempt. Distinct from + * [AwaitingConnectivity] because WorkManager parks a retry in ENQUEUED + * whether or not the device is online, and claiming a network wait there + * would be false on a connected device. + */ + Retrying, SelectingSource, AwaitingNetworkRoute, Transferring, @@ -74,10 +81,26 @@ interface ApkDownloader { } } +/** + * What a queued work record is actually waiting for. + * + * WorkManager parks both cases in ENQUEUED, so the state alone cannot tell them apart. A non-zero + * [runAttemptCount] means the work already ran and failed, which makes this the retry backoff + * rather than an unmet network constraint. + */ +internal fun queuedPhase(runAttemptCount: Int): ApkDownloader.DownloadPhase = + if (runAttemptCount > 0) { + ApkDownloader.DownloadPhase.Retrying + } else { + ApkDownloader.DownloadPhase.AwaitingConnectivity + } + /** Shared by the notification and the About sheet so both name a phase identically. */ internal fun downloadPhaseLabel(phase: ApkDownloader.DownloadPhase): Int = when (phase) { ApkDownloader.DownloadPhase.AwaitingConnectivity -> com.bitchat.android.R.string.prepare_apk_phase_awaiting_connectivity + ApkDownloader.DownloadPhase.Retrying -> + com.bitchat.android.R.string.prepare_apk_phase_retrying ApkDownloader.DownloadPhase.SelectingSource -> com.bitchat.android.R.string.prepare_apk_phase_selecting_source ApkDownloader.DownloadPhase.AwaitingNetworkRoute -> diff --git a/app/src/main/java/com/bitchat/android/util/WorkManagerApkDownloader.kt b/app/src/main/java/com/bitchat/android/util/WorkManagerApkDownloader.kt index 214b8118..6d482c34 100644 --- a/app/src/main/java/com/bitchat/android/util/WorkManagerApkDownloader.kt +++ b/app/src/main/java/com/bitchat/android/util/WorkManagerApkDownloader.kt @@ -58,11 +58,13 @@ class WorkManagerApkDownloader(context: Context) : ApkDownloader { return when (workInfo.state) { WorkInfo.State.ENQUEUED, WorkInfo.State.BLOCKED -> { - // Waiting for constraints (network). Show existing partial progress if any. + // ENQUEUED covers two different waits. A non-zero attempt count means the work + // already ran and failed, so this is the retry backoff rather than a missing + // network — saying "waiting for network" there would be false while online. val partial = apkManager.getPartialDownloadProgress() ApkDownloader.DownloadState.Downloading( partial ?: 0, - ApkDownloader.DownloadPhase.AwaitingConnectivity + queuedPhase(workInfo.runAttemptCount) ) } WorkInfo.State.RUNNING -> { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f5fde380..074ce540 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -252,6 +252,7 @@ Downloading… %1$d%% Waiting for network… + Retrying… Selecting download source… Waiting for Tor… Downloading… diff --git a/app/src/test/kotlin/com/bitchat/android/util/DownloadPhaseTest.kt b/app/src/test/kotlin/com/bitchat/android/util/DownloadPhaseTest.kt index f08c009c..7e5c7d6e 100644 --- a/app/src/test/kotlin/com/bitchat/android/util/DownloadPhaseTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/util/DownloadPhaseTest.kt @@ -43,6 +43,18 @@ class DownloadPhaseTest { ) } + @Test + fun `a queued retry is not reported as a connectivity wait`() { + // WorkManager returns a retry to ENQUEUED for the backoff even while the device is online, + // so attempt count is the only thing separating the two waits. + assertEquals( + ApkDownloader.DownloadPhase.AwaitingConnectivity, + queuedPhase(runAttemptCount = 0) + ) + assertEquals(ApkDownloader.DownloadPhase.Retrying, queuedPhase(runAttemptCount = 1)) + assertEquals(ApkDownloader.DownloadPhase.Retrying, queuedPhase(runAttemptCount = 2)) + } + @Test fun `only the transfer claims measurable progress`() { assertTrue(ApkDownloader.DownloadPhase.Transferring.hasMeasurableProgress)