fix: auto tunnel bugs

Fixes auto tunnel bug that can happen on startup
Fixes auto tunnel bug that didn't allow manual toggle override of tunnel while auto tunnel is active
Add basic foreground persistent notifications (optionally turned off via android settings)
This commit is contained in:
Zane Schepke 2024-11-07 23:03:00 -05:00
parent 7d810c7c3d
commit cab2945930
12 changed files with 100 additions and 70 deletions

View File

@ -9,3 +9,11 @@ annotation class Kernel
@Qualifier @Qualifier
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class Userspace annotation class Userspace
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class TunnelShell
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppShell

View File

@ -25,9 +25,18 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
class TunnelModule { class TunnelModule {
@Provides @Provides
@Singleton @Singleton
fun provideRootShell(@ApplicationContext context: Context): RootShell { @TunnelShell
fun provideTunnelRootShell(@ApplicationContext context: Context): RootShell {
return RootShell(context)
}
@Provides
@Singleton
@AppShell
fun provideAppRootShell(@ApplicationContext context: Context): RootShell {
return RootShell(context) return RootShell(context)
} }
@ -40,14 +49,14 @@ class TunnelModule {
@Provides @Provides
@Singleton @Singleton
@Userspace @Userspace
fun provideUserspaceBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend { fun provideUserspaceBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
return GoBackend(context, RootTunnelActionHandler(rootShell)) return GoBackend(context, RootTunnelActionHandler(rootShell))
} }
@Provides @Provides
@Singleton @Singleton
@Kernel @Kernel
fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend { fun provideKernelBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell)) return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell))
} }

View File

@ -12,6 +12,7 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.Settings
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.module.AppShell
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.service.network.EthernetService import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
@ -25,7 +26,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
import com.zaneschepke.wireguardautotunnel.util.extensions.isDown
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -34,7 +34,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine 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
@ -50,6 +49,7 @@ class AutoTunnelService : LifecycleService() {
private val foregroundId = 122 private val foregroundId = 122
@Inject @Inject
@AppShell
lateinit var rootShell: Provider<RootShell> lateinit var rootShell: Provider<RootShell>
@Inject @Inject
@ -143,7 +143,7 @@ class AutoTunnelService : LifecycleService() {
super.onDestroy() super.onDestroy()
} }
private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) { private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
val notification = val notification =
notificationService.createNotification( notificationService.createNotification(
channelId = getString(R.string.watcher_channel_id), channelId = getString(R.string.watcher_channel_id),
@ -209,28 +209,16 @@ class AutoTunnelService : LifecycleService() {
when (status) { when (status) {
is NetworkStatus.Available -> { is NetworkStatus.Available -> {
Timber.i("Gained Mobile data connection") Timber.i("Gained Mobile data connection")
autoTunnelStateFlow.update { emitMobileDataConnected(true)
it.copy(
isMobileDataConnected = true,
)
}
} }
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
autoTunnelStateFlow.update { emitMobileDataConnected(true)
it.copy(
isMobileDataConnected = true,
)
}
Timber.i("Mobile data capabilities changed") Timber.i("Mobile data capabilities changed")
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
autoTunnelStateFlow.update { emitMobileDataConnected(false)
it.copy(
isMobileDataConnected = false,
)
}
Timber.i("Lost mobile data connection") Timber.i("Lost mobile data connection")
} }
} }
@ -283,6 +271,7 @@ class AutoTunnelService : LifecycleService() {
old.map { it.isActive } != new.map { it.isActive } old.map { it.isActive } != new.map { it.isActive }
}, },
) { settings, tunnels -> ) { settings, tunnels ->
Timber.d("Tunnels or settings changed!")
autoTunnelStateFlow.value.copy( autoTunnelStateFlow.value.copy(
settings = settings, settings = settings,
tunnels = tunnels, tunnels = tunnels,
@ -299,7 +288,7 @@ class AutoTunnelService : LifecycleService() {
Timber.i("Starting vpn state watcher") Timber.i("Starting vpn state watcher")
withContext(ioDispatcher) { withContext(ioDispatcher) {
tunnelService.get().vpnState.distinctUntilChanged { old, new -> tunnelService.get().vpnState.distinctUntilChanged { old, new ->
old.tunnelConfig == new.tunnelConfig && old.status == new.status old.tunnelConfig?.id == new.tunnelConfig?.id
}.collect { state -> }.collect { state ->
autoTunnelStateFlow.update { autoTunnelStateFlow.update {
it.copy(vpnState = state) it.copy(vpnState = state)
@ -367,7 +356,7 @@ class AutoTunnelService : LifecycleService() {
mobileDataJob = null mobileDataJob = null
} }
private fun updateEthernet(connected: Boolean) { private fun emitEthernetConnected(connected: Boolean) {
autoTunnelStateFlow.update { autoTunnelStateFlow.update {
it.copy( it.copy(
isEthernetConnected = connected, isEthernetConnected = connected,
@ -375,7 +364,7 @@ class AutoTunnelService : LifecycleService() {
} }
} }
private fun updateWifi(connected: Boolean) { private fun emitWifiConnected(connected: Boolean) {
autoTunnelStateFlow.update { autoTunnelStateFlow.update {
it.copy( it.copy(
isWifiConnected = connected, isWifiConnected = connected,
@ -383,6 +372,22 @@ class AutoTunnelService : LifecycleService() {
} }
} }
private fun emitWifiSSID(ssid: String) {
autoTunnelStateFlow.update {
it.copy(
currentNetworkSSID = ssid,
)
}
}
private fun emitMobileDataConnected(connected: Boolean) {
autoTunnelStateFlow.update {
it.copy(
isMobileDataConnected = connected,
)
}
}
private suspend fun watchForEthernetConnectivityChanges() { private suspend fun watchForEthernetConnectivityChanges() {
withContext(ioDispatcher) { withContext(ioDispatcher) {
Timber.i("Starting ethernet data watcher") Timber.i("Starting ethernet data watcher")
@ -390,16 +395,16 @@ class AutoTunnelService : LifecycleService() {
when (status) { when (status) {
is NetworkStatus.Available -> { is NetworkStatus.Available -> {
Timber.i("Gained Ethernet connection") Timber.i("Gained Ethernet connection")
updateEthernet(true) emitEthernetConnected(true)
} }
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Ethernet capabilities changed") Timber.i("Ethernet capabilities changed")
updateEthernet(true) emitEthernetConnected(true)
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
updateEthernet(false) emitEthernetConnected(false)
Timber.i("Lost Ethernet connection") Timber.i("Lost Ethernet connection")
} }
} }
@ -414,12 +419,12 @@ class AutoTunnelService : LifecycleService() {
when (status) { when (status) {
is NetworkStatus.Available -> { is NetworkStatus.Available -> {
Timber.i("Gained Wi-Fi connection") Timber.i("Gained Wi-Fi connection")
updateWifi(true) emitWifiConnected(true)
} }
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Wifi capabilities changed") Timber.i("Wifi capabilities changed")
updateWifi(true) emitWifiConnected(true)
val ssid = getWifiSSID(status.networkCapabilities) val ssid = getWifiSSID(status.networkCapabilities)
ssid?.let { name -> ssid?.let { name ->
if (name.contains(Constants.UNREADABLE_SSID)) { if (name.contains(Constants.UNREADABLE_SSID)) {
@ -428,16 +433,12 @@ class AutoTunnelService : LifecycleService() {
Timber.i("Detected valid SSID") Timber.i("Detected valid SSID")
} }
appDataRepository.appState.setCurrentSsid(name) appDataRepository.appState.setCurrentSsid(name)
autoTunnelStateFlow.update { emitWifiSSID(name)
it.copy(
currentNetworkSSID = name,
)
}
} ?: Timber.w("Failed to read ssid") } ?: Timber.w("Failed to read ssid")
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
updateWifi(false) emitWifiConnected(false)
Timber.i("Lost Wi-Fi connection") Timber.i("Lost Wi-Fi connection")
} }
} }
@ -461,17 +462,17 @@ class AutoTunnelService : LifecycleService() {
private suspend fun handleNetworkEventChanges() { private suspend fun handleNetworkEventChanges() {
withContext(ioDispatcher) { withContext(ioDispatcher) {
Timber.i("Starting network event watcher") Timber.i("Starting network event watcher")
autoTunnelStateFlow.collectLatest { watcherState -> autoTunnelStateFlow.collect { watcherState ->
val autoTunnel = "Auto-tunnel watcher" val autoTunnel = "Auto-tunnel watcher"
Timber.d("New watcher state!")
// delay for rapid network state changes and then collect latest // delay for rapid network state changes and then collect latest
delay(Constants.WATCHER_COLLECTION_DELAY) delay(Constants.WATCHER_COLLECTION_DELAY)
val activeTunnel = watcherState.vpnState.tunnelConfig val activeTunnel = watcherState.vpnState.tunnelConfig
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel() val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
val isTunnelDown = tunnelService.get().getState() == TunnelState.DOWN
when { when {
watcherState.isEthernetConditionMet() -> { watcherState.isEthernetConditionMet() -> {
Timber.i("$autoTunnel - tunnel on on ethernet condition met") Timber.i("$autoTunnel - tunnel on on ethernet condition met")
if (watcherState.vpnState.isDown()) { if (isTunnelDown) {
defaultTunnel?.let { defaultTunnel?.let {
tunnelService.get().startTunnel(it) tunnelService.get().startTunnel(it)
} }
@ -483,7 +484,7 @@ class AutoTunnelService : LifecycleService() {
val mobileDataTunnel = getMobileDataTunnel() val mobileDataTunnel = getMobileDataTunnel()
val tunnel = val tunnel =
mobileDataTunnel ?: defaultTunnel mobileDataTunnel ?: defaultTunnel
if (watcherState.vpnState.isDown() || activeTunnel?.isMobileDataTunnel == false) { if (isTunnelDown || activeTunnel?.isMobileDataTunnel == false) {
tunnel?.let { tunnel?.let {
tunnelService.get().startTunnel(it) tunnelService.get().startTunnel(it)
} }
@ -492,7 +493,7 @@ class AutoTunnelService : LifecycleService() {
watcherState.isTunnelOffOnMobileDataConditionMet() -> { watcherState.isTunnelOffOnMobileDataConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off") Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
if (!watcherState.vpnState.isDown()) { if (!isTunnelDown) {
activeTunnel?.let { activeTunnel?.let {
tunnelService.get().stopTunnel(it) tunnelService.get().stopTunnel(it)
} }
@ -502,20 +503,20 @@ class AutoTunnelService : LifecycleService() {
watcherState.isUntrustedWifiConditionMet() -> { watcherState.isUntrustedWifiConditionMet() -> {
Timber.i("Untrusted wifi condition met") Timber.i("Untrusted wifi condition met")
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false || if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
watcherState.vpnState.isDown() isTunnelDown
) { ) {
Timber.i( Timber.i(
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met", "$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
) )
watcherState.getTunnelWithMatchingTunnelNetwork()?.let { watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}") Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
if (watcherState.vpnState.isDown() || activeTunnel?.id != it.id) { if (isTunnelDown || activeTunnel?.id != it.id) {
tunnelService.get().startTunnel(it) tunnelService.get().startTunnel(it)
} }
} ?: suspend { } ?: suspend {
Timber.i("No tunnel associated with this SSID, using defaults") Timber.i("No tunnel associated with this SSID, using defaults")
val default = appDataRepository.getPrimaryOrFirstTunnel() val default = appDataRepository.getPrimaryOrFirstTunnel()
if (default?.name != tunnelService.get().name || watcherState.vpnState.isDown()) { if (default?.name != tunnelService.get().name || isTunnelDown) {
default?.let { default?.let {
tunnelService.get().startTunnel(it) tunnelService.get().startTunnel(it)
} }
@ -528,22 +529,22 @@ class AutoTunnelService : LifecycleService() {
Timber.i( Timber.i(
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off", "$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
) )
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) } if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
} }
watcherState.isTunnelOffOnWifiConditionMet() -> { watcherState.isTunnelOffOnWifiConditionMet() -> {
Timber.i( Timber.i(
"$autoTunnel - tunnel off on wifi condition met, turning vpn off", "$autoTunnel - tunnel off on wifi condition met, turning vpn off",
) )
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) } if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
}
watcherState.isTunnelOffOnNoConnectivityMet() -> {
Timber.i(
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
)
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
} }
// TODO disable for this now
// watcherState.isTunnelOffOnNoConnectivityMet() -> {
// Timber.i(
// "$autoTunnel - tunnel off on no connectivity met, turning vpn off",
// )
// if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
// }
else -> { else -> {
Timber.i("$autoTunnel - no condition met") Timber.i("$autoTunnel - no condition met")

View File

@ -27,12 +27,14 @@ class ServiceManager
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager) companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
private fun <T : Service> startService(cls: Class<T>, background: Boolean) { private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
val intent = Intent(context, cls) runCatching {
if (background) { val intent = Intent(context, cls)
context.startForegroundService(intent) if (background) {
} else { context.startForegroundService(intent)
context.startService(intent) } else {
} context.startService(intent)
}
}.onFailure { Timber.e(it) }
} }
suspend fun startAutoTunnel(background: Boolean) { suspend fun startAutoTunnel(background: Boolean) {

View File

@ -62,7 +62,7 @@ class TunnelBackgroundService : LifecycleService() {
return notificationService.createNotification( return notificationService.createNotification(
getString(R.string.vpn_channel_id), getString(R.string.vpn_channel_id),
getString(R.string.vpn_channel_name), getString(R.string.vpn_channel_name),
getString(R.string.tunnel_start_text), getString(R.string.tunnel_running),
description = "", description = "",
) )
} }

View File

@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.amnezia.awg.backend.Tunnel import org.amnezia.awg.backend.Tunnel
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
@ -52,6 +53,8 @@ constructor(
private var statsJob: Job? = null private var statsJob: Job? = null
private val runningHandle = AtomicBoolean(false)
private suspend fun backend(): Any { private suspend fun backend(): Any {
val settings = appDataRepository.settings.getSettings() val settings = appDataRepository.settings.getSettings()
if (settings.isKernelEnabled) return kernelBackend.get() if (settings.isKernelEnabled) return kernelBackend.get()
@ -91,8 +94,14 @@ constructor(
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> { override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
if (runningHandle.get() == true && tunnelConfig == vpnState.value.tunnelConfig) {
Timber.w("Tunnel already running")
return@withContext Result.success(vpnState.value.status)
}
runningHandle.set(true)
onBeforeStart(tunnelConfig) onBeforeStart(tunnelConfig)
if (background) startBackgroundService() val settings = appDataRepository.settings.getSettings()
if (background || settings.isKernelEnabled) startBackgroundService()
setState(tunnelConfig, TunnelState.UP).onSuccess { setState(tunnelConfig, TunnelState.UP).onSuccess {
emitTunnelState(it) emitTunnelState(it)
}.onFailure { }.onFailure {
@ -112,6 +121,7 @@ constructor(
onStopFailed() onStopFailed()
}.also { }.also {
stopBackgroundService() stopBackgroundService()
runningHandle.set(false)
} }
} }
} }
@ -146,6 +156,7 @@ constructor(
} }
cancelStatsJob() cancelStatsJob()
resetBackendStatistics() resetBackendStatistics()
runningHandle.set(false)
} }
private suspend fun shutDownActiveTunnel(config: TunnelConfig) { private suspend fun shutDownActiveTunnel(config: TunnelConfig) {

View File

@ -39,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@ -60,6 +61,7 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@ -213,7 +215,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
) )
LazyColumn( LazyColumn(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top),
modifier = modifier =
Modifier Modifier
.fillMaxSize().padding(it) .fillMaxSize().padding(it)

View File

@ -9,6 +9,7 @@ import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.R 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.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
@ -29,7 +30,7 @@ class SettingsViewModel
@Inject @Inject
constructor( constructor(
private val appDataRepository: AppDataRepository, private val appDataRepository: AppDataRepository,
private val rootShell: Provider<RootShell>, @AppShell private val rootShell: Provider<RootShell>,
private val fileUtils: FileUtils, private val fileUtils: FileUtils,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher, @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {

View File

@ -6,6 +6,7 @@ import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.R 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.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
@ -23,7 +24,7 @@ class AutoTunnelViewModel
@Inject @Inject
constructor( constructor(
private val appDataRepository: AppDataRepository, private val appDataRepository: AppDataRepository,
private val rootShell: Provider<RootShell>, @AppShell private val rootShell: Provider<RootShell>,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher, @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {

View File

@ -21,7 +21,6 @@ object Constants {
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024 const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
const val SUBSCRIPTION_TIMEOUT = 5_000L const val SUBSCRIPTION_TIMEOUT = 5_000L
const val FOCUS_REQUEST_DELAY = 500L
const val TRANSITION_ANIMATION_TIME = 200 const val TRANSITION_ANIMATION_TIME = 200

View File

@ -4,8 +4,6 @@ import androidx.compose.ui.graphics.Color
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.wireguard.config.Peer import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
@ -23,10 +21,6 @@ fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis) return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
} }
fun VpnState.isDown(): Boolean {
return this.status == TunnelState.DOWN
}
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus { fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
// TODO add never connected status after duration // TODO add never connected status after duration
return this.latestHandshakeSeconds().let { return this.latestHandshakeSeconds().let {

View File

@ -225,4 +225,6 @@
<string name="kernel_not_supported">Kernel not supported</string> <string name="kernel_not_supported">Kernel not supported</string>
<string name="start_auto">Start auto-tunnel</string> <string name="start_auto">Start auto-tunnel</string>
<string name="stop_auto">Stop auto-tunnel</string> <string name="stop_auto">Stop auto-tunnel</string>
<string name="tunnel_running">Tunnel running</string>
<string name="monitoring_state_changes">Monitoring state changes</string>
</resources> </resources>