summaryrefslogtreecommitdiffhomepage
path: root/android/lib/common
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-03-04 16:29:47 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-03-07 08:11:28 +0100
commit80d86298cc7bfac65577a2b07a0ca27693945eb4 (patch)
tree2e7dd8a899a74f28e6ac9cb8200418fd903847cc /android/lib/common
parent32fd95f7a81a3ab923838a29489af8336f3b6bf0 (diff)
downloadmullvadvpn-80d86298cc7bfac65577a2b07a0ca27693945eb4.tar.xz
mullvadvpn-80d86298cc7bfac65577a2b07a0ca27693945eb4.zip
Add documentation about detecting always_on_vpn_app
Only before Android 11 and on test builds (running from Android studio) it will report always-on vpn app.
Diffstat (limited to 'android/lib/common')
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt13
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt55
2 files changed, 39 insertions, 29 deletions
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
index 992ae9404d..882279c999 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
@@ -3,11 +3,8 @@ package net.mullvad.mullvadvpn.lib.common.util
import android.content.Context
import android.content.Intent
import android.net.Uri
-import android.provider.Settings
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
-private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
-
fun createAccountUri(accountUri: String, websiteAuthToken: WebsiteAuthToken?): Uri {
val urlString = buildString {
append(accountUri)
@@ -19,16 +16,6 @@ fun createAccountUri(accountUri: String, websiteAuthToken: WebsiteAuthToken?): U
return Uri.parse(urlString)
}
-// NOTE: This function will return the current Always-on VPN package's name. In case of either
-// Always-on VPN being disabled or not being able to read the state, NULL will be returned.
-fun Context.resolveAlwaysOnVpnPackageName(): String? {
- return try {
- Settings.Secure.getString(contentResolver, ALWAYS_ON_VPN_APP)
- } catch (ex: SecurityException) {
- null
- }
-}
-
fun Context.openVpnSettings() {
val intent = Intent("android.settings.VPN_SETTINGS")
startActivity(intent)
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
index 06c862936b..dfc70609e1 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
@@ -4,7 +4,10 @@ import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.net.VpnService.prepare
+import android.os.Build
import android.os.ParcelFileDescriptor
+import android.provider.Settings
+import androidx.annotation.DeprecatedSinceApi
import arrow.core.Either
import arrow.core.flatMap
import arrow.core.left
@@ -22,8 +25,9 @@ import net.mullvad.mullvadvpn.lib.model.Prepared
* Invoking VpnService.prepare() can result in 3 out comes:
* 1. IllegalStateException - There is a legacy VPN profile marked as always on
* 2. Intent
- * - A: Can-prepare - Create Vpn profile
- * - B: Always-on-VPN - Another Vpn Profile is marked as always on
+ * - A: Can-prepare - Create Vpn profile or Always-on-VPN is not detected in case of Android 11+
+ * - B: Always-on-VPN - Another Vpn Profile is marked as always on (Only available up to Android
+ * 11 or where testOnly is set, e.g builds from Android Studio)
* 3. null - The app has the VPN permission
*
* In case 1 and 2b, you don't know if you have a VPN profile or not.
@@ -44,25 +48,44 @@ fun Context.prepareVpnSafe(): Either<PrepareError, Prepared> =
if (intent == null) {
Prepared.right()
} else {
- val alwaysOnVpnApp = getAlwaysOnVpnAppName()
- if (alwaysOnVpnApp == null) {
- PrepareError.NotPrepared(intent).left()
- } else {
- PrepareError.OtherAlwaysOnApp(alwaysOnVpnApp).left()
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ val alwaysOnVpnApp = getOtherAlwaysOnVpnAppName()
+ if (alwaysOnVpnApp != null) {
+ return@flatMap PrepareError.OtherAlwaysOnApp(alwaysOnVpnApp).left()
+ }
}
+ return@flatMap PrepareError.NotPrepared(intent).left()
}
}
-fun Context.getAlwaysOnVpnAppName(): String? {
- return resolveAlwaysOnVpnPackageName()
- ?.let { currentAlwaysOnVpn ->
- packageManager.getInstalledPackagesList(0).singleOrNull {
- it.packageName == currentAlwaysOnVpn && it.packageName != packageName
- }
+private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
+
+// NOTE: This function will return the current Always-on VPN package's name. In case of either
+// Always-on VPN being disabled or not being able to read the state, null will be returned.
+//
+// Caveat: For Android 11+ it will always return null unless the app is a test build (e.g running
+// from Android Studio).
+@DeprecatedSinceApi(Build.VERSION_CODES.S)
+fun Context.getOtherAlwaysOnVpnAppName(): String? {
+ val currentAlwaysOnPackageName =
+ try {
+ Settings.Secure.getString(contentResolver, ALWAYS_ON_VPN_APP)
+ } catch (ex: SecurityException) {
+ return null
}
- ?.applicationInfo
- ?.loadLabel(packageManager)
- ?.toString()
+
+ // If we are the current Always-on VPN app, we return null
+ return if (currentAlwaysOnPackageName == packageName) {
+ null
+ } else {
+ // Resolve package name to app name
+ packageManager
+ .getInstalledPackagesList(0)
+ .firstOrNull { it.packageName == currentAlwaysOnPackageName }
+ ?.applicationInfo
+ ?.loadLabel(packageManager)
+ ?.toString()
+ }
}
/**