@@ -78,6 +78,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
7878 // If connected/late-stage connecting, the proxy we're connected/trying to connect to. Otherwise null.
7979 private var currentProxyConfig: ProxyConfig ? = activeVpnConfig()
8080
81+ // Used to track extremely fast VPN setup failures, indicating setup issues (rather than
82+ // manual user cancellation). Doesn't matter that it's not properly persistent.
83+ private var lastPauseTime = - 1L ;
84+
8185 override fun onCreate (savedInstanceState : Bundle ? ) {
8286 super .onCreate(savedInstanceState)
8387
@@ -141,6 +145,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
141145 super .onPause()
142146 Log .d(TAG , " onPause" )
143147 app.clearScreen()
148+ this .lastPauseTime = System .currentTimeMillis()
144149 }
145150
146151 override fun onDestroy () {
@@ -511,6 +516,19 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
511516
512517 if (isVpnActive()) startVpn()
513518 }
519+ } else if (
520+ requestCode == START_VPN_REQUEST &&
521+ System .currentTimeMillis() - lastPauseTime < 200 && // On Pixel 4a it takes < 50ms
522+ resultCode == Activity .RESULT_CANCELED
523+ ) {
524+ // If another always-on VPN is active, VPN start requests fail instantly as cancelled.
525+ // We can't check that the VPN is always-on, but given an instant failure that's
526+ // the likely cause, so we warn about it:
527+ showActiveVpnFailureAlert()
528+
529+ // Then go back to the disconnected state:
530+ mainState = MainState .DISCONNECTED
531+ updateUi()
514532 } else {
515533 Sentry .capture(" Non-OK result $resultCode for requestCode $requestCode " )
516534 mainState = MainState .FAILED
@@ -730,6 +748,28 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
730748 .show()
731749 }
732750
751+ private fun showActiveVpnFailureAlert () {
752+ MaterialAlertDialogBuilder (this )
753+ .setTitle(" VPN setup failed" )
754+ .setIcon(R .drawable.ic_exclamation_triangle)
755+ .setMessage(
756+ " HTTP Toolkit could not be configured as a VPN on your device." +
757+ " \n\n " +
758+ " This usually means you have an always-on VPN configured, which blocks " +
759+ " installation of other VPNs. To activate HTTP Toolkit you'll need to " +
760+ " deactivate that VPN first."
761+ )
762+ .setNegativeButton(" Cancel" ) { _, _ -> }
763+ .setPositiveButton(" Open VPN Settings" ) { _, _ ->
764+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
765+ startActivity(Intent (Settings .ACTION_VPN_SETTINGS ))
766+ } else {
767+ startActivity(Intent (Settings .ACTION_WIRELESS_SETTINGS ))
768+ }
769+ }
770+ .show()
771+ }
772+
733773 private fun tryStartActivity (intent : Intent ): Boolean {
734774 return try {
735775 startActivity(intent)
0 commit comments