refactor: connectivity monitoring (#553)

This commit is contained in:
Zane Schepke 2025-01-18 11:56:04 -05:00 committed by GitHub
parent 1b9560b601
commit 00254874f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 128 additions and 350 deletions

View File

@ -85,4 +85,8 @@ data class Settings(
defaultValue = "3", defaultValue = "3",
) )
val debounceDelaySeconds: Int = 3, val debounceDelaySeconds: Int = 3,
) ) {
fun debounceDelayMillis(): Long {
return debounceDelaySeconds * 1000L
}
}

View File

@ -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

View File

@ -1,9 +1,7 @@
package com.zaneschepke.wireguardautotunnel.module package com.zaneschepke.wireguardautotunnel.module
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -14,14 +12,5 @@ import dagger.hilt.components.SingletonComponent
abstract class ServiceModule { abstract class ServiceModule {
@Binds @Binds
@Wifi abstract fun provideInternetConnectivityService(wifiService: InternetConnectivityService): NetworkService
abstract fun provideWifiService(wifiService: WifiService): NetworkService
@Binds
@MobileData
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
@Binds
@Ethernet
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
} }

View File

@ -79,9 +79,7 @@ class TunnelModule {
@IoDispatcher ioDispatcher: CoroutineDispatcher, @IoDispatcher ioDispatcher: CoroutineDispatcher,
serviceManager: ServiceManager, serviceManager: ServiceManager,
notificationService: NotificationService, notificationService: NotificationService,
@Wifi wifiService: NetworkService, internetConnectivityService: NetworkService,
@MobileData mobileDataService: NetworkService,
@Ethernet ethernetService: NetworkService,
): TunnelService { ): TunnelService {
return WireGuardTunnel( return WireGuardTunnel(
amneziaBackend, amneziaBackend,
@ -92,9 +90,7 @@ class TunnelModule {
ioDispatcher, ioDispatcher,
serviceManager, serviceManager,
notificationService, notificationService,
wifiService, internetConnectivityService,
mobileDataService,
ethernetService,
) )
} }

View File

@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
import android.content.Intent import android.content.Intent
import android.net.NetworkCapabilities
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.core.app.ServiceCompat 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.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.module.Ethernet
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher 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.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent 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.AutoTunnelState
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState 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.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.NotificationAction
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
@ -53,16 +52,7 @@ class AutoTunnelService : LifecycleService() {
lateinit var rootShell: Provider<RootShell> lateinit var rootShell: Provider<RootShell>
@Inject @Inject
@Wifi lateinit var networkService: NetworkService
lateinit var wifiService: NetworkService
@Inject
@MobileData
lateinit var mobileDataService: NetworkService
@Inject
@Ethernet
lateinit var ethernetService: NetworkService
@Inject @Inject
lateinit var appDataRepository: Provider<AppDataRepository> lateinit var appDataRepository: Provider<AppDataRepository>
@ -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) { private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
combine( combine(
combineSettings(), combineSettings(),
combineNetworkEventsJob(), networkService.status.map {
buildNetworkState(it)
}.distinctUntilChanged(),
) { double, networkState -> ) { double, networkState ->
var wifiName: String? = null AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second)
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)
}.collect { state -> }.collect { state ->
Timber.d("Network state: ${state.networkState}") Timber.d("Network state: ${state.networkState}")
autoTunnelStateFlow.update { 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) { return if (setting.isWifiNameByShellEnabled) {
rootShell.get().getCurrentWifiName() rootShell.get().getCurrentWifiName()
} else if (wifiService.capabilities != null) {
WifiService.getNetworkName(wifiService.capabilities!!, this@AutoTunnelService)
} else { } else {
null InternetConnectivityService.getNetworkName(wifiCapabilities, this@AutoTunnelService)
} }
} }
@OptIn(FlowPreview::class)
private fun combineNetworkEventsJob(): Flow<NetworkState> {
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<Pair<Settings, TunnelConfigs>> { private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
return combine( return combine(
appDataRepository.get().settings.getSettingsFlow(), appDataRepository.get().settings.getSettingsFlow(),
@ -229,9 +213,8 @@ class AutoTunnelService : LifecycleService() {
private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) { private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) {
Timber.i("Starting auto-tunnel network event watcher") Timber.i("Starting auto-tunnel network event watcher")
val settings = appDataRepository.get().settings.getSettings() val settings = appDataRepository.get().settings.getSettings()
val debounce = settings.debounceDelaySeconds * 1000L Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
Timber.d("Starting with debounce delay of: $debounce") autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
autoTunnelStateFlow.debounce(debounce).collect { watcherState ->
if (watcherState == defaultState) return@collect if (watcherState == defaultState) return@collect
Timber.d("New auto tunnel state emitted") Timber.d("New auto tunnel state emitted")
when (val event = watcherState.asAutoTunnelEvent()) { when (val event = watcherState.asAutoTunnelEvent()) {

View File

@ -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)
}
}
}

View File

@ -9,42 +9,82 @@ import android.net.NetworkRequest
import android.net.wifi.SupplicantState import android.net.wifi.SupplicantState
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import com.wireguard.android.util.RootShell import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
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 dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOn
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 javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
class WifiService class InternetConnectivityService
@Inject @Inject
constructor( constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val settingsRepository: SettingsRepository, @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
@AppShell private val rootShell: Provider<RootShell>,
) : NetworkService { ) : NetworkService {
override var capabilities: NetworkCapabilities? = null
val mutex = Mutex()
private var ssid: String? = null
private var available: Boolean = false
private val connectivityManager = private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as 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 { 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 = val networkStatusCallback =
when (Build.VERSION.SDK_INT) { when (Build.VERSION.SDK_INT) {
in Build.VERSION_CODES.S..Int.MAX_VALUE -> { in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
@ -53,20 +93,16 @@ constructor(
FLAG_INCLUDE_LOCATION_INFO, FLAG_INCLUDE_LOCATION_INFO,
) { ) {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network)) onAvailable(network)
} }
override fun onLost(network: Network) { override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable()) updateCapabilityState(false, network)
emitState()
} }
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
trySend( onCapabilitiesChanged(network, networkCapabilities)
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
} }
} }
} }
@ -74,21 +110,16 @@ constructor(
else -> { else -> {
object : ConnectivityManager.NetworkCallback() { object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network)) onAvailable(network)
} }
override fun onLost(network: Network) { override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable()) updateCapabilityState(false, network)
emitState()
} }
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
capabilities = networkCapabilities onCapabilitiesChanged(network, networkCapabilities)
trySend(
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
} }
} }
} }
@ -96,37 +127,19 @@ constructor(
val request = val request =
NetworkRequest.Builder() NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build() .build()
connectivityManager.registerNetworkCallback(request, networkStatusCallback) connectivityManager.registerNetworkCallback(request, networkStatusCallback)
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) } awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
}.onStart { }.flowOn(ioDispatcher)
// needed for services that are not yet available as it will impact later combine flows if we don't emit
emit(NetworkStatus.Unavailable()) override fun getWifiCapabilities(): NetworkCapabilities? {
}.catch { wifiNetworkChanged = false
Timber.e(it) return wifiCapabilities
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))
}
} }
companion object { companion object {

View File

@ -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)
}
}
}

View File

@ -1,28 +1,12 @@
package com.zaneschepke.wireguardautotunnel.service.network package com.zaneschepke.wireguardautotunnel.service.network
import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
interface NetworkService { interface NetworkService {
val status: Flow<Status> val status: Flow<NetworkStatus>
var capabilities: NetworkCapabilities?
}
inline fun <Result> Flow<NetworkStatus>.map( // util to help limit location queries
crossinline onUnavailable: suspend () -> Result, val didWifiChangeSinceLastCapabilitiesQuery: Boolean
crossinline onAvailable: suspend (network: Network) -> Result, fun getWifiCapabilities(): NetworkCapabilities?
crossinline onCapabilitiesChanged:
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
): Flow<Result> = map { status ->
when (status) {
is NetworkStatus.Unavailable -> onUnavailable()
is NetworkStatus.Available -> onAvailable(status.network)
is NetworkStatus.CapabilitiesChanged ->
onCapabilitiesChanged(
status.network,
status.networkCapabilities,
)
}
} }

View File

@ -1,14 +1,9 @@
package com.zaneschepke.wireguardautotunnel.service.network package com.zaneschepke.wireguardautotunnel.service.network
import android.net.Network data class NetworkStatus(
import android.net.NetworkCapabilities val wifiAvailable: Boolean,
val ethernetAvailable: Boolean,
sealed class NetworkStatus { val cellularAvailable: Boolean,
abstract val isConnected: Boolean ) {
class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus() val allOffline = !wifiAvailable && !ethernetAvailable && !cellularAvailable
class Unavailable(override val isConnected: Boolean = false) : NetworkStatus()
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) :
NetworkStatus()
} }

View File

@ -1,6 +0,0 @@
package com.zaneschepke.wireguardautotunnel.service.network
data class Status(
val available: Boolean,
val name: String?,
)

View File

@ -2,28 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Backend import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.Tunnel.State import com.wireguard.android.backend.Tunnel.State
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import com.zaneschepke.wireguardautotunnel.module.Ethernet
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.Kernel 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.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID 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.AmneziaStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.Constants 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.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState 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 com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -63,9 +56,7 @@ constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher, @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val serviceManager: ServiceManager, private val serviceManager: ServiceManager,
private val notificationService: NotificationService, private val notificationService: NotificationService,
@Wifi private val wifiService: NetworkService, private val internetConnectivityService: NetworkService,
@MobileData private val mobileDataService: NetworkService,
@Ethernet private val ethernetService: NetworkService,
) : TunnelService { ) : TunnelService {
private val _vpnState = MutableStateFlow(VpnState()) private val _vpnState = MutableStateFlow(VpnState())
@ -105,23 +96,6 @@ constructor(
} }
} }
// TODO refactor duplicate
@OptIn(FlowPreview::class)
private fun combineNetworkEventsJob(): Flow<NetworkState> {
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<TunnelState> { private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
return runCatching { return runCatching {
when (val backend = backend()) { when (val backend = backend()) {
@ -447,13 +421,8 @@ constructor(
} }
private fun startNetworkJob() = applicationScope.launch(ioDispatcher) { private fun startNetworkJob() = applicationScope.launch(ioDispatcher) {
combineNetworkEventsJob().collect { internetConnectivityService.status.distinctUntilChanged().collect {
Timber.d("New network state: $it") isNetworkAvailable.set(!it.allOffline)
if (!it.isWifiConnected && !it.isEthernetConnected && !it.isMobileDataConnected) {
isNetworkAvailable.set(false)
} else {
isNetworkAvailable.set(true)
}
} }
} }