From 00254874f09fb41f2551444caca0a62db945858d Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Sat, 18 Jan 2025 11:56:04 -0500 Subject: [PATCH] refactor: connectivity monitoring (#553) --- .../data/domain/Settings.kt | 6 +- .../module/NetworkQualifiers.kt | 15 -- .../module/ServiceModule.kt | 15 +- .../module/TunnelModule.kt | 8 +- .../autotunnel/AutoTunnelService.kt | 71 ++++----- .../service/network/EthernetService.kt | 67 --------- ...vice.kt => InternetConnectivityService.kt} | 141 ++++++++++-------- .../service/network/MobileDataService.kt | 67 --------- .../service/network/NetworkService.kt | 24 +-- .../service/network/NetworkStatus.kt | 17 +-- .../service/network/Status.kt | 6 - .../service/tunnel/WireGuardTunnel.kt | 41 +---- 12 files changed, 128 insertions(+), 350 deletions(-) delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NetworkQualifiers.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt rename app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/{WifiService.kt => InternetConnectivityService.kt} (52%) delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/Status.kt diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt index f128495..799445b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt @@ -85,4 +85,8 @@ data class Settings( defaultValue = "3", ) val debounceDelaySeconds: Int = 3, -) +) { + fun debounceDelayMillis(): Long { + return debounceDelaySeconds * 1000L + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NetworkQualifiers.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NetworkQualifiers.kt deleted file mode 100644 index f89048b..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NetworkQualifiers.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.module - -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Wifi - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class MobileData - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Ethernet diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt index b2c580e..c8eda6a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt @@ -1,9 +1,7 @@ package com.zaneschepke.wireguardautotunnel.module -import com.zaneschepke.wireguardautotunnel.service.network.EthernetService -import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService +import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService -import com.zaneschepke.wireguardautotunnel.service.network.WifiService import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,14 +12,5 @@ import dagger.hilt.components.SingletonComponent abstract class ServiceModule { @Binds - @Wifi - abstract fun provideWifiService(wifiService: WifiService): NetworkService - - @Binds - @MobileData - abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService - - @Binds - @Ethernet - abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService + abstract fun provideInternetConnectivityService(wifiService: InternetConnectivityService): NetworkService } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt index 19bfe47..a68394d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt @@ -79,9 +79,7 @@ class TunnelModule { @IoDispatcher ioDispatcher: CoroutineDispatcher, serviceManager: ServiceManager, notificationService: NotificationService, - @Wifi wifiService: NetworkService, - @MobileData mobileDataService: NetworkService, - @Ethernet ethernetService: NetworkService, + internetConnectivityService: NetworkService, ): TunnelService { return WireGuardTunnel( amneziaBackend, @@ -92,9 +90,7 @@ class TunnelModule { ioDispatcher, serviceManager, notificationService, - wifiService, - mobileDataService, - ethernetService, + internetConnectivityService, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt index bc83c92..e2b9554 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt @@ -1,6 +1,7 @@ package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel import android.content.Intent +import android.net.NetworkCapabilities import android.os.IBinder import android.os.PowerManager import androidx.core.app.ServiceCompat @@ -11,17 +12,15 @@ import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.AppShell -import com.zaneschepke.wireguardautotunnel.module.Ethernet import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher -import com.zaneschepke.wireguardautotunnel.module.MobileData -import com.zaneschepke.wireguardautotunnel.module.Wifi import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState +import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService -import com.zaneschepke.wireguardautotunnel.service.network.WifiService +import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification @@ -53,16 +52,7 @@ class AutoTunnelService : LifecycleService() { lateinit var rootShell: Provider @Inject - @Wifi - lateinit var wifiService: NetworkService - - @Inject - @MobileData - lateinit var mobileDataService: NetworkService - - @Inject - @Ethernet - lateinit var ethernetService: NetworkService + lateinit var networkService: NetworkService @Inject lateinit var appDataRepository: Provider @@ -168,17 +158,28 @@ class AutoTunnelService : LifecycleService() { } } + suspend fun buildNetworkState(networkStatus: NetworkStatus): NetworkState { + return with(autoTunnelStateFlow.value.networkState) { + val wifiName = when { + networkStatus.wifiAvailable && + (wifiName == null || wifiName == Constants.UNREADABLE_SSID || networkService.didWifiChangeSinceLastCapabilitiesQuery) -> { + networkService.getWifiCapabilities()?.let { getWifiName(it) } ?: wifiName + } + !networkStatus.wifiAvailable -> null + else -> wifiName + } + copy(networkStatus.wifiAvailable, networkStatus.cellularAvailable, networkStatus.ethernetAvailable, wifiName) + } + } + private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) { combine( combineSettings(), - combineNetworkEventsJob(), + networkService.status.map { + buildNetworkState(it) + }.distinctUntilChanged(), ) { double, networkState -> - var wifiName: String? = null - if (networkState.wifiName == Constants.UNREADABLE_SSID && double.first.isTunnelOnWifiEnabled) { - wifiName = getWifiName(double.first) - } - val netState = wifiName?.let { networkState.copy(wifiName = it) } ?: networkState - AutoTunnelState(tunnelService.get().vpnState.value, netState, double.first, double.second) + AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second) }.collect { state -> Timber.d("Network state: ${state.networkState}") autoTunnelStateFlow.update { @@ -187,32 +188,15 @@ class AutoTunnelService : LifecycleService() { } } - private fun getWifiName(setting: Settings): String? { + private suspend fun getWifiName(wifiCapabilities: NetworkCapabilities): String? { + val setting = appDataRepository.get().settings.getSettings() return if (setting.isWifiNameByShellEnabled) { rootShell.get().getCurrentWifiName() - } else if (wifiService.capabilities != null) { - WifiService.getNetworkName(wifiService.capabilities!!, this@AutoTunnelService) } else { - null + InternetConnectivityService.getNetworkName(wifiCapabilities, this@AutoTunnelService) } } - @OptIn(FlowPreview::class) - private fun combineNetworkEventsJob(): Flow { - return combine( - wifiService.status, - mobileDataService.status, - ethernetService.status, - ) { wifi, mobileData, ethernet -> - NetworkState( - wifi.available, - mobileData.available, - ethernet.available, - wifi.name, - ) - }.distinctUntilChanged() - } - private fun combineSettings(): Flow> { return combine( appDataRepository.get().settings.getSettingsFlow(), @@ -229,9 +213,8 @@ class AutoTunnelService : LifecycleService() { private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) { Timber.i("Starting auto-tunnel network event watcher") val settings = appDataRepository.get().settings.getSettings() - val debounce = settings.debounceDelaySeconds * 1000L - Timber.d("Starting with debounce delay of: $debounce") - autoTunnelStateFlow.debounce(debounce).collect { watcherState -> + Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds") + autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState -> if (watcherState == defaultState) return@collect Timber.d("New auto tunnel state emitted") when (val event = watcherState.asAutoTunnelEvent()) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt deleted file mode 100644 index 43d5570..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.network - -import android.content.Context -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import timber.log.Timber -import javax.inject.Inject - -class EthernetService -@Inject -constructor( - @ApplicationContext context: Context, -) : NetworkService { - - override var capabilities: NetworkCapabilities? = null - - private val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - override val status = callbackFlow { - val networkStatusCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - trySend(NetworkStatus.Available(network)) - } - override fun onLost(network: Network) { - trySend(NetworkStatus.Unavailable()) - } - override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - capabilities = networkCapabilities - trySend( - NetworkStatus.CapabilitiesChanged( - network, - networkCapabilities, - ), - ) - } - } - val request = - NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .build() - connectivityManager.registerNetworkCallback(request, networkStatusCallback) - - awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) } - }.onStart { - // needed for services that are not yet available as it will impact later combine flows if we don't emit - emit(NetworkStatus.Unavailable()) - }.catch { - Timber.e(it) - emit(NetworkStatus.Unavailable()) - }.map { - when (it) { - is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null) - is NetworkStatus.Unavailable -> Status(false, null) - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/InternetConnectivityService.kt similarity index 52% rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/InternetConnectivityService.kt index f51b748..f8950e6 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/InternetConnectivityService.kt @@ -9,42 +9,82 @@ import android.net.NetworkRequest import android.net.wifi.SupplicantState import android.net.wifi.WifiManager import android.os.Build -import com.wireguard.android.util.RootShell -import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository -import com.zaneschepke.wireguardautotunnel.module.AppShell -import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName +import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.transform -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import timber.log.Timber +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject -import javax.inject.Provider -class WifiService +class InternetConnectivityService @Inject constructor( @ApplicationContext private val context: Context, - private val settingsRepository: SettingsRepository, - @AppShell private val rootShell: Provider, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : NetworkService { - override var capabilities: NetworkCapabilities? = null - - val mutex = Mutex() - - private var ssid: String? = null - private var available: Boolean = false - private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + @get:Synchronized @set:Synchronized + private var wifiCapabilities: NetworkCapabilities? = null + + @get:Synchronized @set:Synchronized + private var wifiNetworkChanged: Boolean = false + + override val didWifiChangeSinceLastCapabilitiesQuery: Boolean + get() = wifiNetworkChanged + override val status = callbackFlow { + + var wifiState: Boolean = false + var ethernetState: Boolean = false + var cellularState: Boolean = false + + fun emitState() { + trySend(NetworkStatus(wifiState, ethernetState, cellularState)) + } + + val currentNetwork = connectivityManager.activeNetwork + if (currentNetwork == null) { + emitState() + } + + fun updateCapabilityState(up: Boolean, network: Network) { + with(connectivityManager.getNetworkCapabilities(network)) { + when { + this == null -> return + hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> wifiState = up + hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> + cellularState = up + + hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> + ethernetState = up + } + } + } + + fun onWifiChange(network: Network, callback: () -> Unit) { + if (connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { + callback() + } + } + + fun onAvailable(network: Network) { + onWifiChange(network) { + wifiNetworkChanged = true + } + } + + fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + onWifiChange(network) { + wifiCapabilities = networkCapabilities + } + updateCapabilityState(true, network) + emitState() + } + val networkStatusCallback = when (Build.VERSION.SDK_INT) { in Build.VERSION_CODES.S..Int.MAX_VALUE -> { @@ -53,20 +93,16 @@ constructor( FLAG_INCLUDE_LOCATION_INFO, ) { override fun onAvailable(network: Network) { - trySend(NetworkStatus.Available(network)) + onAvailable(network) } override fun onLost(network: Network) { - trySend(NetworkStatus.Unavailable()) + updateCapabilityState(false, network) + emitState() } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - trySend( - NetworkStatus.CapabilitiesChanged( - network, - networkCapabilities, - ), - ) + onCapabilitiesChanged(network, networkCapabilities) } } } @@ -74,21 +110,16 @@ constructor( else -> { object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { - trySend(NetworkStatus.Available(network)) + onAvailable(network) } override fun onLost(network: Network) { - trySend(NetworkStatus.Unavailable()) + updateCapabilityState(false, network) + emitState() } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - capabilities = networkCapabilities - trySend( - NetworkStatus.CapabilitiesChanged( - network, - networkCapabilities, - ), - ) + onCapabilitiesChanged(network, networkCapabilities) } } } @@ -96,37 +127,19 @@ constructor( val request = NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build() connectivityManager.registerNetworkCallback(request, networkStatusCallback) awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) } - }.onStart { - // needed for services that are not yet available as it will impact later combine flows if we don't emit - emit(NetworkStatus.Unavailable()) - }.catch { - Timber.e(it) - emit(NetworkStatus.Unavailable()) - }.transform { - when (it) { - is NetworkStatus.Available -> mutex.withLock { - available = true - } - is NetworkStatus.CapabilitiesChanged -> mutex.withLock { - if (available || ssid == null || ssid == Constants.UNREADABLE_SSID) { - available = false - Timber.d("Getting SSID from capabilities") - ssid = if (settingsRepository.getSettings().isWifiNameByShellEnabled) { - rootShell.get().getCurrentWifiName() - } else { - getNetworkName(it.networkCapabilities, context) - } - } - emit(Status(true, ssid)) - } - is NetworkStatus.Unavailable -> emit(Status(false, null)) - } + }.flowOn(ioDispatcher) + + override fun getWifiCapabilities(): NetworkCapabilities? { + wifiNetworkChanged = false + return wifiCapabilities } companion object { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt deleted file mode 100644 index 58ff278..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.network - -import android.content.Context -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import timber.log.Timber -import javax.inject.Inject - -class MobileDataService -@Inject -constructor( - @ApplicationContext context: Context, -) : NetworkService { - - override var capabilities: NetworkCapabilities? = null - - private val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - override val status = callbackFlow { - val networkStatusCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - trySend(NetworkStatus.Available(network)) - } - override fun onLost(network: Network) { - trySend(NetworkStatus.Unavailable()) - } - override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - capabilities = networkCapabilities - trySend( - NetworkStatus.CapabilitiesChanged( - network, - networkCapabilities, - ), - ) - } - } - val request = - NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .build() - connectivityManager.registerNetworkCallback(request, networkStatusCallback) - - awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) } - }.onStart { - // needed for services that are not yet available as it will impact later combine flows if we don't emit - emit(NetworkStatus.Unavailable()) - }.catch { - Timber.e(it) - emit(NetworkStatus.Unavailable()) - }.map { - when (it) { - is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null) - is NetworkStatus.Unavailable -> Status(false, null) - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkService.kt index d4ec68f..af2a27f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkService.kt @@ -1,28 +1,12 @@ package com.zaneschepke.wireguardautotunnel.service.network -import android.net.Network import android.net.NetworkCapabilities import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map interface NetworkService { - val status: Flow - var capabilities: NetworkCapabilities? -} + val status: Flow -inline fun Flow.map( - crossinline onUnavailable: suspend () -> Result, - crossinline onAvailable: suspend (network: Network) -> Result, - crossinline onCapabilitiesChanged: - suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result, -): Flow = map { status -> - when (status) { - is NetworkStatus.Unavailable -> onUnavailable() - is NetworkStatus.Available -> onAvailable(status.network) - is NetworkStatus.CapabilitiesChanged -> - onCapabilitiesChanged( - status.network, - status.networkCapabilities, - ) - } + // util to help limit location queries + val didWifiChangeSinceLastCapabilitiesQuery: Boolean + fun getWifiCapabilities(): NetworkCapabilities? } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkStatus.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkStatus.kt index 2c338e7..24ea8ce 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkStatus.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/NetworkStatus.kt @@ -1,14 +1,9 @@ package com.zaneschepke.wireguardautotunnel.service.network -import android.net.Network -import android.net.NetworkCapabilities - -sealed class NetworkStatus { - abstract val isConnected: Boolean - class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus() - - class Unavailable(override val isConnected: Boolean = false) : NetworkStatus() - - class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) : - NetworkStatus() +data class NetworkStatus( + val wifiAvailable: Boolean, + val ethernetAvailable: Boolean, + val cellularAvailable: Boolean, +) { + val allOffline = !wifiAvailable && !ethernetAvailable && !cellularAvailable } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/Status.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/Status.kt deleted file mode 100644 index 6a6bf91..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/Status.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.network - -data class Status( - val available: Boolean, - val name: String?, -) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index 67248a4..d40d285 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -2,28 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel import com.wireguard.android.backend.Backend import com.wireguard.android.backend.Tunnel.State +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope -import com.zaneschepke.wireguardautotunnel.module.Ethernet import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.Kernel -import com.zaneschepke.wireguardautotunnel.module.MobileData -import com.zaneschepke.wireguardautotunnel.module.Wifi import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager -import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState @@ -31,14 +27,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -63,9 +56,7 @@ constructor( @IoDispatcher private val ioDispatcher: CoroutineDispatcher, private val serviceManager: ServiceManager, private val notificationService: NotificationService, - @Wifi private val wifiService: NetworkService, - @MobileData private val mobileDataService: NetworkService, - @Ethernet private val ethernetService: NetworkService, + private val internetConnectivityService: NetworkService, ) : TunnelService { private val _vpnState = MutableStateFlow(VpnState()) @@ -105,23 +96,6 @@ constructor( } } - // TODO refactor duplicate - @OptIn(FlowPreview::class) - private fun combineNetworkEventsJob(): Flow { - return combine( - wifiService.status, - mobileDataService.status, - ethernetService.status, - ) { wifi, mobileData, ethernet -> - NetworkState( - wifi.available, - mobileData.available, - ethernet.available, - wifi.name, - ) - }.distinctUntilChanged() - } - private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result { return runCatching { when (val backend = backend()) { @@ -447,13 +421,8 @@ constructor( } private fun startNetworkJob() = applicationScope.launch(ioDispatcher) { - combineNetworkEventsJob().collect { - Timber.d("New network state: $it") - if (!it.isWifiConnected && !it.isEthernetConnected && !it.isMobileDataConnected) { - isNetworkAvailable.set(false) - } else { - isNetworkAvailable.set(true) - } + internetConnectivityService.status.distinctUntilChanged().collect { + isNetworkAvailable.set(!it.allOffline) } }