@@ -78,6 +78,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
78
78
// If connected/late-stage connecting, the proxy we're connected/trying to connect to. Otherwise null.
79
79
private var currentProxyConfig: ProxyConfig ? = activeVpnConfig()
80
80
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
+
81
85
override fun onCreate (savedInstanceState : Bundle ? ) {
82
86
super .onCreate(savedInstanceState)
83
87
@@ -141,6 +145,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
141
145
super .onPause()
142
146
Log .d(TAG , " onPause" )
143
147
app.clearScreen()
148
+ this .lastPauseTime = System .currentTimeMillis()
144
149
}
145
150
146
151
override fun onDestroy () {
@@ -511,6 +516,19 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
511
516
512
517
if (isVpnActive()) startVpn()
513
518
}
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()
514
532
} else {
515
533
Sentry .capture(" Non-OK result $resultCode for requestCode $requestCode " )
516
534
mainState = MainState .FAILED
@@ -730,6 +748,28 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
730
748
.show()
731
749
}
732
750
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
+
733
773
private fun tryStartActivity (intent : Intent ): Boolean {
734
774
return try {
735
775
startActivity(intent)
0 commit comments