fix: auto tunnel overrides
This commit is contained in:
parent
a362327fa3
commit
d330fa4c28
|
@ -4,7 +4,6 @@ data class GeneralState(
|
|||
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||
val lastActiveTunnelId: Int? = null,
|
||||
) {
|
||||
companion object {
|
||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||
|
|
|
@ -2,57 +2,47 @@ package com.zaneschepke.wireguardautotunnel.data.repository
|
|||
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
class DataStoreAppStateRepository(
|
||||
private val dataStoreManager: DataStoreManager,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) :
|
||||
AppStateRepository {
|
||||
override suspend fun isLocationDisclosureShown(): Boolean {
|
||||
return withContext(ioDispatcher) {
|
||||
dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN)
|
||||
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT
|
||||
}
|
||||
return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN)
|
||||
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setLocationDisclosureShown(shown: Boolean) {
|
||||
withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown) }
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown)
|
||||
}
|
||||
|
||||
override suspend fun isPinLockEnabled(): Boolean {
|
||||
return withContext(ioDispatcher) {
|
||||
dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED)
|
||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT
|
||||
}
|
||||
return dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED)
|
||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setPinLockEnabled(enabled: Boolean) {
|
||||
withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled) }
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled)
|
||||
}
|
||||
|
||||
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
||||
return withContext(ioDispatcher) {
|
||||
dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
|
||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
||||
}
|
||||
return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
|
||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setBatteryOptimizationDisableShown(shown: Boolean) {
|
||||
withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, shown) }
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, shown)
|
||||
}
|
||||
|
||||
override suspend fun getCurrentSsid(): String? {
|
||||
return withContext(ioDispatcher) { dataStoreManager.getFromStore(DataStoreManager.CURRENT_SSID) }
|
||||
return dataStoreManager.getFromStore(DataStoreManager.CURRENT_SSID)
|
||||
}
|
||||
|
||||
override suspend fun setCurrentSsid(ssid: String) {
|
||||
withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid) }
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid)
|
||||
}
|
||||
|
||||
override val generalStateFlow: Flow<GeneralState> =
|
||||
|
|
|
@ -72,8 +72,8 @@ class RepositoryModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGeneralStateRepository(dataStoreManager: DataStoreManager, @IoDispatcher ioDispatcher: CoroutineDispatcher): AppStateRepository {
|
||||
return DataStoreAppStateRepository(dataStoreManager, ioDispatcher)
|
||||
fun provideGeneralStateRepository(dataStoreManager: DataStoreManager): AppStateRepository {
|
||||
return DataStoreAppStateRepository(dataStoreManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.wireguard.android.backend.WgQuickBackend
|
|||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||
import dagger.Module
|
||||
|
@ -61,11 +62,13 @@ class TunnelModule {
|
|||
amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||
@Kernel kernelBackend: Provider<Backend>,
|
||||
appDataRepository: AppDataRepository,
|
||||
tunnelConfigRepository: TunnelConfigRepository,
|
||||
@ApplicationScope applicationScope: CoroutineScope,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
): TunnelService {
|
||||
return WireGuardTunnel(
|
||||
amneziaBackend,
|
||||
tunnelConfigRepository,
|
||||
kernelBackend,
|
||||
appDataRepository,
|
||||
applicationScope,
|
||||
|
|
|
@ -33,6 +33,7 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -71,7 +72,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
@MainImmediateDispatcher
|
||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||
|
||||
private val networkEventsFlow = MutableStateFlow(AutoTunnelState())
|
||||
private val autoTunnelStateFlow = MutableStateFlow(AutoTunnelState())
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
|
@ -132,6 +133,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
initWakeLock()
|
||||
}
|
||||
startSettingsJob()
|
||||
startVpnStateJob()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
|
@ -191,6 +193,10 @@ class AutoTunnelService : LifecycleService() {
|
|||
watchForSettingsChanges()
|
||||
}
|
||||
|
||||
private fun startVpnStateJob() = lifecycleScope.launch {
|
||||
watchForVpnStateChanges()
|
||||
}
|
||||
|
||||
private fun startWifiJob() = lifecycleScope.launch {
|
||||
watchForWifiConnectivityChanges()
|
||||
}
|
||||
|
@ -218,7 +224,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Mobile data connection")
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
|
@ -226,7 +232,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
|
@ -235,7 +241,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = false,
|
||||
)
|
||||
|
@ -284,16 +290,8 @@ class AutoTunnelService : LifecycleService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateSettings(settings: Settings) {
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
settings = settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAutoTunnelPause(paused: Boolean) {
|
||||
if (networkEventsFlow.value.settings.isAutoTunnelPaused
|
||||
if (autoTunnelStateFlow.value.settings.isAutoTunnelPaused
|
||||
!= paused
|
||||
) {
|
||||
when (paused) {
|
||||
|
@ -307,19 +305,36 @@ class AutoTunnelService : LifecycleService() {
|
|||
Timber.i("Starting settings watcher")
|
||||
withContext(ioDispatcher) {
|
||||
appDataRepository.settings.getSettingsFlow().combine(
|
||||
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
||||
// ignore isActive changes to allow manual tunnel overrides
|
||||
appDataRepository.tunnels.getTunnelConfigsFlow().distinctUntilChanged { old, new ->
|
||||
old.map { it.isActive } != new.map { it.isActive }
|
||||
},
|
||||
) { settings, tunnels ->
|
||||
val activeTunnel = tunnels.firstOrNull { it.isActive }
|
||||
if (!settings.isPingEnabled) {
|
||||
settings.copy(isPingEnabled = activeTunnel?.isPingEnabled ?: false)
|
||||
} else {
|
||||
settings
|
||||
}
|
||||
autoTunnelStateFlow.value.copy(
|
||||
settings = settings,
|
||||
tunnels = tunnels,
|
||||
)
|
||||
}.collect {
|
||||
Timber.d("Settings change: $it")
|
||||
onAutoTunnelPause(it.isAutoTunnelPaused)
|
||||
updateSettings(it)
|
||||
manageJobsBySettings(it)
|
||||
onAutoTunnelPause(it.settings.isAutoTunnelPaused)
|
||||
manageJobsBySettings(it.settings)
|
||||
autoTunnelStateFlow.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForVpnStateChanges() {
|
||||
Timber.i("Starting vpn state watcher")
|
||||
withContext(ioDispatcher) {
|
||||
tunnelService.get().vpnState.collect { state ->
|
||||
state.tunnelConfig?.let {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (it.isPingEnabled && !settings.isPingEnabled) {
|
||||
pingJob.onNotRunning { pingJob = startPingJob() }
|
||||
}
|
||||
if (!it.isPingEnabled && !settings.isPingEnabled) {
|
||||
cancelAndResetPingJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +390,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
}
|
||||
|
||||
private fun updateEthernet(connected: Boolean) {
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isEthernetConnected = connected,
|
||||
)
|
||||
|
@ -413,7 +428,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Wi-Fi connection")
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = true,
|
||||
)
|
||||
|
@ -422,7 +437,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.i("Wifi capabilities changed")
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = true,
|
||||
)
|
||||
|
@ -435,7 +450,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
Timber.i("Detected valid SSID")
|
||||
}
|
||||
appDataRepository.appState.setCurrentSsid(name)
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
currentNetworkSSID = name,
|
||||
)
|
||||
|
@ -444,7 +459,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
networkEventsFlow.update {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = false,
|
||||
)
|
||||
|
@ -460,10 +475,6 @@ class AutoTunnelService : LifecycleService() {
|
|||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getSsidTunnel(ssid: String): TunnelConfig? {
|
||||
return appDataRepository.tunnels.findByTunnelNetworksName(ssid).firstOrNull()
|
||||
}
|
||||
|
||||
private fun isTunnelDown(): Boolean {
|
||||
return tunnelService.get().vpnState.value.status == TunnelState.DOWN
|
||||
}
|
||||
|
@ -471,7 +482,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
private suspend fun handleNetworkEventChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting network event watcher")
|
||||
networkEventsFlow.collectLatest { watcherState ->
|
||||
autoTunnelStateFlow.collectLatest { watcherState ->
|
||||
val autoTunnel = "Auto-tunnel watcher"
|
||||
if (!watcherState.settings.isAutoTunnelPaused) {
|
||||
// delay for rapid network state changes and then collect latest
|
||||
|
@ -517,7 +528,7 @@ class AutoTunnelService : LifecycleService() {
|
|||
Timber.i(
|
||||
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
||||
)
|
||||
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
|
||||
watcherState.tunnels.firstOrNull { it.tunnelNetworks.isMatchingToWildcardList(watcherState.currentNetworkSSID) }?.let {
|
||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||
if (isTunnelDown() || activeTunnel?.id != it.id) {
|
||||
tunnelService.get().startTunnel(it)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||
|
||||
data class AutoTunnelState(
|
||||
|
@ -9,6 +10,7 @@ data class AutoTunnelState(
|
|||
val isMobileDataConnected: Boolean = false,
|
||||
val currentNetworkSSID: String = "",
|
||||
val settings: Settings = Settings(),
|
||||
val tunnels: TunnelConfigs = emptyList(),
|
||||
) {
|
||||
fun isEthernetConditionMet(): Boolean {
|
||||
return (
|
||||
|
|
|
@ -2,24 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
|
|||
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.Tunnel.State
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
|
@ -31,6 +31,7 @@ class WireGuardTunnel
|
|||
@Inject
|
||||
constructor(
|
||||
private val amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||
tunnelConfigRepository: TunnelConfigRepository,
|
||||
@Kernel private val kernelBackend: Provider<Backend>,
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
|
@ -38,7 +39,22 @@ constructor(
|
|||
) : TunnelService {
|
||||
|
||||
private val _vpnState = MutableStateFlow(VpnState())
|
||||
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
||||
override val vpnState: StateFlow<VpnState> = _vpnState.combine(
|
||||
tunnelConfigRepository.getTunnelConfigsFlow(),
|
||||
) {
|
||||
vpnState, tunnels ->
|
||||
vpnState.copy(
|
||||
tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id },
|
||||
)
|
||||
}.stateIn(applicationScope, SharingStarted.Lazily, VpnState())
|
||||
|
||||
private var statsJob: Job? = null
|
||||
|
||||
private suspend fun backend(): Any {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isKernelEnabled) return kernelBackend.get()
|
||||
return amneziaBackend.get()
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
return when (val backend = backend()) {
|
||||
|
@ -48,8 +64,6 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private var statsJob: Job? = null
|
||||
|
||||
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
||||
return runCatching {
|
||||
when (val backend = backend()) {
|
||||
|
@ -73,33 +87,26 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun backend(): Any {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isKernelEnabled) return kernelBackend.get()
|
||||
return amneziaBackend.get()
|
||||
}
|
||||
|
||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
return withContext(ioDispatcher) {
|
||||
if (_vpnState.value.status == TunnelState.UP) vpnState.value.tunnelConfig?.let { stopTunnel(it) }
|
||||
emitTunnelConfig(tunnelConfig)
|
||||
onBeforeStart(tunnelConfig)
|
||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||
emitTunnelState(it)
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
onStartFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
return withContext(ioDispatcher) {
|
||||
onBeforeStop(tunnelConfig)
|
||||
setState(tunnelConfig, TunnelState.DOWN).onSuccess {
|
||||
emitTunnelState(it)
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||
resetBackendStatistics()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
onStopFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +114,7 @@ constructor(
|
|||
// use this when we just want to bounce tunnel and not change tunnelConfig active state
|
||||
override suspend fun bounceTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
toggleTunnel(tunnelConfig)
|
||||
delay(Constants.VPN_RESTART_DELAY)
|
||||
delay(VPN_RESTART_DELAY)
|
||||
return toggleTunnel(tunnelConfig)
|
||||
}
|
||||
|
||||
|
@ -122,6 +129,34 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun onStopFailed() {
|
||||
_vpnState.value.tunnelConfig?.let {
|
||||
appDataRepository.tunnels.save(it.copy(isActive = true))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onStartFailed() {
|
||||
_vpnState.value.tunnelConfig?.let {
|
||||
appDataRepository.tunnels.save(it.copy(isActive = false))
|
||||
}
|
||||
cancelStatsJob()
|
||||
resetBackendStatistics()
|
||||
}
|
||||
|
||||
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) {
|
||||
if (_vpnState.value.status == TunnelState.UP) vpnState.value.tunnelConfig?.let { stopTunnel(it) }
|
||||
resetBackendStatistics()
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||
emitVpnStateConfig(tunnelConfig)
|
||||
startStatsJob()
|
||||
}
|
||||
|
||||
private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) {
|
||||
cancelStatsJob()
|
||||
resetBackendStatistics()
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||
}
|
||||
|
||||
private fun emitTunnelState(state: TunnelState) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
|
@ -138,7 +173,7 @@ constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun emitTunnelConfig(tunnelConfig: TunnelConfig?) {
|
||||
private fun emitVpnStateConfig(tunnelConfig: TunnelConfig) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
tunnelConfig = tunnelConfig,
|
||||
|
@ -174,21 +209,9 @@ constructor(
|
|||
return _vpnState.value.tunnelConfig?.name ?: ""
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
handleStateChange(TunnelState.from(newState))
|
||||
}
|
||||
|
||||
private fun handleStateChange(state: TunnelState) {
|
||||
emitTunnelState(state)
|
||||
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
||||
when (state) {
|
||||
TunnelState.UP -> startStatsJob()
|
||||
else -> cancelStatsJob()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) {
|
||||
val backend = backend()
|
||||
delay(STATS_START_DELAY)
|
||||
while (true) {
|
||||
when (backend) {
|
||||
is Backend -> emitBackendStatistics(
|
||||
|
@ -202,11 +225,21 @@ constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
||||
delay(VPN_STATISTIC_CHECK_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
emitTunnelState(TunnelState.from(newState))
|
||||
}
|
||||
|
||||
override fun onStateChange(state: State) {
|
||||
handleStateChange(TunnelState.from(state))
|
||||
emitTunnelState(TunnelState.from(state))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STATS_START_DELAY = 5_000L
|
||||
const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L
|
||||
const val VPN_RESTART_DELAY = 1_000L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ fun ConfigScreen(tunnelId: Int, viewModel: ConfigViewModel, focusRequester: Focu
|
|||
)
|
||||
}
|
||||
|
||||
if(showApplicationsDialog) {
|
||||
if (showApplicationsDialog) {
|
||||
ApplicationSelectionDialog(viewModel, uiState) {
|
||||
showApplicationsDialog = false
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -96,7 +95,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
val haptic = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
val snackbar = SnackbarController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||
|
@ -328,20 +326,22 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
uiState.tunnels,
|
||||
key = { tunnel -> tunnel.id },
|
||||
) { tunnel ->
|
||||
val isActive = uiState.tunnels.any { it.id == tunnel.id && it.isActive }
|
||||
val isActive = uiState.tunnels.any {
|
||||
it.id == tunnel.id &&
|
||||
it.isActive
|
||||
}
|
||||
val leadingIconColor =
|
||||
(
|
||||
if (
|
||||
isActive
|
||||
isActive && uiState.vpnState.statistics != null
|
||||
) {
|
||||
uiState.vpnState.statistics
|
||||
?.mapPeerStats()
|
||||
?.map { it.value?.handshakeStatus() }
|
||||
uiState.vpnState.statistics.mapPeerStats()
|
||||
.map { it.value?.handshakeStatus() }
|
||||
.let { statuses ->
|
||||
when {
|
||||
statuses?.all { it == HandshakeStatus.HEALTHY } == true -> mint
|
||||
statuses?.any { it == HandshakeStatus.STALE } == true -> corn
|
||||
statuses?.all { it == HandshakeStatus.NOT_STARTED } == true ->
|
||||
statuses.all { it == HandshakeStatus.HEALTHY } -> mint
|
||||
statuses.any { it == HandshakeStatus.STALE } -> corn
|
||||
statuses.all { it == HandshakeStatus.NOT_STARTED } ->
|
||||
Color.Gray
|
||||
|
||||
else -> {
|
||||
|
@ -381,15 +381,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
},
|
||||
text = tunnel.name,
|
||||
onHold = {
|
||||
if (
|
||||
(uiState.vpnState.status == TunnelState.UP) &&
|
||||
(tunnel.name == uiState.vpnState.tunnelConfig?.name)
|
||||
) {
|
||||
snackbar.showMessage(
|
||||
context.getString(R.string.turn_off_tunnel),
|
||||
)
|
||||
return@RowListItem
|
||||
}
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
selectedTunnel = tunnel
|
||||
},
|
||||
|
@ -416,18 +407,9 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (
|
||||
uiState.settings.isAutoTunnelEnabled &&
|
||||
!uiState.settings.isAutoTunnelPaused
|
||||
) {
|
||||
snackbar.showMessage(
|
||||
context.getString(R.string.turn_off_tunnel),
|
||||
)
|
||||
} else {
|
||||
navController.navigate(
|
||||
"${Screen.Option.route}/${selectedTunnel?.id}",
|
||||
)
|
||||
}
|
||||
navController.navigate(
|
||||
"${Screen.Option.route}/${selectedTunnel?.id}",
|
||||
)
|
||||
},
|
||||
) {
|
||||
val icon = Icons.Rounded.Settings
|
||||
|
@ -444,6 +426,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
Icon(icon, icon.name)
|
||||
}
|
||||
IconButton(
|
||||
enabled = !isActive,
|
||||
modifier = Modifier.focusable(),
|
||||
onClick = { showDeleteTunnelAlertDialog = true },
|
||||
) {
|
||||
|
@ -468,16 +451,10 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
|||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (uiState.settings.isAutoTunnelEnabled && !uiState.settings.isAutoTunnelPaused) {
|
||||
snackbar.showMessage(
|
||||
context.getString(R.string.turn_off_auto),
|
||||
)
|
||||
} else {
|
||||
selectedTunnel = tunnel
|
||||
navController.navigate(
|
||||
"${Screen.Option.route}/${selectedTunnel?.id}",
|
||||
)
|
||||
}
|
||||
selectedTunnel = tunnel
|
||||
navController.navigate(
|
||||
"${Screen.Option.route}/${selectedTunnel?.id}",
|
||||
)
|
||||
},
|
||||
) {
|
||||
val icon = Icons.Rounded.Settings
|
||||
|
|
|
@ -56,9 +56,11 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
|||
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.WildcardSupportingLabel
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
|
@ -246,6 +248,7 @@ fun OptionsScreen(
|
|||
value = currentText,
|
||||
onValueChange = { currentText = it },
|
||||
label = { Text(stringResource(id = R.string.use_tunnel_on_wifi_name)) },
|
||||
supportingText = { WildcardSupportingLabel { context.openWebUrl(it) } },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
|
|
|
@ -314,7 +314,20 @@ fun SettingsScreen(
|
|||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||
checked = uiState.settings.isTunnelOnWifiEnabled,
|
||||
padding = screenPadding,
|
||||
onCheckChanged = { viewModel.onToggleTunnelOnWifi() },
|
||||
onCheckChanged = { checked ->
|
||||
if (!checked) viewModel.onToggleTunnelOnWifi()
|
||||
if (checked) {
|
||||
when (false) {
|
||||
isBackgroundLocationGranted -> showLocationDialog = true
|
||||
fineLocationState.status.isGranted -> showLocationDialog = true
|
||||
viewModel.isLocationEnabled(context) ->
|
||||
showLocationServicesAlertDialog = true
|
||||
else -> {
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier =
|
||||
if (uiState.settings.isAutoTunnelEnabled) {
|
||||
Modifier
|
||||
|
@ -442,23 +455,7 @@ fun SettingsScreen(
|
|||
TextButton(
|
||||
onClick = {
|
||||
if (uiState.tunnels.isEmpty()) return@TextButton context.showToast(R.string.tunnel_required)
|
||||
if (
|
||||
uiState.settings.isTunnelOnWifiEnabled &&
|
||||
!uiState.settings.isAutoTunnelEnabled
|
||||
) {
|
||||
when (false) {
|
||||
isBackgroundLocationGranted -> showLocationDialog = true
|
||||
fineLocationState.status.isGranted -> showLocationDialog = true
|
||||
viewModel.isLocationEnabled(context) ->
|
||||
showLocationServicesAlertDialog = true
|
||||
|
||||
else -> {
|
||||
handleAutoTunnelToggle()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleAutoTunnelToggle()
|
||||
}
|
||||
handleAutoTunnelToggle()
|
||||
},
|
||||
) {
|
||||
val autoTunnelButtonText =
|
||||
|
|
|
@ -6,8 +6,8 @@ object Constants {
|
|||
|
||||
const val MANUAL_TUNNEL_CONFIG_ID = "0"
|
||||
const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes
|
||||
const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L
|
||||
const val WATCHER_COLLECTION_DELAY = 3_000L
|
||||
|
||||
const val CONF_FILE_EXTENSION = ".conf"
|
||||
const val ZIP_FILE_EXTENSION = ".zip"
|
||||
const val URI_CONTENT_SCHEME = "content"
|
||||
|
@ -27,12 +27,11 @@ object Constants {
|
|||
|
||||
const val DEFAULT_PING_IP = "1.1.1.1"
|
||||
const val PING_TIMEOUT = 5_000L
|
||||
const val VPN_RESTART_DELAY = 1_000L
|
||||
const val PING_INTERVAL = 60_000L
|
||||
const val PING_COOLDOWN = PING_INTERVAL * 60 // one hour
|
||||
|
||||
const val UNREADABLE_SSID = "<unknown ssid>"
|
||||
|
||||
val amneziaProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4")
|
||||
val amProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4")
|
||||
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ fun Config.toWgQuickString(): String {
|
|||
val linesIterator = lines.iterator()
|
||||
while (linesIterator.hasNext()) {
|
||||
val next = linesIterator.next()
|
||||
Constants.amneziaProperties.forEach {
|
||||
Constants.amProperties.forEach {
|
||||
if (next.startsWith(it, ignoreCase = true)) {
|
||||
linesIterator.remove()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue