diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt index a6e2afd8..ce70b7e5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt @@ -51,6 +51,13 @@ class WireGuardAutoTunnel : Application(), KoinComponent { private val backend: Backend by inject() + private val alwaysOnCallback = + object : VpnService.AlwaysOnCallback { + override fun alwaysOnTriggered() { + applicationScope.launch { tunnelCoordinator.startDefault() } + } + } + @OptIn(KoinViewModelScopeApi::class) override fun onCreate() { super.onCreate() @@ -86,13 +93,7 @@ class WireGuardAutoTunnel : Application(), KoinComponent { Timber.plant(ReleaseTree()) } - backend.setAlwaysOnCallback( - object : VpnService.AlwaysOnCallback { - override fun alwaysOnTriggered() { - applicationScope.launch { tunnelCoordinator.startDefault() } - } - } - ) + backend.setAlwaysOnCallback(alwaysOnCallback) val dispatcher = get() val coordinator = get() diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt index 5eeb6069..85589580 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt @@ -25,7 +25,6 @@ import com.zaneschepke.tunnel.util.RootShellException import com.zaneschepke.tunnel.util.buildResolvedPeers import com.zaneschepke.tunnel.util.toHostMap import com.zaneschepke.wireguardautotunnel.parser.ActiveConfig -import java.lang.ref.WeakReference import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -250,7 +249,7 @@ class TunnelBackend( } override fun setAlwaysOnCallback(alwaysOnCallback: VpnService.AlwaysOnCallback) { - ServiceHolder.alwaysOnCallback = WeakReference(alwaysOnCallback) + ServiceHolder.alwaysOnCallback = alwaysOnCallback } override suspend fun stop(id: Int): Result = tunnelMutex.withLock { diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/ServiceHolder.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/ServiceHolder.kt index ce631066..1ba211aa 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/ServiceHolder.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/ServiceHolder.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import com.zaneschepke.tunnel.ProxyBackend import com.zaneschepke.tunnel.util.BackendException -import java.lang.ref.WeakReference import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.delay @@ -90,7 +89,7 @@ internal class ServiceHolder(val context: Context) { service.shutdown() withTimeoutOrNull(1_500L.milliseconds) { tunnelServiceFlow.first { it == null } } } finally { - clearVpnService() + clearTunnelService() } } @@ -110,6 +109,6 @@ internal class ServiceHolder(val context: Context) { const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30 const val DEFAULT_MTU = 1280 // for consumer to set AOVPN callback - var alwaysOnCallback: WeakReference? = null + var alwaysOnCallback: VpnService.AlwaysOnCallback? = null } } diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt index f9a967a7..1ea7013a 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/TunnelService.kt @@ -51,7 +51,7 @@ class TunnelService : LifecycleService() { (intent.component!!.packageName != packageName) ) { Timber.d("TunnelService started by system") - alwaysOnCallback?.get()?.alwaysOnTriggered() + alwaysOnCallback?.alwaysOnTriggered() } return START_STICKY diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt index 9b76c100..542bb5b1 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt @@ -1,5 +1,6 @@ package com.zaneschepke.tunnel.service +import android.content.Context import android.content.Intent import android.net.TrafficStats import android.os.Build @@ -81,15 +82,28 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector { bootKeepaliveService() - // Service restarted by system or Always-on VPN started - if ( - intent == null || - intent.component == null || - (intent.component!!.packageName != packageName) - ) { - Timber.d("VpnService started by system (Always-On trigger)") - alwaysOnCallback?.get()?.alwaysOnTriggered() + // system recovery restart + if (intent == null) { + return START_STICKY } + + val isUserLaunch = intent.getBooleanExtra(getUserLaunchExtraKey(this), false) + + val platformSaysAlwaysOn = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + isAlwaysOn + } else { + false + } + + val isAlwaysOnTrigger = + !isUserLaunch && (intent.action == SERVICE_INTERFACE || platformSaysAlwaysOn) + + if (isAlwaysOnTrigger) { + Timber.d("VpnService started by system (Always-On trigger)") + alwaysOnCallback?.alwaysOnTriggered() + } + return START_STICKY } @@ -330,6 +344,21 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector { } companion object { + + private fun getUserLaunchExtraKey(context: Context): String { + return "${context.packageName}.EXTRA_IS_USER_LAUNCH" + } + + @JvmStatic + fun start(context: Context, serviceClass: Class) { + val intent = + Intent(context, serviceClass).apply { + action = SERVICE_INTERFACE + putExtra(getUserLaunchExtraKey(context), true) + } + context.startService(intent) + } + private const val LOCKDOWN_SESSION_NAME = "Lockdown" private const val LOCALHOST = "127.0.0.1" private const val IPV4_INTERFACE_ADDRESS = "10.0.0.1"