Skip to content

Commit b2e96e7

Browse files
authored
android: drop health warnings in Stopped and NeedsLogin state (#733)
fixes tailscale/corp#36233 We should be dropping health warnings when we're not in the Running state. This also adds some functionality to inject fake health warnings to make it possible to trigger these without triggering a real health condition. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
1 parent ede2a2d commit b2e96e7

3 files changed

Lines changed: 72 additions & 16 deletions

File tree

android/src/main/java/com/tailscale/ipn/ui/notifier/HealthNotifier.kt

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class HealthNotifier(
3434
const val HEALTH_CHANNEL_ID = "tailscale-health"
3535
}
3636

37-
private val TAG = "Health"
37+
private val TAG = "health"
3838
private val ignoredWarnableCodes: Set<String> =
3939
setOf(
4040
// Ignored on Android because installing unstable takes quite some effort
@@ -45,26 +45,27 @@ class HealthNotifier(
4545
"wantrunning-false")
4646

4747
init {
48+
// This roughly matches the iOS/macOS implementation in terms of debouncing, and ingoring
49+
// health warnings in various states.
4850
scope.launch {
4951
healthStateFlow
5052
.distinctUntilChanged { old, new -> old?.Warnings?.count() == new?.Warnings?.count() }
5153
.combine(ipnStateFlow, ::Pair)
52-
.debounce(5000)
54+
.debounce(3000)
5355
.collect { pair ->
5456
val health = pair.first
55-
val ipnState = pair.second
56-
// When the client is Stopped, no warnings should get added, and any warnings added
57-
// previously should be removed.
58-
if (ipnState == Ipn.State.Stopped) {
59-
TSLog.d(
60-
TAG,
61-
"Ignoring and dropping all pre-existing health messages in the Stopped state")
62-
dropAllWarnings()
63-
return@collect
64-
} else {
65-
TSLog.d(TAG, "Health updated: ${health?.Warnings?.keys?.sorted()}")
66-
health?.Warnings?.let {
67-
notifyHealthUpdated(it.values.mapNotNull { it }.toTypedArray())
57+
// Only deliver health notifications when the client is Running
58+
when (val ipnState = pair.second) {
59+
Ipn.State.Running -> {
60+
TSLog.d(TAG, "Health updated: ${health?.Warnings?.keys?.sorted()}")
61+
health?.Warnings?.let {
62+
notifyHealthUpdated(it.values.mapNotNull { it }.toTypedArray())
63+
}
64+
}
65+
else -> {
66+
TSLog.d(TAG, "Ignoring and dropping all health messages in state ${ipnState}")
67+
dropAllWarnings()
68+
return@collect
6869
}
6970
}
7071
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package com.tailscale.ipn.ui.notifier
5+
6+
import com.tailscale.ipn.ui.model.Health
7+
import com.tailscale.ipn.ui.util.set
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
10+
/**
11+
* Injects a fake Health.State for testing purposes.
12+
* This bypasses the normal IPN bus notification flow.
13+
*/
14+
fun Notifier.injectFakeHealthState(
15+
includeHighSeverity: Boolean = true,
16+
includeConnectivityImpact: Boolean = false,
17+
customWarnings: List<Health.UnhealthyState> = emptyList()
18+
) {
19+
val warnings = mutableMapOf<String, Health.UnhealthyState?>()
20+
21+
if (includeHighSeverity) {
22+
warnings["test-high-severity"] = Health.UnhealthyState(
23+
WarnableCode = "test-high-severity",
24+
Severity = Health.Severity.high,
25+
Title = "Test High Severity Warning",
26+
Text = "This is a test warning with high severity",
27+
ImpactsConnectivity = includeConnectivityImpact,
28+
DependsOn = null
29+
)
30+
}
31+
32+
warnings["test-low-severity"] = Health.UnhealthyState(
33+
WarnableCode = "test-low-severity",
34+
Severity = Health.Severity.low,
35+
Title = "Test Low Severity Warning",
36+
Text = "This is a test warning with low severity",
37+
ImpactsConnectivity = false,
38+
DependsOn = null
39+
)
40+
41+
customWarnings.forEach { warning ->
42+
warnings[warning.WarnableCode] = warning
43+
}
44+
45+
(health as MutableStateFlow).set(Health.State(Warnings = warnings))
46+
}

android/src/main/java/com/tailscale/ipn/ui/notifier/Notifier.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import kotlinx.serialization.ExperimentalSerializationApi
2020
import kotlinx.serialization.json.Json
2121
import kotlinx.serialization.json.decodeFromStream
2222

23+
// When set to true, the Notifier will inject fake health warnings for testing purposes
24+
val INJECT_FAKE_HEALTH_WARNINGS = false
25+
2326
// Notifier is a wrapper around the IPN Bus notifier. It provides a way to watch
2427
// for changes in various parts of the Tailscale engine. You will typically only use
2528
// a single Notifier per instance of your application which lasts for the lifetime of
@@ -86,7 +89,13 @@ object Notifier {
8689
notify.OutgoingFiles?.let(outgoingFiles::set)
8790
notify.FilesWaiting?.let(filesWaiting::set)
8891
notify.IncomingFiles?.let(incomingFiles::set)
89-
notify.Health?.let(health::set)
92+
notify.Health?.let {
93+
if (INJECT_FAKE_HEALTH_WARNINGS) {
94+
injectFakeHealthState()
95+
} else {
96+
health.set(it)
97+
}
98+
}
9099
}
91100
}
92101
}

0 commit comments

Comments
 (0)