Skip to content

Commit 928fbf9

Browse files
committed
Detect setup issues due to other always-on VPNs
1 parent 16eff75 commit 928fbf9

File tree

1 file changed

+40
-0
lines changed

1 file changed

+40
-0
lines changed

app/src/main/java/tech/httptoolkit/android/MainActivity.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)