fix: notifications not update, add new notification channels

This commit is contained in:
zaneschepke
2026-06-04 04:18:38 -04:00
parent cbc582df53
commit d80ea167f4
16 changed files with 293 additions and 197 deletions
@@ -42,6 +42,10 @@ class NotificationActionReceiver : BroadcastReceiver(), KoinComponent {
}
tunnelCoordinator.stopTunnel(tunnelId)
}
NotificationAction.STOP_ALL.name -> {
tunnelCoordinator.stopActiveTunnels()
}
}
}
}
@@ -5,8 +5,6 @@ import com.zaneschepke.tunnel.util.BackendException
sealed interface TunnelErrorEvent {
data class VpnPermissionDenied(val tunnelId: Int) : TunnelErrorEvent
data class StateConflict(val tunnelId: Int, val message: String) : TunnelErrorEvent
data class InternalFailure(val tunnelId: Int, val message: String) : TunnelErrorEvent
data class Socks5PortUnavailable(val tunnelId: Int, val port: Int) : TunnelErrorEvent
@@ -16,9 +14,6 @@ sealed interface TunnelErrorEvent {
companion object {
fun from(throwable: Throwable, id: Int): TunnelErrorEvent {
return when (throwable) {
is BackendException.StateConflict -> {
StateConflict(id, throwable.message)
}
is BackendException.Unauthorized -> {
VpnPermissionDenied(id)
}
@@ -1,18 +1,28 @@
package com.zaneschepke.wireguardautotunnel.core.event
import android.content.Context
import com.zaneschepke.tunnel.event.TunnelEvent
import com.zaneschepke.tunnel.model.BackendMode
import com.zaneschepke.tunnel.state.BackendStatus
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.notification.TunnelNotificationLine
import com.zaneschepke.wireguardautotunnel.core.notification.TunnelNotificationService
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.ui.state.DisplayTunnelState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
class TunnelEventDispatcher(private val notificationManager: TunnelNotificationService) {
class TunnelEventDispatcher(
private val notificationManager: TunnelNotificationService,
private val tunnelRepository: TunnelRepository,
private val context: Context,
) {
fun bind(
scope: CoroutineScope,
@@ -27,15 +37,18 @@ class TunnelEventDispatcher(private val notificationManager: TunnelNotificationS
.onEach { event ->
when (event) {
is TunnelEvent.FallbackToIpv4 -> {
notificationManager.showIpv4Fallback(event.tunnelId)
val name = getTunnelName(event.tunnelId)
notificationManager.showIpv4Fallback(name)
}
is TunnelEvent.RecoveredToIpv6 -> {
notificationManager.showIpv6Recovery(event.tunnelId)
val name = getTunnelName(event.tunnelId)
notificationManager.showIpv6Recovery(name)
}
is TunnelEvent.DynamicDnsUpdate -> {
notificationManager.showDynamicDnsUpdate(event.tunnelId)
val name = getTunnelName(event.tunnelId)
notificationManager.showDynamicDnsUpdate(name)
}
is TunnelEvent.NoRootShellAccess -> {
@@ -54,36 +67,85 @@ class TunnelEventDispatcher(private val notificationManager: TunnelNotificationS
notificationManager.showVpnRequired()
}
is TunnelErrorEvent.StateConflict -> {
notificationManager.showStateConflict(error.tunnelId)
}
is TunnelErrorEvent.InternalFailure -> {
notificationManager.showError(error.message)
}
is TunnelErrorEvent.Socks5PortUnavailable -> {
notificationManager.showSocks5PortUnavailable(error.port)
val name = getTunnelName(error.tunnelId)
notificationManager.showSocks5PortUnavailable(error.port, name)
}
is TunnelErrorEvent.HttpPortUnavailable -> {
notificationManager.showHttpPortUnavailable(error.port)
val name = getTunnelName(error.tunnelId)
notificationManager.showHttpPortUnavailable(error.port, name)
}
}
}
.launchIn(scope)
// update persistent notification for services with the tunnel states
providerStatus
.map { it.activeTunnels }
.distinctUntilChangedBy { map ->
val stateSignature =
map.entries
.sortedBy { it.key }
.map { (_, tunnel) -> tunnel.transportState to tunnel.bootstrapState }
map.size to stateSignature
// vpn
combine(providerStatus.map { it.activeTunnels }, tunnelRepository.userTunnelsFlow) {
activeTunnels,
allTunnels ->
activeTunnels
.mapNotNull { (id, activeTunnel) ->
val mode = activeTunnel.mode ?: return@mapNotNull null
// Only include VPN / KillSwitchPrimary modes
if (
mode !is BackendMode.Vpn && mode !is BackendMode.Proxy.KillSwitchPrimary
) {
return@mapNotNull null
}
val tunnel = allTunnels.find { it.id == id } ?: return@mapNotNull null
val displayState = DisplayTunnelState.from(activeTunnel)
TunnelNotificationLine(
id = id,
name = tunnel.name,
displayState = displayState,
)
}
.associateBy { it.id }
}
.distinctUntilChanged()
.onEach { vpnLines -> notificationManager.updateVpnPersistentNotification(vpnLines) }
.launchIn(scope)
// proxy
combine(providerStatus.map { it.activeTunnels }, tunnelRepository.userTunnelsFlow) {
activeTunnels,
allTunnels ->
activeTunnels
.mapNotNull { (id, activeTunnel) ->
val mode = activeTunnel.mode ?: return@mapNotNull null
// Only include Standard Proxy mode
if (mode !is BackendMode.Proxy.Standard) {
return@mapNotNull null
}
val tunnel = allTunnels.find { it.id == id } ?: return@mapNotNull null
val displayState = DisplayTunnelState.from(activeTunnel)
TunnelNotificationLine(
id = id,
name = tunnel.name,
displayState = displayState,
)
}
.associateBy { it.id }
}
.distinctUntilChanged()
.onEach { proxyLines ->
notificationManager.updateProxyPersistentNotification(proxyLines)
}
.onEach { status -> notificationManager.updatePersistentNotifications(status) }
.launchIn(scope)
}
private suspend fun getTunnelName(tunnelId: Int): String {
return tunnelRepository.getById(tunnelId)?.name ?: context.getString(R.string.unknown)
}
}
@@ -13,6 +13,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.Action
import androidx.core.app.NotificationCompat.Builder
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH
import com.zaneschepke.wireguardautotunnel.MainActivity
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.broadcast.NotificationActionReceiver
@@ -35,6 +36,7 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
onlyAlertOnce: Boolean,
groupKey: String?,
isGroupSummary: Boolean,
style: NotificationCompat.Style?,
): Notification {
notificationManager.createNotificationChannel(channel.asChannel())
return channel
@@ -63,6 +65,7 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
setGroupSummary(true)
}
}
style?.let { setStyle(it) }
}
.build()
}
@@ -78,6 +81,7 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
onlyAlertOnce: Boolean,
groupKey: String?,
isGroupSummary: Boolean,
style: NotificationCompat.Style?,
): Notification {
return createNotification(
channel,
@@ -90,6 +94,7 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
onlyAlertOnce,
groupKey,
isGroupSummary,
style,
)
}
@@ -107,7 +112,7 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
return NotificationCompat.Action.Builder(
return Action.Builder(
R.drawable.ic_notification,
notificationAction.title(context),
pendingIntent,
@@ -133,50 +138,88 @@ class AndroidNotificationService(override val context: Context) : NotificationSe
}
}
private fun NotificationChannels.asBuilder(): NotificationCompat.Builder {
private fun NotificationChannels.asBuilder(): Builder {
return when (this) {
NotificationChannels.AUTO_TUNNEL ->
Builder(context, context.getString(R.string.auto_tunnel_channel_id))
NotificationChannels.VPN -> Builder(context, context.getString(R.string.vpn_channel_id))
NotificationChannels.PROXY ->
is NotificationChannels.Tunnel.VPN ->
Builder(context, context.getString(R.string.vpn_channel_id))
is NotificationChannels.Tunnel.Proxy ->
Builder(context, context.getString(R.string.proxy_channel_id))
NotificationChannels.AutoTunnel ->
Builder(context, context.getString(R.string.auto_tunnel_channel_id))
NotificationChannels.App -> Builder(context, context.getString(R.string.app_channel_id))
NotificationChannels.Errors ->
Builder(context, context.getString(R.string.errors_channel_id))
NotificationChannels.Events ->
Builder(context, context.getString(R.string.events_channel_id))
}
}
enum class NotificationChannels(val channelId: Int, val importance: Int) {
VPN(R.string.vpn_channel_id, IMPORTANCE_LOW),
AUTO_TUNNEL(R.string.auto_tunnel_channel_id, IMPORTANCE_LOW),
PROXY(R.string.proxy_channel_id, IMPORTANCE_LOW),
sealed class NotificationChannels(val channelId: Int, val importance: Int) {
sealed class Tunnel(channelId: Int, importance: Int) :
NotificationChannels(channelId, importance) {
data object VPN :
Tunnel(channelId = R.string.vpn_channel_id, importance = IMPORTANCE_LOW)
data object Proxy :
Tunnel(channelId = R.string.proxy_channel_id, importance = IMPORTANCE_LOW)
}
data object AutoTunnel :
NotificationChannels(
channelId = R.string.auto_tunnel_channel_id,
importance = IMPORTANCE_LOW,
)
data object Events :
NotificationChannels(
channelId = R.string.events_channel_id,
importance = IMPORTANCE_LOW,
)
data object Errors :
NotificationChannels(
channelId = R.string.errors_channel_id,
importance = IMPORTANCE_HIGH,
)
data object App :
NotificationChannels(channelId = R.string.app_channel_id, importance = IMPORTANCE_LOW)
companion object {
val all: List<NotificationChannels> =
listOf(Errors, Events, App, Tunnel.VPN, Tunnel.Proxy, AutoTunnel)
}
}
fun NotificationChannels.asChannel(): NotificationChannel {
val (nameResId, descriptionResId) =
when (this) {
is NotificationChannels.Tunnel.VPN ->
R.string.vpn to R.string.vpn_channel_description
is NotificationChannels.Tunnel.Proxy ->
R.string.proxy to R.string.proxy_channel_description
NotificationChannels.AutoTunnel ->
R.string.auto_tunnel to R.string.auto_tunnel_channel_description
NotificationChannels.Errors ->
R.string.errors to R.string.errors_channel_description
NotificationChannels.Events ->
R.string.events to R.string.events_channel_description
NotificationChannels.App -> R.string.app to R.string.app_channel_description
}
return NotificationChannel(
context.getString(channelId),
context.getString(
when (this) {
NotificationChannels.VPN -> R.string.vpn
NotificationChannels.AUTO_TUNNEL -> R.string.auto_tunnel
NotificationChannels.PROXY -> R.string.proxy
}
),
context.getString(nameResId),
importance,
)
.apply {
description =
context.getString(
when (this@asChannel) {
NotificationChannels.VPN -> R.string.vpn_channel_description
NotificationChannels.AUTO_TUNNEL ->
R.string.auto_tunnel_channel_description
NotificationChannels.PROXY -> R.string.proxy_channel_description
}
)
}
.apply { description = context.getString(descriptionResId) }
}
override fun createAllChannels() {
NotificationChannels.entries.forEach { channel ->
NotificationChannels.all.forEach { channel ->
notificationManager.createNotificationChannel(channel.asChannel())
}
}
@@ -1,7 +1,6 @@
package com.zaneschepke.wireguardautotunnel.core.notification
import com.zaneschepke.tunnel.model.BackendMode
import com.zaneschepke.tunnel.state.ActiveTunnel
import androidx.core.app.NotificationCompat
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.notification.AndroidNotificationService.NotificationChannels
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationService.Companion.PROXY_GROUP_KEY
@@ -11,164 +10,154 @@ import com.zaneschepke.wireguardautotunnel.core.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationService.Companion.VPN_GROUP_KEY
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.ui.state.DisplayTunnelState
class AndroidTunnelNotificationService(
private val notificationService: NotificationService,
private val tunnelRepository: TunnelRepository,
) : TunnelNotificationService {
class AndroidTunnelNotificationService(private val notificationService: NotificationService) :
TunnelNotificationService {
override suspend fun updatePersistentNotifications(activeTunnels: Map<Int, ActiveTunnel>) {
private val context = notificationService.context
val vpnTunnels = activeTunnels.filterValues { it.mode is BackendMode.Vpn }
val proxyTunnels = activeTunnels.filterValues { it.mode is BackendMode.Proxy }
updateGroupNotification(
tunnels = vpnTunnels,
notificationId = VPN_NOTIFICATION_ID,
channel = NotificationChannels.VPN,
groupKey = VPN_GROUP_KEY,
)
updateGroupNotification(
tunnels = proxyTunnels,
notificationId = PROXY_NOTIFICATION_ID,
channel = NotificationChannels.PROXY,
groupKey = PROXY_GROUP_KEY,
)
}
private suspend fun updateGroupNotification(
tunnels: Map<Int, ActiveTunnel>,
private fun updateGroupNotification(
tunnelNotificationLines: Map<Int, TunnelNotificationLine>,
notificationId: Int,
channel: NotificationChannels,
channel: NotificationChannels.Tunnel,
groupKey: String,
) {
if (tunnels.isEmpty()) {
if (tunnelNotificationLines.isEmpty()) {
notificationService.remove(notificationId)
return
}
val context = notificationService.context
val lines = tunnels.mapNotNull { (id, activeTunnel) ->
val tunnel = tunnelRepository.getById(id) ?: return@mapNotNull null
val display = DisplayTunnelState.from(activeTunnel)
val formattedLines = tunnelNotificationLines.values.map { line ->
val status = line.displayState.asLocalizedString(context)
context.getString(
R.string.notification_tunnel_status_format,
tunnel.name,
display.asLocalizedString(context),
line.name,
status,
)
}
val description = lines.joinToString("\n")
val description = formattedLines.joinToString("\n")
val stopActions =
tunnels.keys.map {
val actions = if (tunnelNotificationLines.size == 1) {
val tunnelId = tunnelNotificationLines.keys.first()
listOf(
notificationService.createNotificationAction(
notificationAction = NotificationAction.TUNNEL_OFF,
extraId = it,
extraId = tunnelId,
)
}
val title =
when (channel) {
NotificationChannels.VPN -> context.getString(R.string.vpn)
NotificationChannels.PROXY -> context.getString(R.string.proxy)
NotificationChannels.AUTO_TUNNEL -> context.getString(R.string.auto_tunnel)
}
val notification =
notificationService.createNotification(
channel = channel,
title = title,
description = description,
actions = stopActions,
onGoing = true,
onlyAlertOnce = true,
groupKey = groupKey,
)
} else {
listOf(
notificationService.createNotificationAction(
notificationAction = NotificationAction.STOP_ALL,
extraId = null,
)
)
}
val title = when (channel) {
is NotificationChannels.Tunnel.VPN -> context.getString(R.string.vpn)
is NotificationChannels.Tunnel.Proxy -> context.getString(R.string.proxy)
}
val style = if (tunnelNotificationLines.size > 1) {
NotificationCompat.InboxStyle()
.setBigContentTitle(title)
.setSummaryText("${tunnelNotificationLines.size} ${context.getString(R.string.tunnels).lowercase()}")
.also { inboxStyle ->
formattedLines.forEach { inboxStyle.addLine(it) }
}
} else {
null
}
val notification = notificationService.createNotification(
channel = channel,
title = title,
description = description,
actions = actions,
onGoing = true,
onlyAlertOnce = true,
groupKey = groupKey,
style = style,
)
notificationService.show(notificationId, notification)
}
override suspend fun showIpv4Fallback(tunnelId: Int) {
override fun updateProxyPersistentNotification(
tunnelNotificationLines: Map<Int, TunnelNotificationLine>
) {
updateGroupNotification(
tunnelNotificationLines = tunnelNotificationLines,
notificationId = PROXY_NOTIFICATION_ID,
channel = NotificationChannels.Tunnel.Proxy,
groupKey = PROXY_GROUP_KEY,
)
}
val context = notificationService.context
val name = tunnelName(tunnelId)
override fun updateVpnPersistentNotification(
tunnelNotificationLines: Map<Int, TunnelNotificationLine>
) {
updateGroupNotification(
tunnelNotificationLines = tunnelNotificationLines,
notificationId = VPN_NOTIFICATION_ID,
channel = NotificationChannels.Tunnel.VPN,
groupKey = VPN_GROUP_KEY,
)
}
showMessage(
override fun showIpv4Fallback(tunnelName: String) {
showEvent(
title = context.getString(R.string.ipv4_fallback),
message = context.getString(R.string.notification_ipv4_fallback_message, name),
message = context.getString(R.string.notification_ipv4_fallback_message, tunnelName),
)
}
override suspend fun showIpv6Recovery(tunnelId: Int) {
val context = notificationService.context
val name = tunnelName(tunnelId)
showMessage(
override fun showIpv6Recovery(tunnelName: String) {
showEvent(
title = context.getString(R.string.ipv6_recovery),
message = context.getString(R.string.notification_ipv6_recovery_message, name),
message = context.getString(R.string.notification_ipv6_recovery_message, tunnelName),
)
}
override suspend fun showDynamicDnsUpdate(tunnelId: Int) {
val context = notificationService.context
val name = tunnelName(tunnelId)
showMessage(
override fun showDynamicDnsUpdate(tunnelName: String) {
showEvent(
title = context.getString(R.string.dynamic_dns_update),
message = context.getString(R.string.notification_dynamic_dns_message, name),
message = context.getString(R.string.notification_dynamic_dns_message, tunnelName),
)
}
override suspend fun showVpnRequired() {
override fun showVpnRequired() {
showError(notificationService.context.getString(R.string.vpn_permission_required))
}
override suspend fun showStateConflict(tunnelId: Int) {
val context = notificationService.context
val name = tunnelName(tunnelId)
showError(context.getString(R.string.notification_tunnel_already_running, name))
}
override suspend fun showRootShellAccess() {
override fun showRootShellAccess() {
// TODO could improve with fix action
val context = notificationService.context
showError(context.getString(R.string.error_root_denied))
}
override suspend fun showSocks5PortUnavailable(port: Int) {
override fun showSocks5PortUnavailable(port: Int, tunnelName: String) {
val context = notificationService.context
val message = context.getString(R.string.error_socks5_port_unavailable, port)
showError(message)
}
override suspend fun showHttpPortUnavailable(port: Int) {
override fun showHttpPortUnavailable(port: Int, tunnelName: String) {
val context = notificationService.context
val message = context.getString(R.string.error_http_port_unavailable, port)
showError(message)
}
override suspend fun showError(message: String) {
override fun showError(message: String) {
val notification =
notificationService.createNotification(
channel = NotificationChannels.VPN,
channel = NotificationChannels.Errors,
title = notificationService.context.getString(R.string.error),
description = message,
onGoing = false,
@@ -179,11 +168,11 @@ class AndroidTunnelNotificationService(
notificationService.show(TUNNEL_ERROR_NOTIFICATION_ID, notification)
}
private fun showMessage(title: String, message: String) {
private fun showEvent(title: String, message: String) {
val notification =
notificationService.createNotification(
channel = NotificationChannels.VPN,
channel = NotificationChannels.Events,
title = title,
description = message,
onGoing = false,
@@ -193,11 +182,4 @@ class AndroidTunnelNotificationService(
notificationService.show(TUNNEL_MESSAGES_NOTIFICATION_ID, notification)
}
private suspend fun tunnelName(id: Int): String {
val context = notificationService.context
return tunnelRepository.getById(id)?.name ?: context.getString(R.string.unknown, id)
}
}
@@ -21,6 +21,7 @@ interface NotificationService {
onlyAlertOnce: Boolean = true,
groupKey: String? = null,
isGroupSummary: Boolean = false,
style: NotificationCompat.Style? = null,
): Notification
fun createNotification(
@@ -34,6 +35,7 @@ interface NotificationService {
onlyAlertOnce: Boolean = true,
groupKey: String? = null,
isGroupSummary: Boolean = false,
style: NotificationCompat.Style? = null,
): Notification
fun createAllChannels()
@@ -0,0 +1,9 @@
package com.zaneschepke.wireguardautotunnel.core.notification
import com.zaneschepke.wireguardautotunnel.ui.state.DisplayTunnelState
data class TunnelNotificationLine(
val id: Int,
val name: String,
val displayState: DisplayTunnelState,
)
@@ -1,26 +1,24 @@
package com.zaneschepke.wireguardautotunnel.core.notification
import com.zaneschepke.tunnel.state.ActiveTunnel
interface TunnelNotificationService {
suspend fun updatePersistentNotifications(activeTunnels: Map<Int, ActiveTunnel>)
fun updateProxyPersistentNotification(tunnelNotificationLines: Map<Int, TunnelNotificationLine>)
suspend fun showIpv4Fallback(tunnelId: Int)
fun updateVpnPersistentNotification(tunnelNotificationLines: Map<Int, TunnelNotificationLine>)
suspend fun showIpv6Recovery(tunnelId: Int)
fun showIpv4Fallback(tunnelName: String)
suspend fun showDynamicDnsUpdate(tunnelId: Int)
fun showIpv6Recovery(tunnelName: String)
suspend fun showVpnRequired()
fun showDynamicDnsUpdate(tunnelName: String)
suspend fun showStateConflict(tunnelId: Int)
fun showVpnRequired()
suspend fun showSocks5PortUnavailable(port: Int)
fun showSocks5PortUnavailable(port: Int, tunnelName: String)
suspend fun showHttpPortUnavailable(port: Int)
fun showHttpPortUnavailable(port: Int, tunnelName: String)
suspend fun showRootShellAccess()
fun showRootShellAccess()
suspend fun showError(message: String)
fun showError(message: String)
}
@@ -181,7 +181,7 @@ class AutoTunnelService : LifecycleService() {
) {
val notification =
notificationService.createNotification(
AndroidNotificationService.NotificationChannels.AUTO_TUNNEL,
AndroidNotificationService.NotificationChannels.AutoTunnel,
title = getString(R.string.auto_tunnel_title),
description = description,
actions =
@@ -295,7 +295,7 @@ class AutoTunnelService : LifecycleService() {
if (!state.locationPermissionsEnabled) {
val notification =
notificationService.createNotification(
AndroidNotificationService.NotificationChannels.AUTO_TUNNEL,
AndroidNotificationService.NotificationChannels.AutoTunnel,
title = getString(R.string.warning),
description = getString(R.string.location_permissions_missing),
)
@@ -313,7 +313,7 @@ class AutoTunnelService : LifecycleService() {
if (!state.locationServicesEnabled) {
val notification =
notificationService.createNotification(
AndroidNotificationService.NotificationChannels.AUTO_TUNNEL,
AndroidNotificationService.NotificationChannels.AutoTunnel,
title = getString(R.string.warning),
description = getString(R.string.location_services_not_detected),
)
@@ -21,6 +21,7 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelBackendProvider
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider
import com.zaneschepke.wireguardautotunnel.domain.repository.AutoTunnelSettingsRepository
import com.zaneschepke.wireguardautotunnel.util.extensions.to
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChangedBy
@@ -34,7 +35,7 @@ import org.koin.dsl.module
import timber.log.Timber
val tunnelBackendProviderModule = module {
single<TunnelNotificationService> { AndroidTunnelNotificationService(get(), get()) }
single<TunnelNotificationService> { AndroidTunnelNotificationService(get()) }
singleOf(::TunnelEventDispatcher)
single<NotificationProvider> {
@@ -44,7 +45,7 @@ val tunnelBackendProviderModule = module {
override val vpnInitNotification: Notification
get() =
notificationService.createNotification(
channel = NotificationChannels.VPN,
channel = NotificationChannels.Tunnel.VPN,
title = context.getString(R.string.initializing),
onGoing = true,
groupKey = VPN_GROUP_KEY,
@@ -53,7 +54,7 @@ val tunnelBackendProviderModule = module {
override val proxyInitNotification: Notification
get() =
notificationService.createNotification(
channel = NotificationChannels.PROXY,
channel = NotificationChannels.Tunnel.Proxy,
title = context.getString(R.string.initializing),
onGoing = true,
groupKey = PROXY_GROUP_KEY,
@@ -84,7 +85,7 @@ val tunnelBackendProviderModule = module {
object : AndroidNetworkMonitor.ConfigurationListener {
override suspend fun runRootShellCommand(cmd: String): String? {
return try {
withTimeout(3_000) {
withTimeout(3_000.milliseconds) {
withContext(Dispatchers.IO) {
val result = RootShell.run(cmd)
result.output
@@ -1,11 +0,0 @@
package com.zaneschepke.wireguardautotunnel.domain.enums
sealed class BackendMode {
data object Inactive : BackendMode()
data class KillSwitch(
val allowedIps: Set<String>,
val isMetered: Boolean,
val dualStack: Boolean,
) : BackendMode()
}
@@ -5,12 +5,14 @@ import com.zaneschepke.wireguardautotunnel.R
enum class NotificationAction {
TUNNEL_OFF,
AUTO_TUNNEL_OFF;
AUTO_TUNNEL_OFF,
STOP_ALL;
fun title(context: Context): String {
return when (this) {
TUNNEL_OFF -> context.getString(R.string.stop)
AUTO_TUNNEL_OFF -> context.getString(R.string.stop)
STOP_ALL -> context.getString(R.string.stop_all)
}
}
}
@@ -14,7 +14,6 @@ import androidx.compose.material.icons.outlined.RemoveRedEye
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.NetworkCheck
import androidx.compose.material.icons.rounded.Save
+10
View File
@@ -570,4 +570,14 @@
<string name="error_socks5_port_unavailable">SOCKS5 port %1$d is already in use.\nPlease choose a different port.</string>
<string name="error_http_port_unavailable">HTTP listener port %1$d is already in use.\nPlease choose a different port.</string>
<string name="events_channel_id" translatable="false">Events Channel</string>
<string name="errors_channel_id" translatable="false">Errors Channel</string>
<string name="errors">Errors</string>
<string name="events">Events</string>
<string name="errors_channel_description">A channel for application and tunnel errors</string>
<string name="events_channel_description">A channel for app events, like automation event</string>
<string name="app_channel_id" translatable="false">App Channel</string>
<string name="app">App</string>
<string name="app_channel_description">A channel for general application notifications, like version updates</string>
<string name="stop_all">Stop all</string>
</resources>
@@ -154,9 +154,10 @@ class TunnelBackend(
}
override suspend fun stop(id: Int): Result<Unit> = runCatching {
// TODO need a clean localized message for this passed by provider
val runtime =
actor.state.value.byTunnelId[id]
?: throw BackendException.StateConflict(
?: throw BackendException.InternalError(
"Tunnel $id is not active or no longer exists"
)
@@ -1,7 +1,6 @@
package com.zaneschepke.tunnel.util
sealed class BackendException : Exception() {
class StateConflict(override val message: String) : BackendException()
class InternalError(override val message: String) : BackendException()