Skip to content

Commit 2c46913

Browse files
committed
Logic for detecting if there's an always-on VPN, be it the legacy Android system VPN, or another VPNService, that is preventing Orbot VPN from starting. Needs UI work...
1 parent 6164026 commit 2c46913

File tree

3 files changed

+70
-32
lines changed

3 files changed

+70
-32
lines changed

app/src/main/java/org/torproject/android/ui/connect/ConfigConnectionBottomSheet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ class ConfigConnectionBottomSheet :
300300
connectFrag.stopTorAndVpn()
301301
Thread.sleep(3000)
302302
}
303-
connectFrag.startTorAndVpn()
303+
connectFrag.attemptToStartTor()
304304
}
305305

306306
private fun selectRadioButtonFromPreference() {

app/src/main/java/org/torproject/android/ui/connect/ConnectFragment.kt

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.torproject.android.ui.connect
33
import android.app.AlarmManager
44
import android.content.Context
55
import android.content.Intent
6-
import android.net.VpnService
76
import android.os.Build
87
import android.os.Bundle
98
import android.view.LayoutInflater
@@ -26,12 +25,12 @@ import kotlinx.coroutines.launch
2625
import net.freehaven.tor.control.TorControlCommands
2726
import org.torproject.android.OrbotActivity
2827
import org.torproject.android.R
29-
import org.torproject.android.util.putNotSystem
3028
import org.torproject.android.util.sendIntentToService
3129
import org.torproject.android.databinding.FragmentConnectBinding
3230
import org.torproject.android.service.OrbotConstants
3331
import org.torproject.android.service.OrbotService
3432
import org.torproject.android.service.circumvention.Transport
33+
import org.torproject.android.service.vpn.VpnServicePrepareWrapper
3534
import org.torproject.android.util.Prefs
3635
import org.torproject.android.ui.OrbotMenuAction
3736
import org.torproject.jni.TorService
@@ -48,10 +47,21 @@ class ConnectFragment : Fragment(),
4847
private val lastStatus: String
4948
get() = (activity as? OrbotActivity)?.previousReceivedTorStatus ?: ""
5049

51-
private val startTorResultLauncher =
50+
private val startTorVpnResultLauncher =
5251
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
52+
// The user pressed OK, we can start Tor VPN
5353
if (result.resultCode == AppCompatActivity.RESULT_OK) {
54-
startTorAndVpn()
54+
startTorVpn()
55+
} else { /* this happens when:
56+
- the user cancels the system VPN dialog
57+
- the user is on Android S+ and has another VpnService based app
58+
set to be always-on.
59+
60+
we are unable to differentiate these two things, so show a generic error msg
61+
*/
62+
ignoreStartSwitchListener = true
63+
binding.switchConnect.isChecked = false
64+
displayVpnStartError(getString(R.string.unable_to_start_unknown_reason_error_msg))
5565
}
5666
}
5767

@@ -102,7 +112,7 @@ class ConnectFragment : Fragment(),
102112
viewLifecycleOwner.lifecycleScope.launch {
103113
viewModel.events.collect { event ->
104114
when (event) {
105-
is ConnectEvent.StartTorAndVpn -> startTorAndVpn()
115+
is ConnectEvent.StartTorAndVpn -> attemptToStartTor()
106116
is ConnectEvent.RefreshMenuList -> refreshMenuList(requireContext())
107117
}
108118
}
@@ -118,24 +128,27 @@ class ConnectFragment : Fragment(),
118128
}, DEFAULT_THROTTLE_INTERVAL)
119129
}
120130
binding.switchConnect.setOnCheckedChangeListener { _, value ->
131+
if (ignoreStartSwitchListener) {
132+
ignoreStartSwitchListener = false
133+
return@setOnCheckedChangeListener
134+
}
121135
if (value) {
122136
// display msg if optional outbound proxy config is invalid
123137
if (Prefs.outboundProxy.second != null) {
124-
125138
Toast.makeText(
126139
activity,
127140
getString(R.string.invalid_outbound_proxy_config),
128141
Toast.LENGTH_LONG
129142
).show()
130143
}
131-
startTorAndVpn()
132-
} else
133-
stopTorAndVpn()
144+
}
145+
attemptToStartTor()
134146
}
135147
refreshMenuList(requireContext())
136-
137148
}
138149

150+
private var ignoreStartSwitchListener = false
151+
139152
override fun onCreateView(
140153
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
141154
): View {
@@ -170,36 +183,56 @@ class ConnectFragment : Fragment(),
170183
}
171184
}
172185

173-
fun startTorAndVpn() {
174-
val vpnIntent = VpnService.prepare(requireActivity())?.putNotSystem()
175-
if (vpnIntent != null && !Prefs.isPowerUserMode) {
176-
// prompt VPN permission dialog
177-
startTorResultLauncher.launch(vpnIntent)
178-
} else { // either the vpn permission hasn't been granted or we are in power user mode
179-
Prefs.putUseVpn(!Prefs.isPowerUserMode)
180-
if (Prefs.isPowerUserMode) {
181-
// android 14 awkwardly needs this permission to be explicitly granted to use the
182-
// FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED permission without grabbing a VPN Intent
183-
val alarmManager =
184-
requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
185-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
186-
PowerUserForegroundPermDialog().createTransactionAndShow(requireActivity())
187-
return // user can try again after granting permission
188-
} else {
189-
binding.ivStatus.setImageResource(R.drawable.orbieon)
190-
}
186+
fun attemptToStartTorPowerUserMode() {
187+
// android 14 awkwardly needs this permission to be explicitly granted to use the
188+
// FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED permission without grabbing a VPN Intent
189+
val alarmManager =
190+
requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
191+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
192+
PowerUserForegroundPermDialog().createTransactionAndShow(requireActivity())
193+
return // user can try again after granting permission
194+
}
195+
doLayoutStarting(requireContext())
196+
setState(TorService.ACTION_START)
197+
}
198+
199+
fun startTorVpn() {
200+
doLayoutStarting(requireContext())
201+
setState(TorService.ACTION_START)
202+
}
203+
204+
fun attemptToStartTor() {
205+
Prefs.putUseVpn(!Prefs.isPowerUserMode)
206+
if (Prefs.isPowerUserMode) {
207+
attemptToStartTorPowerUserMode()
208+
} else {
209+
val vpnPrepareState =
210+
VpnServicePrepareWrapper.orbotVpnServicePreparedState(requireContext())
211+
when (vpnPrepareState) {
212+
is VpnServicePrepareWrapper.Result.Prepared ->
213+
startTorVpn()
214+
215+
is VpnServicePrepareWrapper.Result.CantPrepare ->
216+
displayVpnStartError(vpnPrepareState.errorMsg)
217+
218+
is VpnServicePrepareWrapper.Result.ShouldAttempt ->
219+
// prompt VPN permission dialog
220+
startTorVpnResultLauncher.launch(vpnPrepareState.prepareIntent)
221+
191222
}
192-
doLayoutStarting(requireContext())
193-
setState(TorService.ACTION_START)
194223
}
195224
refreshMenuList(requireContext())
196225
}
197226

227+
fun displayVpnStartError(msg: String) {
228+
// TODO to better UI
229+
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
230+
}
231+
198232
var lastState: String? = null
199233

200234
@Synchronized
201235
fun setState(newState: String) {
202-
203236
if (lastState != newState) {
204237
requireContext().sendIntentToService(newState)
205238
lastState = newState

app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,9 @@
299299
<string name="my_country_optional">My Country (optional)</string>
300300
<string name="limit_dns_tunnel_use">Limit DNS Tunnel Use</string>
301301
<string name="dns_tunnel_usage_description">DNS tunnels prioritize access over speed and may feel slower. We recommend enabling them only for essential apps. Video streaming is not recommended.</string>
302+
303+
<string name="unable_to_start_orbot_vpn_title">Unable to start Orbot VPN</string>
304+
<string name="unable_to_start_legacy_vpn_error_msg">There is a Custom VPN Profile set to be Always-on. Please disable it before starting Orbot.</string>
305+
<string name="unable_to_start_other_vpn_app_error_msg">The VPN app %1$s is set to be Always-on. Please disable it before starting Orbot.</string>
306+
<string name="unable_to_start_unknown_reason_error_msg">VPN permission was denied or another app has \"Always-on VPN\" enabled</string>
302307
</resources>

0 commit comments

Comments
 (0)