diff --git a/README.md b/README.md
index 5581c80..12dc1ee 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ This is an alternative Android Application for [WireGuard](https://www.wireguard
## Screenshots
-
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9169f10..eab4a92 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,8 +16,8 @@ android {
compileSdk = 33
val versionMajor = 2
- val versionMinor = 0
- val versionPatch = 3
+ val versionMinor = 1
+ val versionPatch = 2
val versionBuild = 0
defaultConfig {
@@ -54,7 +54,7 @@ android {
compose = true
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.4.7"
+ kotlinCompilerExtensionVersion = "1.4.8"
}
packaging {
resources {
@@ -83,7 +83,7 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-test-manifest")
//wireguard tunnel
- implementation("com.wireguard.android:tunnel:1.0.20230405")
+ implementation("com.wireguard.android:tunnel:1.0.20230427")
//logging
implementation("com.jakewharton.timber:timber:5.0.1")
@@ -127,6 +127,7 @@ dependencies {
}
+
kapt {
correctErrorTypes = true
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7e594a4..580267a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
@@ -59,12 +60,13 @@
android:stopWithTask="false"
android:exported="false">
-
+
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
similarity index 84%
rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt
rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
index e1d35f8..ca90313 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
@@ -1,8 +1,9 @@
-package com.zaneschepke.wireguardautotunnel
+package com.zaneschepke.wireguardautotunnel.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
@@ -11,7 +12,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
@@ -23,21 +23,19 @@ class BootReceiver : BroadcastReceiver() {
@Inject
lateinit var settingsRepo : Repository
- @OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
CoroutineScope(SupervisorJob()).launch {
try {
val settings = settingsRepo.getAll()
if (!settings.isNullOrEmpty()) {
- val setting = settings[0]
+ val setting = settings.first()
if (setting.isAutoTunnelEnabled && setting.defaultTunnel != null) {
- val defaultTunnel = TunnelConfig.from(setting.defaultTunnel!!)
ServiceTracker.actionOnService(
Action.START, context,
WireGuardConnectivityWatcherService::class.java,
mapOf(context.resources.getString(R.string.tunnel_extras_key) to
- defaultTunnel.toString())
+ setting.defaultTunnel!!)
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
new file mode 100644
index 0000000..2f79f54
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
@@ -0,0 +1,56 @@
+package com.zaneschepke.wireguardautotunnel.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.repository.Repository
+import com.zaneschepke.wireguardautotunnel.service.foreground.Action
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
+import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class NotificationActionReceiver : BroadcastReceiver() {
+
+ @Inject
+ lateinit var settingsRepo : Repository
+ override fun onReceive(context: Context, intent: Intent?) {
+ CoroutineScope(SupervisorJob()).launch {
+ try {
+ val settings = settingsRepo.getAll()
+ if (!settings.isNullOrEmpty()) {
+ val setting = settings.first()
+ if (setting.defaultTunnel != null) {
+ ServiceTracker.actionOnService(
+ Action.STOP, context,
+ WireGuardTunnelService::class.java,
+ mapOf(
+ context.resources.getString(R.string.tunnel_extras_key) to
+ setting.defaultTunnel!!
+ )
+ )
+ delay(1000)
+ ServiceTracker.actionOnService(
+ Action.START, context,
+ WireGuardTunnelService::class.java,
+ mapOf(
+ context.resources.getString(R.string.tunnel_extras_key) to
+ setting.defaultTunnel!!
+ )
+ )
+ }
+ }
+ } finally {
+ cancel()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt
index ab71f7c..dd502c7 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt
@@ -13,6 +13,7 @@ class QRScanner @Inject constructor(private val gmsBarcodeScanner: GmsBarcodeSca
gmsBarcodeScanner.startScan().addOnSuccessListener {
trySend(it.rawValue)
}.addOnFailureListener {
+ trySend(it.message)
Timber.e(it.message)
}
awaitClose {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
index 9845fbf..4e5b792 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
@@ -20,7 +20,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
@@ -123,7 +122,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
}
- @OptIn(DelicateCoroutinesApi::class)
private fun startWatcherJob() {
watcherJob = CoroutineScope(SupervisorJob()).launch {
val settings = settingsRepo.getAll();
@@ -151,13 +149,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
is NetworkStatus.CapabilitiesChanged -> {
isMobileDataConnected = true
Timber.d("Mobile data capabilities changed")
- if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
- && vpnService.getState() == Tunnel.State.DOWN)
- startVPN()
+ if(!disconnecting && !connecting) {
+ if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
+ && vpnService.getState() == Tunnel.State.DOWN)
+ startVPN()
+ }
}
is NetworkStatus.Unavailable -> {
isMobileDataConnected = false
- if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
+ if(!disconnecting && !connecting) {
+ if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
+ }
Timber.d("Lost mobile data connection")
}
}
@@ -178,7 +180,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
Timber.d("Not connect and not disconnecting")
val ssid = wifiService.getNetworkName(it.networkCapabilities);
Timber.d("SSID: $ssid")
- if ((setting.trustedNetworkSSIDs?.contains(ssid) == false) && vpnService.getState() == Tunnel.State.DOWN) {
+ if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) {
Timber.d("Starting VPN Tunnel for untrusted network: $ssid")
startVPN()
} else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
index 816bf3a..68a2853 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
@@ -1,9 +1,12 @@
package com.zaneschepke.wireguardautotunnel.service.foreground
+import android.app.PendingIntent
+import android.content.Intent
import android.os.Bundle
-import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
+import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.hilt.android.AndroidEntryPoint
@@ -28,6 +31,8 @@ class WireGuardTunnelService : ForegroundService() {
private lateinit var job : Job
+ private var tunnelName : String = ""
+
override fun startService(extras : Bundle?) {
super.startService(extras)
val tunnelConfigString = extras?.getString(getString(R.string.tunnel_extras_key))
@@ -36,10 +41,8 @@ class WireGuardTunnelService : ForegroundService() {
if(tunnelConfigString != null) {
try {
val tunnelConfig = TunnelConfig.from(tunnelConfigString)
- val state = vpnService.startTunnel(tunnelConfig)
- if (state == Tunnel.State.UP) {
- launchVpnConnectedNotification(tunnelConfig.name)
- }
+ tunnelName = tunnelConfig.name
+ vpnService.startTunnel(tunnelConfig)
} catch (e : Exception) {
Timber.e("Problem starting tunnel: ${e.message}")
stopService(extras)
@@ -48,6 +51,34 @@ class WireGuardTunnelService : ForegroundService() {
Timber.e("Tunnel config null")
}
}
+ CoroutineScope(job).launch {
+ var didShowConnected = false
+ var didShowFailedHandshakeNotification = false
+ vpnService.handshakeStatus.collect {
+ when(it) {
+ HandshakeStatus.NOT_STARTED -> {
+ }
+ HandshakeStatus.NEVER_CONNECTED -> {
+ if(!didShowFailedHandshakeNotification) {
+ launchVpnConnectionFailedNotification(getString(R.string.initial_connection_failure_message))
+ didShowFailedHandshakeNotification = true
+ }
+ }
+ HandshakeStatus.HEALTHY -> {
+ if(!didShowConnected) {
+ launchVpnConnectedNotification()
+ didShowConnected = true
+ }
+ }
+ HandshakeStatus.UNHEALTHY -> {
+ if(!didShowFailedHandshakeNotification) {
+ launchVpnConnectionFailedNotification(getString(R.string.lost_connection_failure_message))
+ didShowFailedHandshakeNotification = true
+ }
+ }
+ }
+ }
+ }
}
override fun stopService(extras : Bundle?) {
@@ -59,7 +90,7 @@ class WireGuardTunnelService : ForegroundService() {
stopSelf()
}
- private fun launchVpnConnectedNotification(tunnelName : String) {
+ private fun launchVpnConnectedNotification() {
val notification = notificationService.createNotification(
channelId = getString(R.string.vpn_channel_id),
channelName = getString(R.string.vpn_channel_name),
@@ -70,6 +101,22 @@ class WireGuardTunnelService : ForegroundService() {
)
super.startForeground(foregroundId, notification)
}
+
+ private fun launchVpnConnectionFailedNotification(message : String) {
+ val notification = notificationService.createNotification(
+ channelId = getString(R.string.vpn_channel_id),
+ channelName = getString(R.string.vpn_channel_name),
+ action = PendingIntent.getBroadcast(this,0,Intent(this, NotificationActionReceiver::class.java),PendingIntent.FLAG_IMMUTABLE),
+ actionText = getString(R.string.restart),
+ title = getString(R.string.vpn_connection_failed),
+ onGoing = false,
+ showTimestamp = true,
+ description = message
+ )
+ super.startForeground(foregroundId, notification)
+ }
+
+
private fun cancelJob() {
if(this::job.isInitialized) {
job.cancel()
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
index cdf5151..f6f3745 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.service.network
-import android.app.Service
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
@@ -10,12 +9,10 @@ import android.net.wifi.SupplicantState
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.os.Build
-import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
abstract class BaseNetworkService>(val context: Context, networkCapability : Int) : NetworkService {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
index cc8f0d9..fb3d476 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
@@ -2,12 +2,15 @@ package com.zaneschepke.wireguardautotunnel.service.notification
import android.app.Notification
import android.app.NotificationManager
+import android.app.PendingIntent
interface NotificationService {
fun createNotification(
channelId: String,
channelName: String,
title: String = "",
+ action: PendingIntent? = null,
+ actionText: String? = null,
description: String,
showTimestamp : Boolean = false,
importance: Int = NotificationManager.IMPORTANCE_HIGH,
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
index bc29647..ad68582 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
@@ -20,13 +20,15 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val
channelId: String,
channelName: String,
title: String,
+ action: PendingIntent?,
+ actionText: String?,
description: String,
showTimestamp: Boolean,
importance: Int,
vibration: Boolean,
onGoing: Boolean,
lights: Boolean
- ) : Notification {
+ ): Notification {
val channel = NotificationChannel(
channelId,
channelName,
@@ -42,7 +44,12 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val
notificationManager.createNotificationChannel(channel)
val pendingIntent: PendingIntent =
Intent(context, MainActivity::class.java).let { notificationIntent ->
- PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
+ PendingIntent.getActivity(
+ context,
+ 0,
+ notificationIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
}
val builder: Notification.Builder =
@@ -50,14 +57,21 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val
context,
channelId
)
-
- return builder
- .setContentTitle(title)
- .setContentText(description)
- .setContentIntent(pendingIntent)
- .setOngoing(onGoing)
- .setShowWhen(showTimestamp)
- .setSmallIcon(R.mipmap.ic_launcher_foreground)
- .build()
+ return builder.let {
+ if(action != null && actionText != null) {
+ //TODO find a not deprecated way to do this
+ it.addAction(
+ Notification.Action.Builder(0, actionText, action)
+ .build())
+ it.setAutoCancel(true)
+ }
+ it.setContentTitle(title)
+ .setContentText(description)
+ .setContentIntent(pendingIntent)
+ .setOngoing(onGoing)
+ .setShowWhen(showTimestamp)
+ .setSmallIcon(R.mipmap.ic_launcher_foreground)
+ .build()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt
new file mode 100644
index 0000000..c6bbf35
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt
@@ -0,0 +1,14 @@
+package com.zaneschepke.wireguardautotunnel.service.tunnel
+
+enum class HandshakeStatus {
+ HEALTHY,
+ UNHEALTHY,
+ NEVER_CONNECTED,
+ NOT_STARTED;
+
+ companion object {
+ private const val WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC = 120
+ const val UNHEALTHY_TIME_LIMIT_SEC = WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + 60
+ const val NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC = 30
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt
index 91be50c..346a49a 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt
@@ -1,6 +1,8 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel
+import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
+import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import kotlinx.coroutines.flow.SharedFlow
@@ -9,5 +11,8 @@ interface VpnService : Tunnel {
suspend fun stopTunnel()
val state : SharedFlow
val tunnelName : SharedFlow
+ val statistics : SharedFlow
+ val lastHandshake : SharedFlow