diff --git a/android/src/main/java/com/tailscale/ipn/ui/notifier/HealthNotifier.kt b/android/src/main/java/com/tailscale/ipn/ui/notifier/HealthNotifier.kt index 1e2bfc20a0..21eb18a157 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/notifier/HealthNotifier.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/notifier/HealthNotifier.kt @@ -74,7 +74,11 @@ class HealthNotifier( } .collect { health -> TSLog.d(TAG, "Health updated: ${health?.Warnings?.keys?.sorted()}") - health?.Warnings?.values?.filterNotNull()?.toTypedArray()?.let(::notifyHealthUpdated) + // A healthy backend sends Warnings = null; treat it as empty so that + // previously shown warnings are dropped. + notifyHealthUpdated( + health?.Warnings?.values?.filterNotNull()?.toTypedArray() ?: emptyArray() + ) } } } diff --git a/android/src/test/kotlin/com/tailscale/ipn/ui/notifier/HealthNotifierTest.kt b/android/src/test/kotlin/com/tailscale/ipn/ui/notifier/HealthNotifierTest.kt index b14b719fd5..be382b2a3c 100644 --- a/android/src/test/kotlin/com/tailscale/ipn/ui/notifier/HealthNotifierTest.kt +++ b/android/src/test/kotlin/com/tailscale/ipn/ui/notifier/HealthNotifierTest.kt @@ -102,6 +102,24 @@ class HealthNotifierTest { assertTrue(notifier.currentWarnings.value.isEmpty()) } + /** + * The backend reports a fully healthy state as Warnings = null (a nil Go map), not as an empty + * map. The warning must still be dropped. + */ + @Test + fun warningClearsWhenBackendSendsNullWarnings() = runTest { + val (healthFlow, _, notifier) = createRunningNotifier() + + healthFlow.value = healthState(derpWarning()) + settle() + assertEquals(1, notifier.currentWarnings.value.size) + + healthFlow.value = Health.State(Warnings = null) + settle() + assertTrue(notifier.currentWarnings.value.isEmpty()) + assertNull(notifier.currentIcon.value) + } + @Test fun warningsDroppedWhenStateLeavesRunning() = runTest { val (healthFlow, ipnFlow, notifier) = createRunningNotifier()