Coordinate connectivity detection across multiple networks

This commit is contained in:
Oscar Mira 2025-04-12 18:54:46 +02:00
parent 94a2ecf429
commit 2dae3cccea
No known key found for this signature in database
GPG key ID: B371B98C5DC32237

View file

@ -5,78 +5,107 @@
package org.thoughtcrime.securesms.messages
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.os.Build
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.util.ServiceUtil
/**
* Backcompat listener for determining when the network connection is lost.
* On API 28+, [onNetworkLost] is invoked when the system notifies the app that the network is lost.
* On earlier versions, [onNetworkLost] is invoked on any network change (gained, lost, losing, etc)
* Therefore, [onNetworkLost] is a higher-order function, which takes a function to determine conditionally if it should run.
* API 28+ only runs on lost networks, so it provides a conditional that's always true because that is guaranteed by the call site.
* Earlier versions use [NetworkConstraint.isMet] to query the current network state upon receiving the broadcast.
* Observes changes in internet connectivity and notifies when the connection is lost or regained.
*
* The [onNetworkLost] callback is triggered with a Boolean indicating whether the Internet connection
* is lost or regained. The current connection state is also provided immediately upon registration.
*/
class NetworkConnectionListener(private val context: Context, private val onNetworkLost: (() -> Boolean) -> Unit) {
class NetworkConnectionListener(context: Context, private val onNetworkLost: (() -> Boolean) -> Unit) {
companion object {
private val TAG = Log.tag(NetworkConnectionListener::class.java)
}
private val connectivityManager = ServiceUtil.getConnectivityManager(context)
private val networkChangedCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onUnavailable() {
super.onUnavailable()
Log.d(TAG, "ConnectivityManager.NetworkCallback onUnavailable()")
onNetworkLost { true }
inner class NetworkStateCallback : ConnectivityManager.NetworkCallback() {
private val currentNetworks = mutableMapOf<Network, Boolean>()
private var lastConnectionState: Boolean? = null
private fun updateConnectionState(hasConnection: Boolean) {
lastConnectionState = hasConnection
onNetworkLost { !hasConnection }
}
private fun connectionChanged() {
synchronized(this) {
val hasConnection = currentNetworks.any { it.value }
if (lastConnectionState != hasConnection) {
updateConnectionState(hasConnection)
}
}
}
fun setInitialConnectionState(network: Network?, isAvailable: Boolean) {
Log.d(TAG, "Initial state: Network $network is ${if (isAvailable) "UP" else "DOWN"}")
synchronized(this) {
if (lastConnectionState == null) {
if (network != null && isAvailable) {
currentNetworks[network] = true
updateConnectionState(hasConnection = true)
} else {
updateConnectionState(hasConnection = false)
}
} else {
Log.d(TAG, "Initial state already set, skipping")
}
}
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
super.onBlockedStatusChanged(network, blocked)
Log.d(TAG, "ConnectivityManager.NetworkCallback onBlockedStatusChanged()")
onNetworkLost { blocked }
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.d(TAG, "ConnectivityManager.NetworkCallback onAvailable()")
onNetworkLost { false }
Log.d(TAG, "Network $network is UP and ${if (blocked) "BLOCKED" else "UNBLOCKED"}")
currentNetworks[network] = !blocked
connectionChanged()
}
override fun onLost(network: Network) {
super.onLost(network)
Log.d(TAG, "ConnectivityManager.NetworkCallback onLost()")
onNetworkLost { true }
Log.d(TAG, "Network $network LOST")
currentNetworks.remove(network)
connectionChanged()
}
}
private val connectionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "BroadcastReceiver onReceive().")
onNetworkLost { !NetworkConstraint.isMet(context) }
}
}
private var networkStateCallback: NetworkStateCallback? = null
@Synchronized
fun register() {
if (Build.VERSION.SDK_INT >= 28) {
connectivityManager.registerDefaultNetworkCallback(networkChangedCallback)
} else {
context.registerReceiver(connectionReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}
if (networkStateCallback != null) return
val request =
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
val callback = NetworkStateCallback()
networkStateCallback = callback
connectivityManager.registerNetworkCallback(request, callback)
val network = connectivityManager.activeNetwork
val hasInternet = connectivityManager
.getNetworkCapabilities(network)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
callback.setInitialConnectionState(network, isAvailable = hasInternet)
}
@Synchronized
fun unregister() {
if (Build.VERSION.SDK_INT >= 28) {
connectivityManager.unregisterNetworkCallback(networkChangedCallback)
} else {
context.unregisterReceiver(connectionReceiver)
networkStateCallback?.let { callback ->
connectivityManager.unregisterNetworkCallback(callback)
networkStateCallback = null
}
}
}