refactor: connectivity monitoring (#553)
This commit is contained in:
parent
1b9560b601
commit
00254874f0
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.service.network
|
|
||||||
|
|
||||||
data class Status(
|
|
||||||
val available: Boolean,
|
|
||||||
val name: String?,
|
|
||||||
)
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue