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