diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-06-03 09:24:44 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-06-03 16:34:56 +0200 |
| commit | 2440239ec1658de53b169b109ea6a91a4e7a2c34 (patch) | |
| tree | f9e8012e7a87587e6054ba0c0469fff90ac9d4c3 /android/lib | |
| parent | 8e9b5078eb1a2237d78f1a96fab6ad95ef499283 (diff) | |
| download | mullvadvpn-2440239ec1658de53b169b109ea6a91a4e7a2c34.tar.xz mullvadvpn-2440239ec1658de53b169b109ea6a91a4e7a2c34.zip | |
Listen for all network events to fix missing default network events
Diffstat (limited to 'android/lib')
| -rw-r--r-- | android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt | 152 |
1 files changed, 113 insertions, 39 deletions
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt index 5409bc2f99..1d1b82e685 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt @@ -5,6 +5,7 @@ import android.net.ConnectivityManager.NetworkCallback import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities +import android.net.NetworkRequest import co.touchlab.kermit.Logger import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.FlowPreview @@ -12,59 +13,127 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.scan import net.mullvad.talpid.model.Connectivity private val CONNECTIVITY_DEBOUNCE = 300.milliseconds -fun ConnectivityManager.defaultNetworkEvents(): Flow<NetworkEvent> = callbackFlow { - val callback = - object : NetworkCallback() { - override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { - super.onLinkPropertiesChanged(network, linkProperties) - trySendBlocking(NetworkEvent.LinkPropertiesChanged(network, linkProperties)) - } +fun ConnectivityManager.defaultNetworkEvents(): Flow<NetworkEvent> = + callbackFlow { + val callback = + object : NetworkCallback() { + override fun onLinkPropertiesChanged( + network: Network, + linkProperties: LinkProperties, + ) { + super.onLinkPropertiesChanged(network, linkProperties) + trySendBlocking(NetworkEvent.LinkPropertiesChanged(network, linkProperties)) + } - override fun onAvailable(network: Network) { - super.onAvailable(network) - trySendBlocking(NetworkEvent.Available(network)) - } + override fun onAvailable(network: Network) { + super.onAvailable(network) + trySendBlocking(NetworkEvent.Available(network)) + } - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities, - ) { - super.onCapabilitiesChanged(network, networkCapabilities) - trySendBlocking(NetworkEvent.CapabilitiesChanged(network, networkCapabilities)) - } + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities, + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + trySendBlocking( + NetworkEvent.CapabilitiesChanged(network, networkCapabilities) + ) + } - override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { - super.onBlockedStatusChanged(network, blocked) - trySendBlocking(NetworkEvent.BlockedStatusChanged(network, blocked)) - } + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { + super.onBlockedStatusChanged(network, blocked) + trySendBlocking(NetworkEvent.BlockedStatusChanged(network, blocked)) + } - override fun onLosing(network: Network, maxMsToLive: Int) { - super.onLosing(network, maxMsToLive) - trySendBlocking(NetworkEvent.Losing(network, maxMsToLive)) - } + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + trySendBlocking(NetworkEvent.Losing(network, maxMsToLive)) + } - override fun onLost(network: Network) { - super.onLost(network) - trySendBlocking(NetworkEvent.Lost(network)) - } + override fun onLost(network: Network) { + super.onLost(network) + trySendBlocking(NetworkEvent.Lost(network)) + } - override fun onUnavailable() { - super.onUnavailable() - trySendBlocking(NetworkEvent.Unavailable) - } + override fun onUnavailable() { + super.onUnavailable() + trySendBlocking(NetworkEvent.Unavailable) + } + } + registerDefaultNetworkCallback(callback) + + awaitClose { unregisterNetworkCallback(callback) } } - registerDefaultNetworkCallback(callback) + .onEach { Logger.d("Got a default network event type: ${it::class.simpleName}") } - awaitClose { unregisterNetworkCallback(callback) } -} +fun ConnectivityManager.nonVpnInternetNetworksEvents(): Flow<NetworkEvent> = + callbackFlow { + val callback = + object : NetworkCallback() { + override fun onLinkPropertiesChanged( + network: Network, + linkProperties: LinkProperties, + ) { + super.onLinkPropertiesChanged(network, linkProperties) + trySendBlocking(NetworkEvent.LinkPropertiesChanged(network, linkProperties)) + } + + override fun onAvailable(network: Network) { + super.onAvailable(network) + trySendBlocking(NetworkEvent.Available(network)) + } + + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities, + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + trySendBlocking( + NetworkEvent.CapabilitiesChanged(network, networkCapabilities) + ) + } + + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { + super.onBlockedStatusChanged(network, blocked) + trySendBlocking(NetworkEvent.BlockedStatusChanged(network, blocked)) + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + trySendBlocking(NetworkEvent.Losing(network, maxMsToLive)) + } + + override fun onLost(network: Network) { + super.onLost(network) + trySendBlocking(NetworkEvent.Lost(network)) + } + + override fun onUnavailable() { + super.onUnavailable() + trySendBlocking(NetworkEvent.Unavailable) + } + } + registerNetworkCallback( + NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), + callback, + ) + + awaitClose { unregisterNetworkCallback(callback) } + } + .onEach { Logger.d("Got an other network event type: ${it::class.simpleName}") } internal fun ConnectivityManager.defaultRawNetworkStateFlow(): Flow<RawNetworkState?> = defaultNetworkEvents().scan(null as RawNetworkState?) { state, event -> state.reduce(event) } @@ -130,13 +199,18 @@ internal fun ConnectivityManager.activeRawNetworkState(): RawNetworkState? = * default network and depending on if it is a VPN. If it is not a VPN we check the network * properties directly and if it is a VPN we use a socket to check the underlying network. A * debounce is applied to avoid emitting too many events and to avoid setting the app in an offline - * state when switching networks. + * state when switching networks. The flow is combined with the all network events to fix issues + * with the default network not being updated correctly on Android 9 and below when the VPN is + * turned on. The all network event is not used it is just there to trigger a new connectivity + * check. */ @OptIn(FlowPreview::class) fun ConnectivityManager.hasInternetConnectivity( resolver: UnderlyingConnectivityStatusResolver ): Flow<Connectivity> = - this.defaultRawNetworkStateFlow() + combine(nonVpnInternetNetworksEvents(), defaultRawNetworkStateFlow()) { _, defaultEvent -> + defaultEvent + } .debounce(CONNECTIVITY_DEBOUNCE) .map { resolveConnectivityStatus(it, resolver) } .distinctUntilChanged() |
