Compare commits

...

10 Commits

Author SHA1 Message Date
Zane Schepke c153be6f9f more fixes 2025-02-23 17:17:31 -05:00
Zane Schepke b2f8384862 another 2025-02-23 17:14:30 -05:00
Zane Schepke 4415834b64 another 2025-02-23 17:06:21 -05:00
Zane Schepke cbdfef5daf fix: r8 2025-02-23 16:58:19 -05:00
Zane Schepke 9da9e3cc65 try again 2025-02-23 16:24:05 -05:00
Zane Schepke ad379bd9a9 fix: proguard 2025-02-23 16:13:51 -05:00
Zane Schepke 8021c133a5 fix: proguard 2025-02-23 15:58:08 -05:00
Zane Schepke 6009445a15 bump tunnel deps 2025-02-23 15:16:12 -05:00
Zane Schepke f80af9dd5e fix: tunnel state bug 2025-02-22 23:21:25 -05:00
Zane Schepke 3f912ed532 fix: tunnel change restart logic 2025-02-22 13:07:15 -05:00
20 changed files with 164 additions and 88 deletions
+34
View File
@@ -3,3 +3,37 @@
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite { -keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>; <fields>;
} }
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-keep class com.google.api.client.http.** { *; }
-dontwarn com.google.api.client.http.**
# Keep Joda-Time classes used by Tink
-keep class org.joda.time.** { *; }
-dontwarn org.joda.time.**
-keep class org.slf4j.** { *; }
-dontwarn org.slf4j.**
+33
View File
@@ -22,3 +22,36 @@
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite { -keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>; <fields>;
} }
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-keep class com.google.api.client.http.** { *; }
-dontwarn com.google.api.client.http.**
# Keep Joda-Time classes used by Tink
-keep class org.joda.time.** { *; }
-dontwarn org.joda.time.**
-keep class org.slf4j.** { *; }
-dontwarn org.slf4j.**
@@ -67,7 +67,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.LogsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -121,20 +120,14 @@ class MainActivity : AppCompatActivity() {
viewModel.getEmitSplitTunnelApps(this@MainActivity) viewModel.getEmitSplitTunnelApps(this@MainActivity)
} }
LaunchedEffect(appUiState.autoTunnelActive) {
requestAutoTunnelTileServiceUpdate()
}
with(appUiState.appSettings) { with(appUiState.appSettings) {
LaunchedEffect(isAutoTunnelEnabled) {
this@MainActivity.requestAutoTunnelTileServiceUpdate()
}
LaunchedEffect(isShortcutsEnabled) { LaunchedEffect(isShortcutsEnabled) {
if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts() if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts()
shortcutManager.addShortcuts() shortcutManager.addShortcuts()
} }
} }
// TODO could improve this to cancel when no tuns or autotun on
ServiceWorker.start(this) ServiceWorker.start(this)
CompositionLocalProvider(LocalNavController provides navController) { CompositionLocalProvider(LocalNavController provides navController) {
@@ -221,14 +214,14 @@ class MainActivity : AppCompatActivity() {
composable<Route.Logs> { composable<Route.Logs> {
LogsScreen() LogsScreen()
} }
composable<Route.Config> { composable<Route.Config> { backStack ->
val args = it.toRoute<Route.Config>() val args = backStack.toRoute<Route.Config>()
val config = val config =
appUiState.tunnels.firstOrNull { it.id == args.id } appUiState.tunnels.firstOrNull { it.id == args.id }
ConfigScreen(config, viewModel) ConfigScreen(config, viewModel)
} }
composable<Route.TunnelOptions> { composable<Route.TunnelOptions> { backStack ->
val args = it.toRoute<Route.TunnelOptions>() val args = backStack.toRoute<Route.TunnelOptions>()
val config = appUiState.tunnels.first { it.id == args.id } val config = appUiState.tunnels.first { it.id == args.id }
OptionsScreen(config) OptionsScreen(config)
} }
@@ -241,13 +234,13 @@ class MainActivity : AppCompatActivity() {
composable<Route.KillSwitch> { composable<Route.KillSwitch> {
KillSwitchScreen(appUiState, viewModel) KillSwitchScreen(appUiState, viewModel)
} }
composable<Route.SplitTunnel> { composable<Route.SplitTunnel> { backStack ->
val args = it.toRoute<Route.SplitTunnel>() val args = backStack.toRoute<Route.SplitTunnel>()
val config = appUiState.tunnels.first { it.id == args.id } val config = appUiState.tunnels.first { it.id == args.id }
SplitTunnelScreen(config, viewModel) SplitTunnelScreen(config, viewModel)
} }
composable<Route.TunnelAutoTunnel> { composable<Route.TunnelAutoTunnel> { backStack ->
val args = it.toRoute<Route.TunnelOptions>() val args = backStack.toRoute<Route.TunnelOptions>()
val config = appUiState.tunnels.first { it.id == args.id } val config = appUiState.tunnels.first { it.id == args.id }
TunnelAutoTunnelScreen(config, appUiState.appSettings) TunnelAutoTunnelScreen(config, appUiState.appSettings)
} }
@@ -32,8 +32,8 @@ class KernelReceiver : BroadcastReceiver() {
val action = intent.action ?: return val action = intent.action ?: return
applicationScope.launch { applicationScope.launch {
if (action == REFRESH_TUNNELS_ACTION) { if (action == REFRESH_TUNNELS_ACTION) {
tunnelManager.runningTunnelNames().forEach { tunnelManager.runningTunnelNames().forEach { name ->
val tunnel = tunnelRepository.findByTunnelName(it) val tunnel = tunnelRepository.findByTunnelName(name)
tunnel?.let { tunnel?.let {
tunnelRepository.save(it.copy(isActive = true)) tunnelRepository.save(it.copy(isActive = true))
} }
@@ -187,7 +187,7 @@ class AutoTunnelService : LifecycleService() {
buildNetworkState(it) buildNetworkState(it)
}.distinctUntilChanged(), }.distinctUntilChanged(),
) { double, networkState -> ) { double, networkState ->
AutoTunnelState(tunnelManager.activeTunnels().value, networkState, double.first, double.second) AutoTunnelState(tunnelManager.activeTunnels.value, networkState, double.first, double.second)
}.collect { state -> }.collect { state ->
autoTunnelStateFlow.update { autoTunnelStateFlow.update {
it.copy(activeTunnels = state.activeTunnels, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels) it.copy(activeTunnels = state.activeTunnels, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
@@ -52,9 +52,14 @@ class TunnelControlTile : TileService() {
} }
fun updateTileState() = applicationScope.launch { fun updateTileState() = applicationScope.launch {
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable() val tunnels = appDataRepository.tunnels.getAll()
with(tunnelManager.activeTunnels().value) { if (tunnels.isEmpty()) return@launch setUnavailable()
if (isNotEmpty()) return@launch updateTile(if (size == 1) first().tunName else getString(R.string.multiple), true) with(tunnelManager.activeTunnels.value) {
if (isNotEmpty()) if (size == 1) {
tunnels.firstOrNull { it.id == keys.first() }?.let { return@launch updateTile(it.tunName, true) }
} else {
return@launch updateTile(getString(R.string.multiple), true)
}
} }
appDataRepository.getStartTunnelConfig()?.let { appDataRepository.getStartTunnelConfig()?.let {
updateTile(it.tunName, false) updateTile(it.tunName, false)
@@ -65,7 +70,7 @@ class TunnelControlTile : TileService() {
super.onClick() super.onClick()
unlockAndRun { unlockAndRun {
applicationScope.launch { applicationScope.launch {
if (tunnelManager.activeTunnels().value.isNotEmpty()) return@launch tunnelManager.stopTunnel() if (tunnelManager.activeTunnels.value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
appDataRepository.getStartTunnelConfig()?.let { appDataRepository.getStartTunnelConfig()?.let {
tunnelManager.startTunnel(it) tunnelManager.startTunnel(it)
} }
@@ -14,6 +14,7 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
@@ -45,6 +46,8 @@ open class BaseTunnel(
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList()) internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
private val _activeTunnels = MutableStateFlow<Map<Int, TunnelState>>(emptyMap())
private val tunnelJobs = mutableMapOf<TunnelConf, Job>() private val tunnelJobs = mutableMapOf<TunnelConf, Job>()
private val isNetworkAvailable = AtomicBoolean(false) private val isNetworkAvailable = AtomicBoolean(false)
@@ -67,8 +70,9 @@ open class BaseTunnel(
removedItems.forEach { tun -> removedItems.forEach { tun ->
tunnelJobs[tun]?.cancelWithMessage("Canceling tunnel jobs for tunnel: ${tun.name}") tunnelJobs[tun]?.cancelWithMessage("Canceling tunnel jobs for tunnel: ${tun.name}")
tunnelJobs.remove(tun) tunnelJobs.remove(tun)
_activeTunnels.update { it - tun.id }
serviceManager.updateTunnelTile()
} }
serviceManager.updateTunnelTile()
} }
} }
} }
@@ -83,6 +87,9 @@ open class BaseTunnel(
launch { launch {
startTunnelConfigChangeJob(tunnel) startTunnelConfigChangeJob(tunnel)
} }
launch {
startStateJob(tunnel)
}
} }
override suspend fun bounceTunnel(tunnelConf: TunnelConf) { override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
@@ -99,14 +106,13 @@ open class BaseTunnel(
return emptySet() return emptySet()
} }
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> { override val activeTunnels: StateFlow<Map<Int, TunnelState>>
return tunnels.asStateFlow() get() = _activeTunnels.asStateFlow()
}
override suspend fun startTunnel(tunnelConf: TunnelConf) { override suspend fun startTunnel(tunnelConf: TunnelConf) {
if (tunnels.value.any { it.id == tunnelConf.id }) return Timber.w("Tunnel already running")
serviceManager.startBackgroundService(tunnelConf) serviceManager.startBackgroundService(tunnelConf)
appDataRepository.tunnels.save(tunnelConf.copy(isActive = true)) appDataRepository.tunnels.save(tunnelConf.copy(isActive = true))
addToActiveTunnels(tunnelConf)
} }
override suspend fun stopTunnel(tunnelConf: TunnelConf?) { override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
@@ -131,7 +137,7 @@ open class BaseTunnel(
} }
} }
internal fun addToActiveTunnels(conf: TunnelConf) { private fun addToActiveTunnels(conf: TunnelConf) {
tunnels.update { tunnels.update {
it.toMutableList().apply { it.toMutableList().apply {
add(conf) add(conf)
@@ -153,13 +159,22 @@ open class BaseTunnel(
} }
} }
private suspend fun startStateJob(tunnel: TunnelConf) {
tunnel.state.collect { state ->
_activeTunnels.update {
it + (tunnel.id to state)
}
serviceManager.updateTunnelTile()
}
}
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope { private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
while (isActive) { while (isActive) {
if (isNetworkAvailable.get() && tunnel.isActive) { if (isNetworkAvailable.get() && tunnel.isActive) {
val pingResult = tunnel.pingTunnel(ioDispatcher) val pingResult = tunnel.pingTunnel(ioDispatcher)
handlePingResult(tunnel, pingResult) handlePingResult(tunnel, pingResult)
} }
delay(CHECK_INTERVAL) delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
} }
} }
@@ -200,7 +215,7 @@ open class BaseTunnel(
private suspend fun startTunnelConfigChangeJob(tunnel: TunnelConf) = coroutineScope { private suspend fun startTunnelConfigChangeJob(tunnel: TunnelConf) = coroutineScope {
appDataRepository.tunnels.flow.collect { storageTuns -> appDataRepository.tunnels.flow.collect { storageTuns ->
storageTuns.firstOrNull { it.id == tunnel.id }?.let { storageTun -> storageTuns.firstOrNull { it.id == tunnel.id }?.let { storageTun ->
if (tunnel.isQuickConfigChanged(storageTun) || tunnel.isPingConfigMatching(storageTun)) { if (!tunnel.isQuickConfigMatching(storageTun) || !tunnel.isPingConfigMatching(storageTun)) {
bounceTunnel(tunnel) bounceTunnel(tunnel)
} }
} }
@@ -33,10 +33,10 @@ class KernelTunnel @Inject constructor(
override suspend fun startTunnel(tunnelConf: TunnelConf) { override suspend fun startTunnel(tunnelConf: TunnelConf) {
withContext(ioDispatcher) { withContext(ioDispatcher) {
super.startTunnel(tunnelConf) if (tunnels.value.any { it.id == tunnelConf.id }) return@withContext Timber.w("Tunnel already running")
runCatching { runCatching {
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig()) backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
addToActiveTunnels(tunnelConf) super.startTunnel(tunnelConf)
}.onFailure { }.onFailure {
onTunnelStop(tunnelConf) onTunnelStop(tunnelConf)
if (it is BackendException) { if (it is BackendException) {
@@ -66,8 +66,8 @@ class KernelTunnel @Inject constructor(
} }
} }
override suspend fun toggleTunnel(tunnelConf: TunnelConf, status: TunnelStatus) { override suspend fun toggleTunnel(tunnelConf: TunnelConf, state: TunnelStatus) {
when (status) { when (state) {
TunnelStatus.UP -> backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig()) TunnelStatus.UP -> backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
TunnelStatus.DOWN -> backend.setState(tunnelConf, Tunnel.State.DOWN, tunnelConf.toWgConfig()) TunnelStatus.DOWN -> backend.setState(tunnelConf, Tunnel.State.DOWN, tunnelConf.toWgConfig())
} }
@@ -11,10 +11,12 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.extensions.withData import com.zaneschepke.wireguardautotunnel.util.extensions.withData
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -34,14 +36,21 @@ class TunnelManager @Inject constructor(
initialValue = null, initialValue = null,
) )
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> { @OptIn(ExperimentalCoroutinesApi::class)
return withContext(ioDispatcher) { override val activeTunnels = appSettings
appSettings.filterNotNull().first().let { .filterNotNull()
if (it.isKernelEnabled) return@withContext kernelTunnel.activeTunnels() .flatMapLatest { settings ->
userspaceTunnel.activeTunnels() if (settings.isKernelEnabled) {
kernelTunnel.activeTunnels
} else {
userspaceTunnel.activeTunnels
} }
} }
} .stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
initialValue = emptyMap(),
)
override suspend fun startTunnel(tunnelConf: TunnelConf) { override suspend fun startTunnel(tunnelConf: TunnelConf) {
appSettings.withData { appSettings.withData {
@@ -84,14 +93,14 @@ class TunnelManager @Inject constructor(
if (isRestoreOnBootEnabled) { if (isRestoreOnBootEnabled) {
val previouslyActiveTuns = appDataRepository.tunnels.getActive() val previouslyActiveTuns = appDataRepository.tunnels.getActive()
// handle kernel mode // handle kernel mode
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels().value.any { tun.id == it.id } } val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels.value.any { tun.id == it.key } }
if (isKernelEnabled) { if (isKernelEnabled) {
return@withContext tunsToStart.forEach { return@withContext tunsToStart.forEach {
startTunnel(it) startTunnel(it)
} }
} }
// handle userspace // handle userspace
if (activeTunnels().value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) } if (activeTunnels.value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) }
} }
} }
} }
@@ -2,11 +2,12 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface TunnelProvider { interface TunnelProvider {
suspend fun activeTunnels(): StateFlow<List<TunnelConf>> val activeTunnels: StateFlow<Map<Int, TunnelState>>
suspend fun startTunnel(tunnelConf: TunnelConf) suspend fun startTunnel(tunnelConf: TunnelConf)
@@ -34,10 +34,13 @@ class UserspaceTunnel @Inject constructor(
override suspend fun startTunnel(tunnelConf: TunnelConf) { override suspend fun startTunnel(tunnelConf: TunnelConf) {
withContext(ioDispatcher) { withContext(ioDispatcher) {
super.startTunnel(tunnelConf) if (tunnels.value.any { it.id == tunnelConf.id }) return@withContext Timber.w("Tunnel already running")
if (tunnels.value.isNotEmpty()) {
stopAllTunnels()
}
runCatching { runCatching {
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig()) backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
addToActiveTunnels(tunnelConf) super.startTunnel(tunnelConf)
}.onFailure { }.onFailure {
onTunnelStop(tunnelConf) onTunnelStop(tunnelConf)
if (it is BackendException) { if (it is BackendException) {
@@ -53,7 +53,7 @@ class ServiceWorker @AssistedInject constructor(
Timber.i("Service worker started") Timber.i("Service worker started")
with(appDataRepository.settings.get()) { with(appDataRepository.settings.get()) {
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@with serviceManager.startAutoTunnel(true) if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@with serviceManager.startAutoTunnel(true)
if (tunnelManager.activeTunnels().value.isEmpty()) tunnelManager.restorePreviousState() if (tunnelManager.activeTunnels.value.isEmpty()) tunnelManager.restorePreviousState()
} }
Result.success() Result.success()
} }
@@ -62,9 +62,9 @@ data class TunnelConf(
} }
} }
fun isQuickConfigChanged(updatedConf: TunnelConf): Boolean { fun isQuickConfigMatching(updatedConf: TunnelConf): Boolean {
return updatedConf.wgQuick != wgQuick || return updatedConf.wgQuick == wgQuick ||
updatedConf.amQuick != amQuick updatedConf.amQuick == amQuick
} }
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean { fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
@@ -7,7 +7,7 @@ import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
data class AutoTunnelState( data class AutoTunnelState(
val activeTunnels: List<TunnelConf> = emptyList(), val activeTunnels: Map<Int, TunnelState> = emptyMap(),
val networkState: NetworkState = NetworkState(), val networkState: NetworkState = NetworkState(),
val settings: AppSettings = AppSettings(), val settings: AppSettings = AppSettings(),
val tunnels: List<TunnelConf> = emptyList(), val tunnels: List<TunnelConf> = emptyList(),
@@ -20,12 +20,12 @@ data class AutoTunnelState(
private fun isMobileTunnelDataChangeNeeded(): Boolean { private fun isMobileTunnelDataChangeNeeded(): Boolean {
val preferredTunnel = preferredMobileDataTunnel() val preferredTunnel = preferredMobileDataTunnel()
return preferredTunnel != null && return preferredTunnel != null &&
activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id } activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
} }
private fun isEthernetTunnelChangeNeeded(): Boolean { private fun isEthernetTunnelChangeNeeded(): Boolean {
val preferredTunnel = preferredEthernetTunnel() val preferredTunnel = preferredEthernetTunnel()
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id } return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
} }
private fun preferredMobileDataTunnel(): TunnelConf? { private fun preferredMobileDataTunnel(): TunnelConf? {
@@ -62,7 +62,7 @@ data class AutoTunnelState(
return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted()) return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
} }
fun isNoConnectivity(): Boolean { private fun isNoConnectivity(): Boolean {
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
} }
@@ -100,7 +100,7 @@ data class AutoTunnelState(
private fun isWifiTunnelPreferred(): Boolean { private fun isWifiTunnelPreferred(): Boolean {
val preferred = preferredWifiTunnel() val preferred = preferredWifiTunnel()
return activeTunnels.any { it.id == preferred?.id } return activeTunnels.any { it.key == preferred?.id }
} }
fun asAutoTunnelEvent(): AutoTunnelEvent { fun asAutoTunnelEvent(): AutoTunnelEvent {
@@ -77,7 +77,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) } var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
val isRunningOnTv = remember { context.isRunningOnTv() } val isRunningOnTv = remember { context.isRunningOnTv() }
val activeTunnels by viewModel.activeTunnels.collectAsStateWithLifecycle(emptyList()) val activeTunnels by viewModel.tunnelManager.activeTunnels.collectAsStateWithLifecycle(emptyMap())
val collator = Collator.getInstance(Locale.getDefault()) val collator = Collator.getInstance(Locale.getDefault())
@@ -89,6 +89,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
val startTunnel = withVpnPermission<TunnelConf> { val startTunnel = withVpnPermission<TunnelConf> {
viewModel.onTunnelStart(it) viewModel.onTunnelStart(it)
} }
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) { val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown() if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
if (uiState.appSettings.isKernelEnabled) { if (uiState.appSettings.isKernelEnabled) {
@@ -132,15 +133,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
} }
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) { fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) {
if (!checked) { if (!checked) return viewModel.onTunnelStop(tunnel).let { }
viewModel.onTunnelStop(tunnel) if (uiState.appSettings.isKernelEnabled) viewModel.onTunnelStart(tunnel) else startTunnel(tunnel)
return
}
if (uiState.appSettings.isKernelEnabled) {
viewModel.onTunnelStart(tunnel)
} else {
startTunnel.invoke(tunnel)
}
} }
Scaffold( Scaffold(
@@ -233,13 +227,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
key = { tunnel -> tunnel.id }, key = { tunnel -> tunnel.id },
) { tunnel -> ) { tunnel ->
val expanded = uiState.generalState.isTunnelStatsExpanded val expanded = uiState.generalState.isTunnelStatsExpanded
val tunnelState = activeTunnels.firstOrNull { it.id == tunnel.id }?.state?.collectAsStateWithLifecycle() val tunnelState = activeTunnels.getOrDefault(tunnel.id, TunnelState())
TunnelRowItem( TunnelRowItem(
tunnel.isActive, tunnelState.state.isUp(),
expanded, expanded,
selectedTunnel?.id == tunnel.id, selectedTunnel?.id == tunnel.id,
tunnel, tunnel,
tunnelState = tunnelState?.value ?: TunnelState(), tunnelState = tunnelState,
{ selectedTunnel = tunnel }, { selectedTunnel = tunnel },
{ viewModel.onExpandedChanged(!expanded) }, { viewModel.onExpandedChanged(!expanded) },
onDelete = { showDeleteTunnelAlertDialog = true }, onDelete = { showDeleteTunnelAlertDialog = true },
@@ -46,9 +46,9 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
fun Peer.isReachable(preferIpv4: Boolean): Boolean { fun Peer.isReachable(preferIpv4: Boolean): Boolean {
val host = val host =
if (this.endpoint.isPresent && if (this.endpoint.isPresent &&
this.endpoint.get().getResolved(preferIpv4).isPresent this.endpoint.get().resolved.isPresent
) { ) {
this.endpoint.get().getResolved(preferIpv4).get().host this.endpoint.get().resolved.get().host
} else { } else {
Constants.DEFAULT_PING_IP Constants.DEFAULT_PING_IP
} }
@@ -109,8 +109,8 @@ constructor(
} }
private suspend fun initTunnels() { private suspend fun initTunnels() {
tunnels.withData { tunnels.withData { tunnels ->
it.filter { it.isActive }.forEach { tunnels.filter { it.isActive }.forEach {
tunnelManager.startTunnel(it) tunnelManager.startTunnel(it)
} }
} }
@@ -24,8 +24,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import com.zaneschepke.wireguardautotunnel.util.extensions.withData import com.zaneschepke.wireguardautotunnel.util.extensions.withData
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.amnezia.awg.config.Config import org.amnezia.awg.config.Config
@@ -44,15 +42,6 @@ constructor(
appDataRepository: AppDataRepository, appDataRepository: AppDataRepository,
) : BaseViewModel(appDataRepository) { ) : BaseViewModel(appDataRepository) {
private val _activeTunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
val activeTunnels = _activeTunnels.asStateFlow()
init {
viewModelScope.launch {
tunnelManager.activeTunnels().collect(_activeTunnels::emit)
}
}
fun onDelete(tunnel: TunnelConf) = viewModelScope.launch { fun onDelete(tunnel: TunnelConf) = viewModelScope.launch {
appSettings.withData { settings -> appSettings.withData { settings ->
tunnels.withData { tunnels.withData {
+2 -2
View File
@@ -1,7 +1,7 @@
[versions] [versions]
accompanist = "0.37.0" accompanist = "0.37.0"
activityCompose = "1.10.0" activityCompose = "1.10.0"
amneziawgAndroid = "1.2.6" amneziawgAndroid = "1.2.8"
androidx-junit = "1.2.1" androidx-junit = "1.2.1"
appcompat = "1.7.0" appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05" biometricKtx = "1.2.0-alpha05"
@@ -19,7 +19,7 @@ navigationCompose = "2.8.7"
pinLockCompose = "1.0.4" pinLockCompose = "1.0.4"
roomVersion = "2.6.1" roomVersion = "2.6.1"
timber = "5.0.1" timber = "5.0.1"
tunnel = "1.2.2" tunnel = "1.2.4"
androidGradlePlugin = "8.8.0-alpha05" androidGradlePlugin = "8.8.0-alpha05"
kotlin = "2.1.10" kotlin = "2.1.10"
ksp = "2.1.10-1.0.30" ksp = "2.1.10-1.0.30"
+1 -1
View File
@@ -1 +1 @@
1 2