fix: make ping aware of network availability
add basic tunnel error messages
This commit is contained in:
parent
53c09267df
commit
b1dc6c5d59
|
@ -164,7 +164,7 @@
|
||||||
tools:node="merge" />
|
tools:node="merge" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.foreground.TunnelBackgroundService"
|
android:name=".service.foreground.TunnelForegroundService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:persistent="true"
|
android:persistent="true"
|
||||||
android:foregroundServiceType="systemExempted"
|
android:foregroundServiceType="systemExempted"
|
||||||
|
|
|
@ -3,6 +3,9 @@ package com.zaneschepke.wireguardautotunnel
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
|
@ -52,6 +55,7 @@ class WireGuardAutoTunnel : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
StrictMode.setThreadPolicy(
|
StrictMode.setThreadPolicy(
|
||||||
|
@ -88,7 +92,25 @@ class WireGuardAutoTunnel : Application() {
|
||||||
super.onTerminate()
|
super.onTerminate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppLifecycleObserver : DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
|
Timber.d("Application entered foreground")
|
||||||
|
foreground = true
|
||||||
|
}
|
||||||
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
|
Timber.d("Application entered background")
|
||||||
|
foreground = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private var foreground = false
|
||||||
|
|
||||||
|
fun isForeground(): Boolean {
|
||||||
|
return foreground
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var instance: WireGuardAutoTunnel
|
lateinit var instance: WireGuardAutoTunnel
|
||||||
private set
|
private set
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,25 +7,21 @@ import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.components.ServiceComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dagger.hilt.android.scopes.ServiceScoped
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(ServiceComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
abstract class ServiceModule {
|
abstract class ServiceModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Wifi
|
@Wifi
|
||||||
@ServiceScoped
|
|
||||||
abstract fun provideWifiService(wifiService: WifiService): NetworkService
|
abstract fun provideWifiService(wifiService: WifiService): NetworkService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@MobileData
|
@MobileData
|
||||||
@ServiceScoped
|
|
||||||
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
|
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Ethernet
|
@Ethernet
|
||||||
@ServiceScoped
|
|
||||||
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
|
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.wireguard.android.util.ToolsInstaller
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||||
|
@ -78,6 +79,9 @@ class TunnelModule {
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
serviceManager: ServiceManager,
|
serviceManager: ServiceManager,
|
||||||
notificationService: NotificationService,
|
notificationService: NotificationService,
|
||||||
|
@Wifi wifiService: NetworkService,
|
||||||
|
@MobileData mobileDataService: NetworkService,
|
||||||
|
@Ethernet ethernetService: NetworkService,
|
||||||
): TunnelService {
|
): TunnelService {
|
||||||
return WireGuardTunnel(
|
return WireGuardTunnel(
|
||||||
amneziaBackend,
|
amneziaBackend,
|
||||||
|
@ -88,6 +92,9 @@ class TunnelModule {
|
||||||
ioDispatcher,
|
ioDispatcher,
|
||||||
serviceManager,
|
serviceManager,
|
||||||
notificationService,
|
notificationService,
|
||||||
|
wifiService,
|
||||||
|
mobileDataService,
|
||||||
|
ethernetService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class AppUpdateReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
if (!settings.isAutoTunnelEnabled) {
|
if (!settings.isAutoTunnelEnabled) {
|
||||||
val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive }
|
val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive }
|
||||||
if (tunnels.isNotEmpty()) tunnelService.get().startTunnel(tunnels.first(), true)
|
if (tunnels.isNotEmpty()) tunnelService.get().startTunnel(tunnels.first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class BootReceiver : BroadcastReceiver() {
|
||||||
val tunState = tunnelService.get().vpnState.value.status
|
val tunState = tunnelService.get().vpnState.value.status
|
||||||
if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) {
|
if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) {
|
||||||
Timber.i("Starting previously active tunnel")
|
Timber.i("Starting previously active tunnel")
|
||||||
tunnelService.get().startTunnel(activeTunnels.first(), true)
|
tunnelService.get().startTunnel(activeTunnels.first())
|
||||||
}
|
}
|
||||||
if (isAutoTunnelEnabled) {
|
if (isAutoTunnelEnabled) {
|
||||||
Timber.i("Starting watcher service from boot")
|
Timber.i("Starting watcher service from boot")
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ServiceManager
|
||||||
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
||||||
|
|
||||||
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||||
var backgroundService = CompletableDeferred<TunnelBackgroundService>()
|
var backgroundService = CompletableDeferred<TunnelForegroundService>()
|
||||||
var autoTunnelTile = CompletableDeferred<AutoTunnelControlTile>()
|
var autoTunnelTile = CompletableDeferred<AutoTunnelControlTile>()
|
||||||
var tunnelControlTile = CompletableDeferred<TunnelControlTile>()
|
var tunnelControlTile = CompletableDeferred<TunnelControlTile>()
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ class ServiceManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startBackgroundService(tunnelConfig: TunnelConfig?) {
|
suspend fun startBackgroundService(tunnelConfig: TunnelConfig) {
|
||||||
if (backgroundService.isCompleted) return
|
if (backgroundService.isCompleted) return
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
startService(TunnelBackgroundService::class.java, true)
|
startService(TunnelForegroundService::class.java, true)
|
||||||
backgroundService.await()
|
backgroundService.await()
|
||||||
backgroundService.getCompleted().start(tunnelConfig)
|
backgroundService.getCompleted().start(tunnelConfig)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class TunnelBackgroundService : LifecycleService() {
|
class TunnelForegroundService : LifecycleService() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationService: NotificationService
|
lateinit var notificationService: NotificationService
|
||||||
|
@ -39,9 +39,9 @@ class TunnelBackgroundService : LifecycleService() {
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(tunnelConfig: TunnelConfig?) {
|
fun start(tunnelConfig: TunnelConfig) {
|
||||||
ServiceCompat.startForeground(
|
ServiceCompat.startForeground(
|
||||||
this,
|
this@TunnelForegroundService,
|
||||||
NotificationService.KERNEL_SERVICE_NOTIFICATION_ID,
|
NotificationService.KERNEL_SERVICE_NOTIFICATION_ID,
|
||||||
createNotification(tunnelConfig),
|
createNotification(tunnelConfig),
|
||||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
|
@ -5,4 +5,8 @@ data class NetworkState(
|
||||||
val isMobileDataConnected: Boolean = false,
|
val isMobileDataConnected: Boolean = false,
|
||||||
val isEthernetConnected: Boolean = false,
|
val isEthernetConnected: Boolean = false,
|
||||||
val wifiName: String? = null,
|
val wifiName: String? = null,
|
||||||
)
|
) {
|
||||||
|
fun hasNoCapabilities(): Boolean {
|
||||||
|
return !isWifiConnected && !isMobileDataConnected && !isEthernetConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ShortcutsActivity : ComponentActivity() {
|
||||||
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
||||||
tunnelConfig?.let {
|
tunnelConfig?.let {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Action.START.name -> tunnelService.get().startTunnel(it, true)
|
Action.START.name -> tunnelService.get().startTunnel(it)
|
||||||
Action.STOP.name -> tunnelService.get().stopTunnel()
|
Action.STOP.name -> tunnelService.get().stopTunnel()
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ class TunnelControlTile : TileService() {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
if (tunnelService.vpnState.value.status.isUp()) return@launch tunnelService.stopTunnel()
|
if (tunnelService.vpnState.value.status.isUp()) return@launch tunnelService.stopTunnel()
|
||||||
appDataRepository.getStartTunnelConfig()?.let {
|
appDataRepository.getStartTunnelConfig()?.let {
|
||||||
tunnelService.startTunnel(it, true)
|
tunnelService.startTunnel(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
|
|
||||||
suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean = false)
|
suspend fun startTunnel(tunnelConfig: TunnelConfig?)
|
||||||
|
|
||||||
suspend fun stopTunnel()
|
suspend fun stopTunnel()
|
||||||
|
|
||||||
|
|
|
@ -2,33 +2,44 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||||
|
|
||||||
import com.wireguard.android.backend.Backend
|
import com.wireguard.android.backend.Backend
|
||||||
import com.wireguard.android.backend.Tunnel.State
|
import com.wireguard.android.backend.Tunnel.State
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.Ethernet
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.MobileData
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.Wifi
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -37,6 +48,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@ -51,6 +63,9 @@ constructor(
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
private val notificationService: NotificationService,
|
private val notificationService: NotificationService,
|
||||||
|
@Wifi private val wifiService: NetworkService,
|
||||||
|
@MobileData private val mobileDataService: NetworkService,
|
||||||
|
@Ethernet private val ethernetService: NetworkService,
|
||||||
) : TunnelService {
|
) : TunnelService {
|
||||||
|
|
||||||
private val _vpnState = MutableStateFlow(VpnState())
|
private val _vpnState = MutableStateFlow(VpnState())
|
||||||
|
@ -59,9 +74,11 @@ constructor(
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
private var tunnelChangesJob: Job? = null
|
private var tunnelChangesJob: Job? = null
|
||||||
private var pingJob: Job? = null
|
private var pingJob: Job? = null
|
||||||
|
private var networkJob: Job? = null
|
||||||
|
|
||||||
@get:Synchronized @set:Synchronized
|
@get:Synchronized @set:Synchronized
|
||||||
private var isKernelBackend: Boolean? = null
|
private var isKernelBackend: Boolean? = null
|
||||||
|
private val isNetworkAvailable = AtomicBoolean(false)
|
||||||
|
|
||||||
private val tunnelControlMutex = Mutex()
|
private val tunnelControlMutex = Mutex()
|
||||||
|
|
||||||
|
@ -88,6 +105,23 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO refactor duplicate
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||||
|
return combine(
|
||||||
|
wifiService.status,
|
||||||
|
mobileDataService.status,
|
||||||
|
ethernetService.status,
|
||||||
|
) { wifi, mobileData, ethernet ->
|
||||||
|
NetworkState(
|
||||||
|
wifi.available,
|
||||||
|
mobileData.available,
|
||||||
|
ethernet.available,
|
||||||
|
wifi.name,
|
||||||
|
)
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
when (val backend = backend()) {
|
when (val backend = backend()) {
|
||||||
|
@ -113,20 +147,43 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig?) {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
if (tunnelConfig == null || isTunnelAlreadyRunning(tunnelConfig)) return@withContext
|
if (tunnelConfig == null || isTunnelAlreadyRunning(tunnelConfig)) return@withContext
|
||||||
onBeforeStart(background)
|
onBeforeStart(tunnelConfig)
|
||||||
updateTunnelConfig(tunnelConfig) // need to update this here
|
updateTunnelConfig(tunnelConfig) // need to update this here
|
||||||
|
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||||
withServiceActive {
|
withServiceActive {
|
||||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||||
updateTunnelState(it, tunnelConfig)
|
updateTunnelState(it, tunnelConfig)
|
||||||
onTunnelStart(tunnelConfig, background)
|
startActiveTunnelJobs()
|
||||||
|
}.onFailure {
|
||||||
|
onTunnelStop(tunnelConfig)
|
||||||
|
// TODO improve this with better statuses and handling
|
||||||
|
showTunnelStartFailed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showTunnelStartFailed() {
|
||||||
|
if (WireGuardAutoTunnel.isForeground()) {
|
||||||
|
SnackbarController.showMessage(StringValue.StringResource(R.string.error_tunnel_start))
|
||||||
|
} else {
|
||||||
|
launchStartFailedNotification()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchStartFailedNotification() {
|
||||||
|
with(notificationService) {
|
||||||
|
val notification = createNotification(
|
||||||
|
WireGuardNotification.NotificationChannels.VPN,
|
||||||
|
title = context.getString(R.string.error_tunnel_start),
|
||||||
|
)
|
||||||
|
show(VPN_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun stopTunnel() {
|
override suspend fun stopTunnel() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
if (_vpnState.value.status.isDown()) return@withContext
|
if (_vpnState.value.status.isDown()) return@withContext
|
||||||
|
@ -200,31 +257,10 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onBeforeStart(background: Boolean) {
|
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) {
|
||||||
with(_vpnState.value) {
|
with(_vpnState.value) {
|
||||||
if (status.isUp()) stopTunnel() else clearJobsAndStats()
|
if (status.isUp()) stopTunnel() else clearJobsAndStats()
|
||||||
if (isKernelBackend == true || background) serviceManager.startBackgroundService(tunnelConfig)
|
serviceManager.startBackgroundService(tunnelConfig)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun onTunnelStart(tunnelConfig: TunnelConfig, background: Boolean) {
|
|
||||||
startActiveTunnelJobs()
|
|
||||||
if (_vpnState.value.status.isUp()) {
|
|
||||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
|
||||||
}
|
|
||||||
if (isKernelBackend == false && !background) launchUserspaceTunnelNotification()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchUserspaceTunnelNotification() {
|
|
||||||
with(notificationService) {
|
|
||||||
val notification = createNotification(
|
|
||||||
WireGuardNotification.NotificationChannels.VPN,
|
|
||||||
title = "${context.getString(R.string.tunnel_running)} - ${_vpnState.value.tunnelConfig?.name}",
|
|
||||||
actions = listOf(
|
|
||||||
notificationService.createNotificationAction(NotificationAction.TUNNEL_OFF),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
show(VPN_NOTIFICATION_ID, notification)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,14 +314,21 @@ constructor(
|
||||||
statsJob?.cancelWithMessage("Tunnel stats job cancelled")
|
statsJob?.cancelWithMessage("Tunnel stats job cancelled")
|
||||||
tunnelChangesJob?.cancelWithMessage("Tunnel changes job cancelled")
|
tunnelChangesJob?.cancelWithMessage("Tunnel changes job cancelled")
|
||||||
pingJob?.cancelWithMessage("Ping job cancelled")
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
|
networkJob?.cancelWithMessage("Network job cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startActiveTunnelJobs() {
|
override fun startActiveTunnelJobs() {
|
||||||
statsJob = startTunnelStatisticsJob()
|
statsJob = startTunnelStatisticsJob()
|
||||||
tunnelChangesJob = startTunnelConfigChangesJob()
|
tunnelChangesJob = startTunnelConfigChangesJob()
|
||||||
if (_vpnState.value.tunnelConfig?.isPingEnabled == true) pingJob = startPingJob()
|
if (_vpnState.value.tunnelConfig?.isPingEnabled == true) {
|
||||||
|
startPingJobs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startPingJobs() {
|
||||||
|
pingJob = startPingJob()
|
||||||
|
networkJob = startNetworkJob()
|
||||||
|
}
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
return _vpnState.value.tunnelConfig?.name ?: ""
|
return _vpnState.value.tunnelConfig?.name ?: ""
|
||||||
}
|
}
|
||||||
|
@ -331,6 +374,7 @@ constructor(
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
if (!isPingEnabled && pingJob?.isActive == true) {
|
if (!isPingEnabled && pingJob?.isActive == true) {
|
||||||
pingJob?.cancelWithMessage("Ping job cancelled")
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
|
networkJob?.cancelWithMessage("Network job cancelled")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
restartPingJob()
|
restartPingJob()
|
||||||
|
@ -339,7 +383,8 @@ constructor(
|
||||||
|
|
||||||
private fun restartPingJob() {
|
private fun restartPingJob() {
|
||||||
pingJob?.cancelWithMessage("Ping job cancelled")
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
pingJob = startPingJob()
|
networkJob?.cancelWithMessage("Network job cancelled")
|
||||||
|
startPingJobs()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
||||||
|
@ -376,13 +421,16 @@ constructor(
|
||||||
do {
|
do {
|
||||||
run {
|
run {
|
||||||
with(_vpnState.value) {
|
with(_vpnState.value) {
|
||||||
// TODO ignore when no connectivity
|
if (status.isUp() && tunnelConfig != null && isNetworkAvailable.get()) {
|
||||||
if (status.isUp() && tunnelConfig != null) {
|
|
||||||
val reachable = pingTunnel(tunnelConfig)
|
val reachable = pingTunnel(tunnelConfig)
|
||||||
if (reachable.contains(false)) {
|
if (reachable.contains(false)) {
|
||||||
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
if (isNetworkAvailable.get()) {
|
||||||
bounceTunnel()
|
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
||||||
delay(tunnelConfig.pingCooldown ?: Constants.PING_COOLDOWN)
|
bounceTunnel()
|
||||||
|
delay(tunnelConfig.pingCooldown ?: Constants.PING_COOLDOWN)
|
||||||
|
} else {
|
||||||
|
Timber.i("Ping result: target was not reachable, but not network available")
|
||||||
|
}
|
||||||
return@run
|
return@run
|
||||||
} else {
|
} else {
|
||||||
Timber.i("Ping result: all ping targets were reached successfully")
|
Timber.i("Ping result: all ping targets were reached successfully")
|
||||||
|
@ -394,6 +442,17 @@ constructor(
|
||||||
} while (true)
|
} while (true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startNetworkJob() = applicationScope.launch(ioDispatcher) {
|
||||||
|
combineNetworkEventsJob().collect {
|
||||||
|
Timber.d("New network state: $it")
|
||||||
|
if (!it.isWifiConnected && !it.isEthernetConnected && !it.isMobileDataConnected) {
|
||||||
|
isNetworkAvailable.set(false)
|
||||||
|
} else {
|
||||||
|
isNetworkAvailable.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateChange(newState: Tunnel.State) {
|
override fun onStateChange(newState: Tunnel.State) {
|
||||||
_vpnState.update {
|
_vpnState.update {
|
||||||
it.copy(status = TunnelState.from(newState))
|
it.copy(status = TunnelState.from(newState))
|
||||||
|
|
|
@ -112,12 +112,14 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initTunnel() {
|
private suspend fun initTunnel() {
|
||||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs()
|
withContext(ioDispatcher) {
|
||||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs()
|
||||||
if (activeTunnels.isNotEmpty() &&
|
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||||
tunnelService.get().getState() == TunnelState.DOWN
|
if (activeTunnels.isNotEmpty() &&
|
||||||
) {
|
tunnelService.get().getState() == TunnelState.DOWN
|
||||||
tunnelService.get().startTunnel(activeTunnels.first())
|
) {
|
||||||
|
tunnelService.get().startTunnel(activeTunnels.first())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
@ -142,7 +141,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
SnackbarControllerProvider { host ->
|
SnackbarControllerProvider { host ->
|
||||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.background(color = MaterialTheme.colorScheme.background),
|
|
||||||
contentWindowInsets = WindowInsets(0),
|
contentWindowInsets = WindowInsets(0),
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(host) { snackbarData: SnackbarData ->
|
SnackbarHost(host) { snackbarData: SnackbarData ->
|
||||||
|
|
|
@ -82,7 +82,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
|
|
||||||
val startAutoTunnel = withVpnPermission<Unit> { viewModel.onToggleAutoTunnel() }
|
val startAutoTunnel = withVpnPermission<Unit> { viewModel.onToggleAutoTunnel() }
|
||||||
val startTunnel = withVpnPermission<TunnelConfig> {
|
val startTunnel = withVpnPermission<TunnelConfig> {
|
||||||
viewModel.onTunnelStart(it, uiState.settings.isKernelEnabled)
|
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()
|
||||||
|
@ -129,7 +129,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
if (!checked) viewModel.onTunnelStop().also { return }
|
if (!checked) viewModel.onTunnelStop().also { return }
|
||||||
if (uiState.settings.isKernelEnabled) {
|
if (uiState.settings.isKernelEnabled) {
|
||||||
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
viewModel.onTunnelStart(tunnel)
|
||||||
} else {
|
} else {
|
||||||
startTunnel.invoke(tunnel)
|
startTunnel.invoke(tunnel)
|
||||||
}
|
}
|
||||||
|
@ -226,8 +226,9 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
) { tunnel ->
|
) { tunnel ->
|
||||||
val expanded = uiState.generalState.isTunnelStatsExpanded
|
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||||
TunnelRowItem(
|
TunnelRowItem(
|
||||||
tunnel.id == uiState.vpnState.tunnelConfig?.id &&
|
tunnel.id == uiState.vpnState.tunnelConfig?.id && (
|
||||||
uiState.vpnState.status.isUp(),
|
uiState.vpnState.status.isUp() || (uiState.settings.isKernelEnabled && tunnel.isActive)
|
||||||
|
),
|
||||||
expanded,
|
expanded,
|
||||||
selectedTunnel?.id == tunnel.id,
|
selectedTunnel?.id == tunnel.id,
|
||||||
tunnel,
|
tunnel,
|
||||||
|
|
|
@ -67,9 +67,9 @@ constructor(
|
||||||
appDataRepository.appState.setTunnelStatsExpanded(expanded)
|
appDataRepository.appState.setTunnelStatsExpanded(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStart(tunnelConfig: TunnelConfig, background: Boolean) = viewModelScope.launch {
|
fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
||||||
tunnelService.get().startTunnel(tunnelConfig, background)
|
tunnelService.get().startTunnel(tunnelConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStop() = viewModelScope.launch {
|
fun onTunnelStop() = viewModelScope.launch {
|
||||||
|
@ -86,20 +86,6 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateQrCodeTunnelName(config: String): String {
|
|
||||||
var defaultName = generateQrCodeDefaultName(config)
|
|
||||||
val lines = config.lines().toMutableList()
|
|
||||||
val linesIterator = lines.iterator()
|
|
||||||
while (linesIterator.hasNext()) {
|
|
||||||
val next = linesIterator.next()
|
|
||||||
if (next.contains(Constants.QR_CODE_NAME_PROPERTY)) {
|
|
||||||
defaultName = next.substringAfter(Constants.QR_CODE_NAME_PROPERTY).trim()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultName
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
val tunnels = appDataRepository.tunnels.getAll()
|
val tunnels = appDataRepository.tunnels.getAll()
|
||||||
|
|
|
@ -202,4 +202,5 @@
|
||||||
<string name="remove_amnezia_compatibility">Remove Amnezia compatibility</string>
|
<string name="remove_amnezia_compatibility">Remove Amnezia compatibility</string>
|
||||||
<string name="exclude_lan">Exclude LAN</string>
|
<string name="exclude_lan">Exclude LAN</string>
|
||||||
<string name="include_lan">Include LAN</string>
|
<string name="include_lan">Include LAN</string>
|
||||||
|
<string name="error_tunnel_start">Failed to starting tunnel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
What's new:
|
||||||
|
- Ping feature now works independent of auto tunnel
|
||||||
|
- Added convenience action for Amnezia compatibility
|
||||||
|
- Added convenience action for excluding LAN from tunnel
|
||||||
|
- Added debounce delay tuning option for auto tunnel
|
||||||
|
- Many bug fixes and improvements
|
Loading…
Reference in New Issue