Skip to content

Commit cadb804

Browse files
committed
Fix connection bar not updating live on connectivity change
Fix 2 re-queried ConnectivityManager.getActiveNetwork() in onLost to avoid misreporting a network handover as a disconnect. On real devices getActiveNetwork() can still return the network being torn down at that instant, so a genuine disconnect was suppressed by the de-dupe, leaving wasConnected stuck true. The "No connection" bar then only updated on the next onResume poll, not live. Track the set of available internet-capable networks instead (registerNetworkCallback with a NET_CAPABILITY_INTERNET request): a handover adds the replacement before removing the old network so the set never empties (no false disconnect), while a real disconnect empties it reliably. Removes the fragile getActiveNetwork() query.
1 parent c95717d commit cadb804

1 file changed

Lines changed: 21 additions & 21 deletions

File tree

WordPress/src/main/java/org/wordpress/android/networking/NetworkConnectionMonitor.kt

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.net.ConnectivityManager
55
import android.net.Network
66
import android.net.NetworkCapabilities
7+
import android.net.NetworkRequest
78
import android.os.Handler
89
import android.os.HandlerThread
910
import org.greenrobot.eventbus.EventBus
@@ -13,21 +14,25 @@ import javax.inject.Inject
1314
import javax.inject.Singleton
1415

1516
/**
16-
* Global monitor for changes to the device's default network connectivity.
17+
* Global monitor for changes to the device's network connectivity.
1718
*
1819
* Uses [ConnectivityManager.NetworkCallback] registered on a background [HandlerThread] rather than the
1920
* deprecated CONNECTIVITY_ACTION broadcast. Because a NetworkCallback is not a broadcast it is not subject to
2021
* background-broadcast ANR timeouts, and all connectivity work runs off the main thread. A
2122
* [ConnectionChangeEvent] is posted on EventBus whenever the connected state changes; EventBus marshals the
2223
* `onEventMainThread` subscribers back onto the main thread.
24+
*
25+
* Connectivity is tracked as the set of currently-available internet-capable networks rather than a single
26+
* network. During a handover (e.g. Wi-Fi -> cellular) the replacement is added before the old one is removed,
27+
* so the set never empties and the handover is not misreported as a disconnection; a genuine disconnect
28+
* empties the set and is reported reliably.
2329
*/
2430
@Singleton
2531
class NetworkConnectionMonitor @Inject constructor() {
2632
private var started = false
2733
private var wasConnected = false
2834
private var isFirstCallback = true
29-
30-
private var connectivityManager: ConnectivityManager? = null
35+
private val availableNetworks = mutableSetOf<Network>()
3136

3237
class ConnectionChangeEvent(val isConnected: Boolean)
3338

@@ -36,23 +41,28 @@ class NetworkConnectionMonitor @Inject constructor() {
3641
if (started) return
3742
val manager = context.applicationContext
3843
.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return
39-
connectivityManager = manager
4044

4145
val thread = HandlerThread("NetworkConnectionMonitor").apply { start() }
4246
val callback = object : ConnectivityManager.NetworkCallback() {
43-
override fun onAvailable(network: Network) = onConnectivityChanged(true)
44-
// onLost fires for the network that was lost, but during a handover (e.g. Wi-Fi -> cellular) the
45-
// new default has already arrived via onAvailable, so re-query rather than assuming we're offline.
46-
override fun onLost(network: Network) = onConnectivityChanged(hasActiveConnection())
47+
override fun onAvailable(network: Network) {
48+
availableNetworks.add(network)
49+
onConnectivityChanged(availableNetworks.isNotEmpty())
50+
}
51+
override fun onLost(network: Network) {
52+
availableNetworks.remove(network)
53+
onConnectivityChanged(availableNetworks.isNotEmpty())
54+
}
4755
}
48-
manager.registerDefaultNetworkCallback(callback, Handler(thread.looper))
56+
val request = NetworkRequest.Builder()
57+
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
58+
.build()
59+
manager.registerNetworkCallback(request, callback, Handler(thread.looper))
4960
started = true
5061
}
5162

5263
/**
5364
* Called on the monitor's background thread. Posts a [ConnectionChangeEvent] on the first callback and
54-
* whenever the connected state actually changes, so subscribers aren't spammed when a network's
55-
* capabilities change without a change in availability.
65+
* whenever the connected state actually changes, so subscribers aren't spammed while connectivity churns.
5666
*/
5767
private fun onConnectivityChanged(isConnected: Boolean) {
5868
if (isFirstCallback || isConnected != wasConnected) {
@@ -62,14 +72,4 @@ class NetworkConnectionMonitor @Inject constructor() {
6272
EventBus.getDefault().post(ConnectionChangeEvent(isConnected))
6373
}
6474
}
65-
66-
/**
67-
* Whether there is currently a default network capable of reaching the internet. Used to distinguish a
68-
* true disconnection from a handover between networks, where the replacement is already the default.
69-
*/
70-
private fun hasActiveConnection(): Boolean {
71-
val manager = connectivityManager ?: return false
72-
val capabilities = manager.activeNetwork?.let { manager.getNetworkCapabilities(it) }
73-
return capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
74-
}
7575
}

0 commit comments

Comments
 (0)