mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
fix: notifications not update, add new notification channels
This commit is contained in:
+4
@@ -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)
|
||||
}
|
||||
|
||||
+83
-21
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+73
-30
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
+89
-107
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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()
|
||||
|
||||
+9
@@ -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,
|
||||
)
|
||||
+10
-12
@@ -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)
|
||||
}
|
||||
|
||||
+3
-3
@@ -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()
|
||||
}
|
||||
+3
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user