From e5796d641d163000bd9da88b99a62a438e8bc285 Mon Sep 17 00:00:00 2001 From: zaneschepke Date: Sat, 20 Jun 2026 12:33:25 -0400 Subject: [PATCH] fix: auto tunnel rapid toggle bug Improve notification efficiency #1288 --- .../service/autotunnel/AutoTunnelService.kt | 8 ++++++-- .../com/zaneschepke/tunnel/service/Extensions.kt | 13 +++++++++++++ .../com/zaneschepke/tunnel/service/TunnelService.kt | 7 +++---- .../tunnel/service/VpnCompanionService.kt | 6 +++--- 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tunnel/src/main/java/com/zaneschepke/tunnel/service/Extensions.kt diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/autotunnel/AutoTunnelService.kt index d80b3b40..db711661 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/autotunnel/AutoTunnelService.kt @@ -27,13 +27,14 @@ import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.extensions.to import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -77,13 +78,16 @@ class AutoTunnelService : LifecycleService() { @Volatile private var hasUserOverride = false private var lastNetworkFingerprint: AutoTunnelState.NetworkFingerprint? = null + @OptIn(FlowPreview::class) private val autoTunnelStateFlow: Flow by lazy { val networkFlow = networkEngine.stableState.mapNotNull { it?.state?.toDomain() } val settingsFlow = combineSettings() val backendFlow = - tunnelCoordinator.backendStatus.distinctUntilChangedBy { it.activeTunnels.keys.toSet() } + tunnelCoordinator.backendStatus + .distinctUntilChanged { old, new -> old.activeTunnels == new.activeTunnels } + .debounce(300L.milliseconds) combine(networkFlow, settingsFlow, backendFlow) { network, settings, backend -> AutoTunnelState( diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/Extensions.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/Extensions.kt new file mode 100644 index 00000000..ed94d744 --- /dev/null +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/Extensions.kt @@ -0,0 +1,13 @@ +package com.zaneschepke.tunnel.service + +import com.zaneschepke.tunnel.model.BackendMode +import com.zaneschepke.tunnel.state.BackendStatus + +fun BackendStatus.toNotificationComparisonKey(): Any = + activeTunnels.mapValues { (_, tunnel) -> + Triple( + tunnel.transportState, + tunnel.bootstrapState, + tunnel.mode is BackendMode.Vpn || tunnel.mode is BackendMode.Proxy.KillSwitchPrimary, + ) + } to (activeTunnels.keys to (killSwitch.enabled to dnsMode)) diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt index 92d8c5a5..f9a967a7 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.launch import org.koin.java.KoinJavaComponent.inject import timber.log.Timber @@ -61,12 +61,11 @@ class TunnelService : LifecycleService() { private fun observeProxyPersistentNotification() { lifecycleScope.launch { backend.status - .distinctUntilChanged { old, new -> old.activeTunnels == new.activeTunnels } - .debounce(1_000.milliseconds) + .distinctUntilChangedBy { it.toNotificationComparisonKey() } + .debounce(700.milliseconds) .collect { status -> val notification = backend.applicationProvider.buildProxyPersistentNotification(status) - notificationManager.notify( backend.applicationProvider.proxyNotificationId, notification, diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnCompanionService.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnCompanionService.kt index 331aa774..944f3cc8 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnCompanionService.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnCompanionService.kt @@ -10,7 +10,7 @@ import com.zaneschepke.tunnel.backend.Backend import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import timber.log.Timber @@ -43,8 +43,8 @@ class VpnCompanionService : LifecycleService() { private fun observeVpnPersistentNotification() { lifecycleScope.launch { backend.status - .distinctUntilChanged { old, new -> old.activeTunnels == new.activeTunnels } - .debounce(1000.milliseconds) + .distinctUntilChangedBy { it.toNotificationComparisonKey() } + .debounce(700.milliseconds) .collect { status -> val notification = backend.applicationProvider.buildVpnPersistentNotification(status)