fix: auto tunnel bugs
Fixes auto tunnel bug that can happen on startup Fixes auto tunnel bug that didn't allow manual toggle override of tunnel while auto tunnel is active Add basic foreground persistent notifications (optionally turned off via android settings)
This commit is contained in:
parent
7d810c7c3d
commit
cab2945930
|
@ -9,3 +9,11 @@ annotation class Kernel
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
annotation class Userspace
|
annotation class Userspace
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class TunnelShell
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class AppShell
|
||||||
|
|
|
@ -25,9 +25,18 @@ import javax.inject.Singleton
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class TunnelModule {
|
class TunnelModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideRootShell(@ApplicationContext context: Context): RootShell {
|
@TunnelShell
|
||||||
|
fun provideTunnelRootShell(@ApplicationContext context: Context): RootShell {
|
||||||
|
return RootShell(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@AppShell
|
||||||
|
fun provideAppRootShell(@ApplicationContext context: Context): RootShell {
|
||||||
return RootShell(context)
|
return RootShell(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +49,14 @@ class TunnelModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Userspace
|
@Userspace
|
||||||
fun provideUserspaceBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
|
fun provideUserspaceBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
|
||||||
return GoBackend(context, RootTunnelActionHandler(rootShell))
|
return GoBackend(context, RootTunnelActionHandler(rootShell))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Kernel
|
@Kernel
|
||||||
fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
|
fun provideKernelBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
|
||||||
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell))
|
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
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.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||||
|
@ -25,7 +26,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isDown
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
@ -34,7 +34,6 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
@ -50,6 +49,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
private val foregroundId = 122
|
private val foregroundId = 122
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@AppShell
|
||||||
lateinit var rootShell: Provider<RootShell>
|
lateinit var rootShell: Provider<RootShell>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -143,7 +143,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) {
|
private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
|
||||||
val notification =
|
val notification =
|
||||||
notificationService.createNotification(
|
notificationService.createNotification(
|
||||||
channelId = getString(R.string.watcher_channel_id),
|
channelId = getString(R.string.watcher_channel_id),
|
||||||
|
@ -209,28 +209,16 @@ class AutoTunnelService : LifecycleService() {
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Mobile data connection")
|
Timber.i("Gained Mobile data connection")
|
||||||
autoTunnelStateFlow.update {
|
emitMobileDataConnected(true)
|
||||||
it.copy(
|
|
||||||
isMobileDataConnected = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
autoTunnelStateFlow.update {
|
emitMobileDataConnected(true)
|
||||||
it.copy(
|
|
||||||
isMobileDataConnected = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Timber.i("Mobile data capabilities changed")
|
Timber.i("Mobile data capabilities changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
autoTunnelStateFlow.update {
|
emitMobileDataConnected(false)
|
||||||
it.copy(
|
|
||||||
isMobileDataConnected = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Timber.i("Lost mobile data connection")
|
Timber.i("Lost mobile data connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,6 +271,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
old.map { it.isActive } != new.map { it.isActive }
|
old.map { it.isActive } != new.map { it.isActive }
|
||||||
},
|
},
|
||||||
) { settings, tunnels ->
|
) { settings, tunnels ->
|
||||||
|
Timber.d("Tunnels or settings changed!")
|
||||||
autoTunnelStateFlow.value.copy(
|
autoTunnelStateFlow.value.copy(
|
||||||
settings = settings,
|
settings = settings,
|
||||||
tunnels = tunnels,
|
tunnels = tunnels,
|
||||||
|
@ -299,7 +288,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
Timber.i("Starting vpn state watcher")
|
Timber.i("Starting vpn state watcher")
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
tunnelService.get().vpnState.distinctUntilChanged { old, new ->
|
tunnelService.get().vpnState.distinctUntilChanged { old, new ->
|
||||||
old.tunnelConfig == new.tunnelConfig && old.status == new.status
|
old.tunnelConfig?.id == new.tunnelConfig?.id
|
||||||
}.collect { state ->
|
}.collect { state ->
|
||||||
autoTunnelStateFlow.update {
|
autoTunnelStateFlow.update {
|
||||||
it.copy(vpnState = state)
|
it.copy(vpnState = state)
|
||||||
|
@ -367,7 +356,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
mobileDataJob = null
|
mobileDataJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateEthernet(connected: Boolean) {
|
private fun emitEthernetConnected(connected: Boolean) {
|
||||||
autoTunnelStateFlow.update {
|
autoTunnelStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isEthernetConnected = connected,
|
isEthernetConnected = connected,
|
||||||
|
@ -375,7 +364,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWifi(connected: Boolean) {
|
private fun emitWifiConnected(connected: Boolean) {
|
||||||
autoTunnelStateFlow.update {
|
autoTunnelStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isWifiConnected = connected,
|
isWifiConnected = connected,
|
||||||
|
@ -383,6 +372,22 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun emitWifiSSID(ssid: String) {
|
||||||
|
autoTunnelStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
currentNetworkSSID = ssid,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitMobileDataConnected(connected: Boolean) {
|
||||||
|
autoTunnelStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
isMobileDataConnected = connected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun watchForEthernetConnectivityChanges() {
|
private suspend fun watchForEthernetConnectivityChanges() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Starting ethernet data watcher")
|
Timber.i("Starting ethernet data watcher")
|
||||||
|
@ -390,16 +395,16 @@ class AutoTunnelService : LifecycleService() {
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Ethernet connection")
|
Timber.i("Gained Ethernet connection")
|
||||||
updateEthernet(true)
|
emitEthernetConnected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Ethernet capabilities changed")
|
Timber.i("Ethernet capabilities changed")
|
||||||
updateEthernet(true)
|
emitEthernetConnected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
updateEthernet(false)
|
emitEthernetConnected(false)
|
||||||
Timber.i("Lost Ethernet connection")
|
Timber.i("Lost Ethernet connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,12 +419,12 @@ class AutoTunnelService : LifecycleService() {
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Wi-Fi connection")
|
Timber.i("Gained Wi-Fi connection")
|
||||||
updateWifi(true)
|
emitWifiConnected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Wifi capabilities changed")
|
Timber.i("Wifi capabilities changed")
|
||||||
updateWifi(true)
|
emitWifiConnected(true)
|
||||||
val ssid = getWifiSSID(status.networkCapabilities)
|
val ssid = getWifiSSID(status.networkCapabilities)
|
||||||
ssid?.let { name ->
|
ssid?.let { name ->
|
||||||
if (name.contains(Constants.UNREADABLE_SSID)) {
|
if (name.contains(Constants.UNREADABLE_SSID)) {
|
||||||
|
@ -428,16 +433,12 @@ class AutoTunnelService : LifecycleService() {
|
||||||
Timber.i("Detected valid SSID")
|
Timber.i("Detected valid SSID")
|
||||||
}
|
}
|
||||||
appDataRepository.appState.setCurrentSsid(name)
|
appDataRepository.appState.setCurrentSsid(name)
|
||||||
autoTunnelStateFlow.update {
|
emitWifiSSID(name)
|
||||||
it.copy(
|
|
||||||
currentNetworkSSID = name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: Timber.w("Failed to read ssid")
|
} ?: Timber.w("Failed to read ssid")
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
updateWifi(false)
|
emitWifiConnected(false)
|
||||||
Timber.i("Lost Wi-Fi connection")
|
Timber.i("Lost Wi-Fi connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,17 +462,17 @@ class AutoTunnelService : LifecycleService() {
|
||||||
private suspend fun handleNetworkEventChanges() {
|
private suspend fun handleNetworkEventChanges() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Starting network event watcher")
|
Timber.i("Starting network event watcher")
|
||||||
autoTunnelStateFlow.collectLatest { watcherState ->
|
autoTunnelStateFlow.collect { watcherState ->
|
||||||
val autoTunnel = "Auto-tunnel watcher"
|
val autoTunnel = "Auto-tunnel watcher"
|
||||||
Timber.d("New watcher state!")
|
|
||||||
// delay for rapid network state changes and then collect latest
|
// delay for rapid network state changes and then collect latest
|
||||||
delay(Constants.WATCHER_COLLECTION_DELAY)
|
delay(Constants.WATCHER_COLLECTION_DELAY)
|
||||||
val activeTunnel = watcherState.vpnState.tunnelConfig
|
val activeTunnel = watcherState.vpnState.tunnelConfig
|
||||||
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
|
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
|
val isTunnelDown = tunnelService.get().getState() == TunnelState.DOWN
|
||||||
when {
|
when {
|
||||||
watcherState.isEthernetConditionMet() -> {
|
watcherState.isEthernetConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||||
if (watcherState.vpnState.isDown()) {
|
if (isTunnelDown) {
|
||||||
defaultTunnel?.let {
|
defaultTunnel?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -483,7 +484,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
val mobileDataTunnel = getMobileDataTunnel()
|
val mobileDataTunnel = getMobileDataTunnel()
|
||||||
val tunnel =
|
val tunnel =
|
||||||
mobileDataTunnel ?: defaultTunnel
|
mobileDataTunnel ?: defaultTunnel
|
||||||
if (watcherState.vpnState.isDown() || activeTunnel?.isMobileDataTunnel == false) {
|
if (isTunnelDown || activeTunnel?.isMobileDataTunnel == false) {
|
||||||
tunnel?.let {
|
tunnel?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -492,7 +493,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
||||||
if (!watcherState.vpnState.isDown()) {
|
if (!isTunnelDown) {
|
||||||
activeTunnel?.let {
|
activeTunnel?.let {
|
||||||
tunnelService.get().stopTunnel(it)
|
tunnelService.get().stopTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -502,20 +503,20 @@ class AutoTunnelService : LifecycleService() {
|
||||||
watcherState.isUntrustedWifiConditionMet() -> {
|
watcherState.isUntrustedWifiConditionMet() -> {
|
||||||
Timber.i("Untrusted wifi condition met")
|
Timber.i("Untrusted wifi condition met")
|
||||||
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
|
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
|
||||||
watcherState.vpnState.isDown()
|
isTunnelDown
|
||||||
) {
|
) {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
||||||
)
|
)
|
||||||
watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
|
watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
|
||||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||||
if (watcherState.vpnState.isDown() || activeTunnel?.id != it.id) {
|
if (isTunnelDown || activeTunnel?.id != it.id) {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
} ?: suspend {
|
} ?: suspend {
|
||||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||||
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
if (default?.name != tunnelService.get().name || watcherState.vpnState.isDown()) {
|
if (default?.name != tunnelService.get().name || isTunnelDown) {
|
||||||
default?.let {
|
default?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -528,22 +529,22 @@ class AutoTunnelService : LifecycleService() {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
||||||
)
|
)
|
||||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
||||||
)
|
)
|
||||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
}
|
|
||||||
|
|
||||||
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
|
||||||
Timber.i(
|
|
||||||
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
|
||||||
)
|
|
||||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
|
||||||
}
|
}
|
||||||
|
// TODO disable for this now
|
||||||
|
// watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
||||||
|
// Timber.i(
|
||||||
|
// "$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
||||||
|
// )
|
||||||
|
// if (!isTunnelDown) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
|
// }
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Timber.i("$autoTunnel - no condition met")
|
Timber.i("$autoTunnel - no condition met")
|
||||||
|
|
|
@ -27,12 +27,14 @@ class ServiceManager
|
||||||
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
|
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
|
||||||
|
|
||||||
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
||||||
val intent = Intent(context, cls)
|
runCatching {
|
||||||
if (background) {
|
val intent = Intent(context, cls)
|
||||||
context.startForegroundService(intent)
|
if (background) {
|
||||||
} else {
|
context.startForegroundService(intent)
|
||||||
context.startService(intent)
|
} else {
|
||||||
}
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
}.onFailure { Timber.e(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startAutoTunnel(background: Boolean) {
|
suspend fun startAutoTunnel(background: Boolean) {
|
||||||
|
|
|
@ -62,7 +62,7 @@ class TunnelBackgroundService : LifecycleService() {
|
||||||
return notificationService.createNotification(
|
return notificationService.createNotification(
|
||||||
getString(R.string.vpn_channel_id),
|
getString(R.string.vpn_channel_id),
|
||||||
getString(R.string.vpn_channel_name),
|
getString(R.string.vpn_channel_name),
|
||||||
getString(R.string.tunnel_start_text),
|
getString(R.string.tunnel_running),
|
||||||
description = "",
|
description = "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
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.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@ -52,6 +53,8 @@ constructor(
|
||||||
|
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
|
|
||||||
|
private val runningHandle = AtomicBoolean(false)
|
||||||
|
|
||||||
private suspend fun backend(): Any {
|
private suspend fun backend(): Any {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isKernelEnabled) return kernelBackend.get()
|
if (settings.isKernelEnabled) return kernelBackend.get()
|
||||||
|
@ -91,8 +94,14 @@ constructor(
|
||||||
|
|
||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
|
if (runningHandle.get() == true && tunnelConfig == vpnState.value.tunnelConfig) {
|
||||||
|
Timber.w("Tunnel already running")
|
||||||
|
return@withContext Result.success(vpnState.value.status)
|
||||||
|
}
|
||||||
|
runningHandle.set(true)
|
||||||
onBeforeStart(tunnelConfig)
|
onBeforeStart(tunnelConfig)
|
||||||
if (background) startBackgroundService()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
if (background || settings.isKernelEnabled) startBackgroundService()
|
||||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||||
emitTunnelState(it)
|
emitTunnelState(it)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
@ -112,6 +121,7 @@ constructor(
|
||||||
onStopFailed()
|
onStopFailed()
|
||||||
}.also {
|
}.also {
|
||||||
stopBackgroundService()
|
stopBackgroundService()
|
||||||
|
runningHandle.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +156,7 @@ constructor(
|
||||||
}
|
}
|
||||||
cancelStatsJob()
|
cancelStatsJob()
|
||||||
resetBackendStatistics()
|
resetBackendStatistics()
|
||||||
|
runningHandle.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun shutDownActiveTunnel(config: TunnelConfig) {
|
private suspend fun shutDownActiveTunnel(config: TunnelConfig) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
@ -60,6 +61,7 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -213,7 +215,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
)
|
)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize().padding(it)
|
.fillMaxSize().padding(it)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
|
@ -29,7 +30,7 @@ class SettingsViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val rootShell: Provider<RootShell>,
|
@AppShell private val rootShell: Provider<RootShell>,
|
||||||
private val fileUtils: FileUtils,
|
private val fileUtils: FileUtils,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
@ -23,7 +24,7 @@ class AutoTunnelViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val rootShell: Provider<RootShell>,
|
@AppShell private val rootShell: Provider<RootShell>,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ object Constants {
|
||||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
||||||
|
|
||||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||||
const val FOCUS_REQUEST_DELAY = 500L
|
|
||||||
|
|
||||||
const val TRANSITION_ANIMATION_TIME = 200
|
const val TRANSITION_ANIMATION_TIME = 200
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ import androidx.compose.ui.graphics.Color
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||||
|
@ -23,10 +21,6 @@ fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
|
||||||
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
|
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VpnState.isDown(): Boolean {
|
|
||||||
return this.status == TunnelState.DOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||||
// TODO add never connected status after duration
|
// TODO add never connected status after duration
|
||||||
return this.latestHandshakeSeconds().let {
|
return this.latestHandshakeSeconds().let {
|
||||||
|
|
|
@ -225,4 +225,6 @@
|
||||||
<string name="kernel_not_supported">Kernel not supported</string>
|
<string name="kernel_not_supported">Kernel not supported</string>
|
||||||
<string name="start_auto">Start auto-tunnel</string>
|
<string name="start_auto">Start auto-tunnel</string>
|
||||||
<string name="stop_auto">Stop auto-tunnel</string>
|
<string name="stop_auto">Stop auto-tunnel</string>
|
||||||
|
<string name="tunnel_running">Tunnel running</string>
|
||||||
|
<string name="monitoring_state_changes">Monitoring state changes</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue