summaryrefslogtreecommitdiffhomepage
path: root/android/lib
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-06-03 09:24:44 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-06-03 16:34:56 +0200
commit2440239ec1658de53b169b109ea6a91a4e7a2c34 (patch)
treef9e8012e7a87587e6054ba0c0469fff90ac9d4c3 /android/lib
parent8e9b5078eb1a2237d78f1a96fab6ad95ef499283 (diff)
downloadmullvadvpn-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.kt152
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()