diff --git a/android/src/main/java/com/tailscale/ipn/MainActivity.kt b/android/src/main/java/com/tailscale/ipn/MainActivity.kt index 98b046a..336edd9 100644 --- a/android/src/main/java/com/tailscale/ipn/MainActivity.kt +++ b/android/src/main/java/com/tailscale/ipn/MainActivity.kt @@ -5,12 +5,15 @@ package com.tailscale.ipn import android.annotation.SuppressLint import android.app.Activity +import android.app.AlertDialog import android.content.Context import android.content.Intent import android.content.RestrictionsManager import android.content.pm.ActivityInfo import android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE import android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Bundle import android.provider.Settings import android.util.Log @@ -129,8 +132,13 @@ class MainActivity : ComponentActivity() { vpnViewModel.setVpnPrepared(true) App.get().startVPN() } else { - Log.d("VpnPermission", "VPN permission denied") - vpnViewModel.setVpnPrepared(false) + if (isAnotherVpnActive(this)) { + Log.d("VpnPermission", "Another VPN is likely active") + showOtherVPNConflictDialog() + } else { + Log.d("VpnPermission", "Permission was denied by the user") + viewModel.setVpnPrepared(false) + } } } viewModel.setVpnPermissionLauncher(vpnPermissionLauncher) @@ -285,6 +293,34 @@ class MainActivity : ComponentActivity() { lifecycleScope.launch { Notifier.loginFinished.collect { _ -> loginQRCode.set(null) } } } + private fun showOtherVPNConflictDialog() { + AlertDialog.Builder(this) + .setTitle(R.string.vpn_permission_denied) + .setMessage(R.string.multiple_vpn_explainer) + .setPositiveButton(R.string.go_to_settings) { _, _ -> + // Intent to open the VPN settings + val intent = Intent(Settings.ACTION_VPN_SETTINGS) + startActivity(intent) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + fun isAnotherVpnActive(context: Context): Boolean { + val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + val activeNetwork = connectivityManager.activeNetwork + if (activeNetwork != null) { + val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) + if (networkCapabilities != null && + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + return true + } + } + return false + } + // Returns true if we should render a QR code instead of launching a browser // for login requests private fun useQRCodeLogin(): Boolean { diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt index 8bf07c0..e583c1a 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt @@ -100,8 +100,10 @@ class MainViewModel(private val vpnViewModel: VpnViewModel) : IpnViewModel() { val isOn = when { - currentState == State.Running || currentState == State.Starting -> true - previousState == State.NoState && currentState == State.Starting -> true + prepared && currentState == State.Running || currentState == State.Starting -> + true + previousState == State.NoState && currentState == State.Starting -> + true else -> false } @@ -182,7 +184,7 @@ private fun userStringRes(currentState: State?, previousState: State?, vpnPrepar currentState == State.NeedsMachineAuth -> R.string.needs_machine_auth currentState == State.Stopped -> R.string.stopped currentState == State.Starting -> R.string.starting - currentState == State.Running -> R.string.connected + currentState == State.Running -> if (vpnPrepared) R.string.connected else R.string.placeholder else -> R.string.placeholder } } diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 1f9a033..6d79df3 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -292,4 +292,11 @@ Health warnings No issues found Tailscale is operating normally. + + + VPN permission denied + Only one VPN can be active, and it appears another is already running. Before starting Tailscale, disable the other VPN. + Go to Settings + Cancel +