@@ -4,6 +4,7 @@ import android.content.Context
44import android.net.ConnectivityManager
55import android.net.Network
66import android.net.NetworkCapabilities
7+ import android.net.NetworkRequest
78import android.os.Handler
89import android.os.HandlerThread
910import org.greenrobot.eventbus.EventBus
@@ -13,21 +14,25 @@ import javax.inject.Inject
1314import 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
2531class 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