summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-11-18 14:23:05 +0100
committerDavid Göransson <david.goransson@mullvad.net>2024-11-27 09:00:18 +0100
commit1bb7fc7ebaa2837ed9f9d28c2bb5a6fd91033988 (patch)
treea83565926fb753a2dd9fd24f0e2bd07262e4507e /android
parent0d155385e1cb7075012bd270de0398d83a438bc5 (diff)
downloadmullvadvpn-1bb7fc7ebaa2837ed9f9d28c2bb5a6fd91033988.tar.xz
mullvadvpn-1bb7fc7ebaa2837ed9f9d28c2bb5a6fd91033988.zip
Handle legacy always-on vpn profiles
Co-authored-by: Jonatan Rhodin <jonatan.rhodin@mullvad.net>
Diffstat (limited to 'android')
-rw-r--r--android/CHANGELOG.md1
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt140
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt55
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/CreateVpnProfile.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RequestVpnPermission.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt22
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt (renamed from android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnPermissionViewModel.kt)14
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt5
-rw-r--r--android/lib/common/build.gradle.kts3
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt2
-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/ErrorNotificationMessage.kt7
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt77
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt61
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt127
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PrepareError.kt17
-rw-r--r--android/lib/resource/src/main/res/values-da/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-de/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-es/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-fi/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-fr/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-it/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-ja/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-ko/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-my/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-nb/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-nl/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-pl/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-pt/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-ru/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-sv/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-th/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-tr/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-zh-rCN/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values-zh-rTW/strings.xml4
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml9
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt5
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt11
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnProfileUseCase.kt11
-rw-r--r--android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt14
-rw-r--r--android/lib/talpid/build.gradle.kts2
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt18
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt10
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt10
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt6
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt45
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt49
-rw-r--r--android/tile/build.gradle.kts1
-rw-r--r--android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt40
59 files changed, 518 insertions, 440 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 37562f4119..cae6231483 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -35,6 +35,7 @@ Line wrap the file at 100 chars. Th
- Fix a bug where the Android account expiry notifications would not be updated if the app was
running in the background for a long time.
- Fix ANR due to the tokio runtime being blocked by `getaddrinfo` when dropped.
+- Fix crash when having a legacy VPN profile as always-on.
## [android/2024.8] - 2024-11-01
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
index 487e739025..9087fe2f6c 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
@@ -241,7 +241,7 @@ class ConnectScreenTest {
}
// Assert
- onNodeWithText("FAILED TO SECURE CONNECTION").assertExists()
+ onNodeWithText("FAILED TO CONNECT").assertExists()
onNodeWithText(mockLocationName).assertExists()
onNodeWithText("Dismiss").assertExists()
onNodeWithText(text = "Critical error (your attention is required)", ignoreCase = true)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
index 538a746b99..bc1aaeb641 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
@@ -129,7 +130,7 @@ private fun Notification(notificationBannerData: NotificationData) {
},
)
Text(
- text = title.uppercase(),
+ text = title.toUpperCase(),
modifier =
Modifier.constrainAs(textTitle) {
top.linkTo(parent.top)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt
index 5318933852..97a7b9a483 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationData.kt
@@ -3,7 +3,6 @@ package net.mullvad.mullvadvpn.compose.component.notificationbanner
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
@@ -13,26 +12,29 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.core.text.HtmlCompat
+import java.net.InetAddress
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
-import net.mullvad.mullvadvpn.lib.common.util.getErrorNotificationResources
+import net.mullvad.mullvadvpn.lib.model.AuthFailedError
import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.ui.notification.StatusLevel
data class NotificationData(
- val title: String,
+ val title: AnnotatedString,
val message: AnnotatedString? = null,
val statusLevel: StatusLevel,
val action: NotificationAction? = null,
) {
constructor(
title: String,
- message: String?,
+ message: String? = null,
statusLevel: StatusLevel,
- action: NotificationAction?,
- ) : this(title, message?.let { AnnotatedString(it) }, statusLevel, action)
+ action: NotificationAction? = null,
+ ) : this(AnnotatedString(title), message?.let { AnnotatedString(it) }, statusLevel, action)
}
data class NotificationAction(
@@ -51,22 +53,11 @@ fun InAppNotification.toNotificationData(
when (this) {
is InAppNotification.NewDevice ->
NotificationData(
- title = stringResource(id = R.string.new_device_notification_title),
+ title =
+ AnnotatedString(stringResource(id = R.string.new_device_notification_title)),
message =
- HtmlCompat.fromHtml(
- stringResource(
- id = R.string.new_device_notification_message,
- deviceName,
- ),
- HtmlCompat.FROM_HTML_MODE_COMPACT,
- )
- .toAnnotatedString(
- boldSpanStyle =
- SpanStyle(
- color = MaterialTheme.colorScheme.onSurface,
- fontWeight = FontWeight.ExtraBold,
- )
- ),
+ stringResource(id = R.string.new_device_notification_message, deviceName)
+ .formatWithHtml(),
statusLevel = StatusLevel.Info,
action =
NotificationAction(
@@ -111,23 +102,94 @@ fun InAppNotification.toNotificationData(
@Composable
private fun errorMessageBannerData(error: ErrorState) =
- error.getErrorNotificationResources(LocalContext.current).run {
- NotificationData(
- title = stringResource(id = titleResourceId),
- message =
- HtmlCompat.fromHtml(
- optionalMessageArgument?.let { stringResource(id = messageResourceId, it) }
- ?: stringResource(id = messageResourceId),
- HtmlCompat.FROM_HTML_MODE_COMPACT,
- )
- .toAnnotatedString(
- boldSpanStyle =
- SpanStyle(
- color = MaterialTheme.colorScheme.onSurface,
- fontWeight = FontWeight.ExtraBold,
- )
- ),
- statusLevel = StatusLevel.Error,
- action = null,
+ NotificationData(
+ title = error.title().formatWithHtml(),
+ message = error.message().formatWithHtml(),
+ statusLevel = StatusLevel.Error,
+ action = null,
+ )
+
+@Composable
+private fun String.formatWithHtml(): AnnotatedString =
+ HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_COMPACT)
+ .toAnnotatedString(
+ boldSpanStyle =
+ SpanStyle(
+ color = MaterialTheme.colorScheme.onSurface,
+ fontWeight = FontWeight.ExtraBold,
+ )
)
+
+@Composable
+private fun ErrorState.title(): String {
+ val cause = this.cause
+ return when {
+ cause is ErrorStateCause.InvalidDnsServers -> stringResource(R.string.blocking_internet)
+ cause is ErrorStateCause.NotPrepared ->
+ stringResource(R.string.vpn_permission_error_notification_title)
+ cause is ErrorStateCause.OtherAlwaysOnApp ->
+ stringResource(R.string.always_on_vpn_error_notification_title, cause.appName)
+ cause is ErrorStateCause.OtherLegacyAlwaysOnApp ->
+ stringResource(R.string.legacy_always_on_vpn_error_notification_title)
+ isBlocking -> stringResource(R.string.blocking_internet)
+ else -> stringResource(R.string.critical_error)
}
+}
+
+@Composable
+private fun ErrorState.message(): String {
+ val cause = this.cause
+ return when {
+ isBlocking -> cause.errorMessageId()
+ else -> stringResource(R.string.failed_to_block_internet)
+ }
+}
+
+@Composable
+private fun ErrorStateCause.errorMessageId(): String =
+ when (this) {
+ is ErrorStateCause.AuthFailed -> stringResource(error.errorMessageId())
+ is ErrorStateCause.Ipv6Unavailable -> stringResource(R.string.ipv6_unavailable)
+ is ErrorStateCause.FirewallPolicyError -> stringResource(R.string.set_firewall_policy_error)
+ is ErrorStateCause.DnsError -> stringResource(R.string.set_dns_error)
+ is ErrorStateCause.StartTunnelError -> stringResource(R.string.start_tunnel_error)
+ is ErrorStateCause.IsOffline -> stringResource(R.string.is_offline)
+ is ErrorStateCause.TunnelParameterError -> stringResource(error.errorMessageId())
+ is ErrorStateCause.NotPrepared ->
+ stringResource(R.string.vpn_permission_error_notification_message)
+ is ErrorStateCause.OtherAlwaysOnApp ->
+ stringResource(R.string.always_on_vpn_error_notification_content, appName)
+ is ErrorStateCause.OtherLegacyAlwaysOnApp ->
+ stringResource(R.string.legacy_always_on_vpn_error_notification_content)
+ is ErrorStateCause.InvalidDnsServers ->
+ stringResource(
+ R.string.invalid_dns_servers,
+ addresses.joinToString { address -> address.addressString() },
+ )
+ }
+
+private fun AuthFailedError.errorMessageId(): Int =
+ when (this) {
+ AuthFailedError.ExpiredAccount -> R.string.account_credit_has_expired
+ AuthFailedError.InvalidAccount,
+ AuthFailedError.TooManyConnections,
+ AuthFailedError.Unknown -> R.string.auth_failed
+ }
+
+private fun ParameterGenerationError.errorMessageId(): Int =
+ when (this) {
+ ParameterGenerationError.NoMatchingRelay,
+ ParameterGenerationError.NoMatchingBridgeRelay -> {
+ R.string.no_matching_relay
+ }
+ ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key
+ ParameterGenerationError.CustomTunnelHostResultionError ->
+ R.string.custom_tunnel_host_resolution_error
+ }
+
+private fun InetAddress.addressString(): String {
+ val hostNameAndAddress = this.toString().split('/', limit = 2)
+ val address = hostNameAndAddress[1]
+
+ return address
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index c3640979d3..b143e72f25 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -61,7 +61,6 @@ import com.ramcosta.composedestinations.generated.destinations.SettingsDestinati
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.result.ResultRecipient
import kotlinx.coroutines.launch
-import mullvad_daemon.management_interface.tunnelState
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ConnectionButton
import net.mullvad.mullvadvpn.compose.button.SwitchLocationButton
@@ -84,8 +83,8 @@ import net.mullvad.mullvadvpn.compose.test.RECONNECT_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.transitions.HomeTransition
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
+import net.mullvad.mullvadvpn.compose.util.CreateVpnProfile
import net.mullvad.mullvadvpn.compose.util.OnNavResultValue
-import net.mullvad.mullvadvpn.compose.util.RequestVpnPermission
import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately
import net.mullvad.mullvadvpn.constant.SECURE_ZOOM
import net.mullvad.mullvadvpn.constant.SECURE_ZOOM_ANIMATION_MILLIS
@@ -100,6 +99,7 @@ import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
import net.mullvad.mullvadvpn.lib.model.LatLong
import net.mullvad.mullvadvpn.lib.model.Latitude
import net.mullvad.mullvadvpn.lib.model.Longitude
+import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -142,9 +142,9 @@ fun Connect(
val snackbarHostState = remember { SnackbarHostState() }
- val launchVpnPermission =
- rememberLauncherForActivityResult(RequestVpnPermission()) {
- connectViewModel.requestVpnPermissionResult(it)
+ val createVpnProfile =
+ rememberLauncherForActivityResult(CreateVpnProfile()) {
+ connectViewModel.createVpnProfileResult(it)
}
val openAccountPage = LocalUriHandler.current.createOpenAccountPageHook()
@@ -154,9 +154,8 @@ fun Connect(
minActiveState = Lifecycle.State.RESUMED,
) { sideEffect ->
when (sideEffect) {
- is ConnectViewModel.UiSideEffect.OpenAccountManagementPageInBrowser -> {
+ is ConnectViewModel.UiSideEffect.OpenAccountManagementPageInBrowser ->
openAccountPage(sideEffect.token)
- }
is ConnectViewModel.UiSideEffect.OutOfTime ->
navigator.navigate(OutOfTimeDestination) {
@@ -170,7 +169,24 @@ fun Connect(
popUpTo(NavGraphs.root) { inclusive = true }
}
- is ConnectViewModel.UiSideEffect.NoVpnPermission -> launchVpnPermission.launch(Unit)
+ is ConnectViewModel.UiSideEffect.NotPrepared ->
+ when (sideEffect.prepareError) {
+ is PrepareError.OtherLegacyAlwaysOnVpn ->
+ launch {
+ snackbarHostState.showSnackbarImmediately(
+ message = sideEffect.prepareError.toMessage(context)
+ )
+ }
+
+ is PrepareError.OtherAlwaysOnApp ->
+ launch {
+ snackbarHostState.showSnackbarImmediately(
+ message = sideEffect.prepareError.toMessage(context)
+ )
+ }
+ is PrepareError.NotPrepared ->
+ createVpnProfile.launch(sideEffect.prepareError.prepareIntent)
+ }
is ConnectViewModel.UiSideEffect.ConnectError ->
launch {
snackbarHostState.showSnackbarImmediately(
@@ -178,13 +194,12 @@ fun Connect(
)
}
- is ConnectViewModel.UiSideEffect.OpenUri -> {
+ is ConnectViewModel.UiSideEffect.OpenUri ->
try {
uriHandler.openUri(sideEffect.uri.toString())
} catch (e: IllegalArgumentException) {
Logger.w("Failed to open uri", e)
}
- }
}
}
@@ -581,15 +596,17 @@ fun GeoIpLocation.toLatLong() =
private fun ConnectViewModel.UiSideEffect.ConnectError.toMessage(context: Context): String =
when (this) {
- ConnectViewModel.UiSideEffect.ConnectError.NoVpnPermission ->
- context.getString(R.string.vpn_permission_denied_error)
-
- is ConnectViewModel.UiSideEffect.ConnectError.AlwaysOnVpn ->
- // Snackbar currently do not support annotated string
- context
- .getString(R.string.always_on_vpn_error_notification_content, appName)
- .removeHtmlTags()
-
ConnectViewModel.UiSideEffect.ConnectError.Generic ->
context.getString(R.string.error_occurred)
+
+ ConnectViewModel.UiSideEffect.ConnectError.PermissionDenied ->
+ context.getString(R.string.vpn_permission_denied_error)
}
+
+private fun PrepareError.OtherLegacyAlwaysOnVpn.toMessage(context: Context) =
+ context
+ .getString(R.string.always_on_vpn_error_notification_content, "Legacy app")
+ .removeHtmlTags()
+
+private fun PrepareError.OtherAlwaysOnApp.toMessage(context: Context) =
+ context.getString(R.string.always_on_vpn_error_notification_content, appName).removeHtmlTags()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt
index 332992c4e5..f52a4e8879 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt
@@ -7,9 +7,11 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.navigation.NavHostController
+import arrow.core.merge
import co.touchlab.kermit.Logger
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.generated.NavGraphs
@@ -17,11 +19,14 @@ import com.ramcosta.composedestinations.generated.destinations.NoDaemonDestinati
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.rememberNavHostEngine
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
-import net.mullvad.mullvadvpn.compose.util.RequestVpnPermission
+import net.mullvad.mullvadvpn.compose.util.CreateVpnProfile
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
+import net.mullvad.mullvadvpn.lib.model.PrepareError
+import net.mullvad.mullvadvpn.lib.model.Prepared
import net.mullvad.mullvadvpn.viewmodel.DaemonScreenEvent
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
-import net.mullvad.mullvadvpn.viewmodel.VpnPermissionSideEffect
-import net.mullvad.mullvadvpn.viewmodel.VpnPermissionViewModel
+import net.mullvad.mullvadvpn.viewmodel.VpnProfileSideEffect
+import net.mullvad.mullvadvpn.viewmodel.VpnProfileViewModel
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalComposeUiApi::class)
@@ -32,7 +37,7 @@ fun MullvadApp() {
val navigator: DestinationsNavigator = navHostController.rememberDestinationsNavigator()
val serviceVm = koinViewModel<NoDaemonViewModel>()
- val permissionVm = koinViewModel<VpnPermissionViewModel>()
+ val permissionVm = koinViewModel<VpnProfileViewModel>()
DisposableEffect(Unit) {
navHostController.addOnDestinationChangedListener(serviceVm)
@@ -64,11 +69,20 @@ fun MullvadApp() {
// Ask for VPN Permission
val launchVpnPermission =
- rememberLauncherForActivityResult(RequestVpnPermission()) { _ -> permissionVm.connect() }
+ rememberLauncherForActivityResult(CreateVpnProfile()) { _ -> permissionVm.connect() }
+ val context = LocalContext.current
LaunchedEffect(Unit) {
permissionVm.uiSideEffect.collect {
- if (it is VpnPermissionSideEffect.ShowDialog) {
- launchVpnPermission.launch(Unit)
+ if (it is VpnProfileSideEffect.RequestVpnProfile) {
+ val prepareResult = context.prepareVpnSafe().merge()
+ when (prepareResult) {
+ is PrepareError.NotPrepared ->
+ launchVpnPermission.launch(prepareResult.prepareIntent)
+ // If legacy or other always on connect at let daemon generate a error state
+ is PrepareError.OtherLegacyAlwaysOnVpn,
+ is PrepareError.OtherAlwaysOnApp,
+ Prepared -> permissionVm.connect()
+ }
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/CreateVpnProfile.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/CreateVpnProfile.kt
new file mode 100644
index 0000000000..750ca6485c
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/CreateVpnProfile.kt
@@ -0,0 +1,14 @@
+package net.mullvad.mullvadvpn.compose.util
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import androidx.activity.result.contract.ActivityResultContract
+
+class CreateVpnProfile : ActivityResultContract<Intent, Boolean>() {
+ override fun createIntent(context: Context, input: Intent): Intent = input
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
+ return resultCode == Activity.RESULT_OK
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RequestVpnPermission.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RequestVpnPermission.kt
deleted file mode 100644
index f198a3159c..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/RequestVpnPermission.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package net.mullvad.mullvadvpn.compose.util
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.net.VpnService
-import androidx.activity.result.contract.ActivityResultContract
-
-class RequestVpnPermission : ActivityResultContract<Unit, Boolean>() {
- override fun createIntent(context: Context, input: Unit): Intent {
- return VpnService.prepare(context)!!
- }
-
- override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
- return resultCode == Activity.RESULT_OK
- }
-
- // We expect this permission to only be requested when the permission is missing. However,
- // if it for some reason is called incorrectly we will skip the call to create intent
- // to avoid crashing. The app will then proceed as the user accepted the permission.
- override fun getSynchronousResult(context: Context, input: Unit): SynchronousResult<Boolean>? {
- return if (VpnService.prepare(context) == null) {
- SynchronousResult(true)
- } else {
- null
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt
index e8cd424156..3128870ae5 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt
@@ -13,7 +13,7 @@ import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.lib.shared.LocaleRepository
import net.mullvad.mullvadvpn.lib.shared.RelayLocationTranslationRepository
-import net.mullvad.mullvadvpn.lib.shared.VpnPermissionRepository
+import net.mullvad.mullvadvpn.lib.shared.VpnProfileUseCase
import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named
import org.koin.dsl.module
@@ -29,11 +29,13 @@ val appModule = module {
scope = MainScope(),
)
}
+
+ single { VpnProfileUseCase(androidContext()) }
+
single { BuildVersion(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) }
single { IntentProvider() }
single { AccountRepository(get(), get(), MainScope()) }
single { DeviceRepository(get()) }
- single { VpnPermissionRepository(androidContext()) }
single { ConnectionProxy(get(), get(), get()) }
single { LocaleRepository(get()) }
single { RelayLocationTranslationRepository(get(), get(), MainScope()) }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 1d62de5bb2..f43f1caf8f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -91,7 +91,7 @@ import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import net.mullvad.mullvadvpn.viewmodel.Udp2TcpSettingsViewModel
import net.mullvad.mullvadvpn.viewmodel.ViewLogsViewModel
import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
-import net.mullvad.mullvadvpn.viewmodel.VpnPermissionViewModel
+import net.mullvad.mullvadvpn.viewmodel.VpnProfileViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnSettingsViewModel
import net.mullvad.mullvadvpn.viewmodel.WelcomeViewModel
import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogViewModel
@@ -206,7 +206,6 @@ val uiModule = module {
get(),
get(),
get(),
- get(),
IS_PLAY_BUILD,
get(named(SELF_PACKAGE_NAME)),
)
@@ -237,7 +236,7 @@ val uiModule = module {
viewModel { DeleteCustomListConfirmationViewModel(get(), get()) }
viewModel { ServerIpOverridesViewModel(get(), get()) }
viewModel { ResetServerIpOverridesConfirmationViewModel(get()) }
- viewModel { VpnPermissionViewModel(get(), get()) }
+ viewModel { VpnProfileViewModel(get(), get()) }
viewModel { ApiAccessListViewModel(get()) }
viewModel { EditApiAccessMethodViewModel(get(), get(), get()) }
viewModel { SaveApiAccessMethodViewModel(get(), get()) }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt
index 9f153e724b..3ab3750c5e 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt
@@ -3,10 +3,10 @@ package net.mullvad.mullvadvpn.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.net.VpnService
import co.touchlab.kermit.Logger
import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
class BootCompletedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -16,7 +16,7 @@ class BootCompletedReceiver : BroadcastReceiver() {
}
private fun startAndConnectTunnel(context: Context) {
- val hasVpnPermission = VpnService.prepare(context) == null
+ val hasVpnPermission = context.prepareVpnSafe().isRight()
Logger.i("AutoStart on boot and connect, hasVpnPermission: $hasVpnPermission")
if (hasVpnPermission) {
val intent =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
index 5572f93961..42838d75d6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
@@ -21,12 +21,12 @@ import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.lib.model.ConnectError
import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
-import net.mullvad.mullvadvpn.lib.shared.VpnPermissionRepository
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.repository.NewDeviceRepository
import net.mullvad.mullvadvpn.usecase.LastKnownLocationUseCase
@@ -48,7 +48,6 @@ class ConnectViewModel(
private val paymentUseCase: PaymentUseCase,
private val connectionProxy: ConnectionProxy,
lastKnownLocationUseCase: LastKnownLocationUseCase,
- private val vpnPermissionRepository: VpnPermissionRepository,
private val resources: Resources,
private val isPlayBuild: Boolean,
private val packageName: String,
@@ -138,23 +137,20 @@ class ConnectViewModel(
viewModelScope.launch {
connectionProxy.connect().onLeft { connectError ->
when (connectError) {
- ConnectError.NoVpnPermission -> _uiSideEffect.send(UiSideEffect.NoVpnPermission)
- is ConnectError.Unknown -> {
- _uiSideEffect.send(UiSideEffect.ConnectError.Generic)
- }
+ is ConnectError.Unknown -> _uiSideEffect.send(UiSideEffect.ConnectError.Generic)
+ is ConnectError.NotPrepared ->
+ _uiSideEffect.send(UiSideEffect.NotPrepared(connectError.error))
}
}
}
}
- fun requestVpnPermissionResult(hasVpnPermission: Boolean) {
+ fun createVpnProfileResult(hasVpnPermission: Boolean) {
viewModelScope.launch {
if (hasVpnPermission) {
connectionProxy.connect()
} else {
- vpnPermissionRepository.getAlwaysOnVpnAppName()?.let {
- _uiSideEffect.send(UiSideEffect.ConnectError.AlwaysOnVpn(it))
- } ?: _uiSideEffect.send(UiSideEffect.ConnectError.NoVpnPermission)
+ _uiSideEffect.send(UiSideEffect.ConnectError.PermissionDenied)
}
}
}
@@ -206,14 +202,12 @@ class ConnectViewModel(
data object RevokedDevice : UiSideEffect
- data object NoVpnPermission : UiSideEffect
+ data class NotPrepared(val prepareError: PrepareError) : UiSideEffect
sealed interface ConnectError : UiSideEffect {
data object Generic : ConnectError
- data object NoVpnPermission : ConnectError
-
- data class AlwaysOnVpn(val appName: String) : ConnectError
+ data object PermissionDenied : ConnectError
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnPermissionViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt
index 1e5972b538..cb1a2862bf 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnPermissionViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt
@@ -9,19 +9,19 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
-import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PERMISSION
+import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PROFILE
import net.mullvad.mullvadvpn.lib.intent.IntentProvider
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
-class VpnPermissionViewModel(
+class VpnProfileViewModel(
intentProvider: IntentProvider,
private val connectionProxy: ConnectionProxy,
) : ViewModel() {
- val uiSideEffect: Flow<VpnPermissionSideEffect> =
+ val uiSideEffect: Flow<VpnProfileSideEffect> =
intentProvider.intents
- .filter { it?.action == KEY_REQUEST_VPN_PERMISSION }
+ .filter { it?.action == KEY_REQUEST_VPN_PROFILE }
.distinctUntilChanged()
- .map { VpnPermissionSideEffect.ShowDialog }
+ .map { VpnProfileSideEffect.RequestVpnProfile }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
fun connect() {
@@ -29,6 +29,6 @@ class VpnPermissionViewModel(
}
}
-sealed interface VpnPermissionSideEffect {
- data object ShowDialog : VpnPermissionSideEffect
+sealed interface VpnProfileSideEffect {
+ data object RequestVpnProfile : VpnProfileSideEffect
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
index 3dada2a433..0d61b3e300 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
@@ -30,7 +30,6 @@ import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
-import net.mullvad.mullvadvpn.lib.shared.VpnPermissionRepository
import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
@@ -87,9 +86,6 @@ class ConnectViewModelTest {
// Last known location
private val mockLastKnownLocationUseCase: LastKnownLocationUseCase = mockk()
- // VpnPermissionRepository
- private val mockVpnPermissionRepository: VpnPermissionRepository = mockk(relaxed = true)
-
@BeforeEach
fun setup() {
every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
@@ -124,7 +120,6 @@ class ConnectViewModelTest {
selectedLocationTitleUseCase = mockSelectedLocationTitleUseCase,
connectionProxy = mockConnectionProxy,
lastKnownLocationUseCase = mockLastKnownLocationUseCase,
- vpnPermissionRepository = mockVpnPermissionRepository,
resources = mockk(),
isPlayBuild = false,
packageName = "net.mullvad.mullvadvpn",
diff --git a/android/lib/common/build.gradle.kts b/android/lib/common/build.gradle.kts
index 2c4fbe0233..c8554b52c7 100644
--- a/android/lib/common/build.gradle.kts
+++ b/android/lib/common/build.gradle.kts
@@ -31,10 +31,11 @@ android {
dependencies {
implementation(projects.lib.model)
implementation(projects.lib.resource)
- implementation(projects.lib.talpid)
+ implementation(libs.arrow)
implementation(libs.androidx.appcompat)
implementation(libs.jodatime)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.kermit)
}
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
index ea420f2d0a..76f71d82e3 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
@@ -3,4 +3,4 @@ package net.mullvad.mullvadvpn.lib.common.constant
// Actions
const val KEY_CONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.connect_action"
const val KEY_DISCONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.disconnect_action"
-const val KEY_REQUEST_VPN_PERMISSION = "$MULLVAD_PACKAGE_NAME.request_vpn_permission"
+const val KEY_REQUEST_VPN_PROFILE = "$MULLVAD_PACKAGE_NAME.request_vpn_profile"
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 7ea74edfaa..992ae9404d 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
@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
-import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
@@ -20,18 +19,6 @@ fun createAccountUri(accountUri: String, websiteAuthToken: WebsiteAuthToken?): U
return Uri.parse(urlString)
}
-fun Context.getAlwaysOnVpnAppName(): String? {
- return resolveAlwaysOnVpnPackageName()
- ?.let { currentAlwaysOnVpn ->
- packageManager.getInstalledPackagesList(0).singleOrNull {
- it.packageName == currentAlwaysOnVpn && it.packageName != packageName
- }
- }
- ?.applicationInfo
- ?.loadLabel(packageManager)
- ?.toString()
-}
-
// 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? {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorNotificationMessage.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorNotificationMessage.kt
deleted file mode 100644
index 4a5c902d96..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorNotificationMessage.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-data class ErrorNotificationMessage(
- val titleResourceId: Int,
- val messageResourceId: Int,
- val optionalMessageArgument: String? = null,
-)
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
deleted file mode 100644
index a61ec10c17..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-import android.content.Context
-import net.mullvad.mullvadvpn.lib.common.R
-import net.mullvad.mullvadvpn.lib.model.AuthFailedError
-import net.mullvad.mullvadvpn.lib.model.ErrorState
-import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
-import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
-import net.mullvad.talpid.util.addressString
-
-fun ErrorState.getErrorNotificationResources(context: Context): ErrorNotificationMessage {
- return when {
- cause is ErrorStateCause.InvalidDnsServers -> {
- ErrorNotificationMessage(
- R.string.blocking_internet,
- cause.errorMessageId(),
- (cause as ErrorStateCause.InvalidDnsServers).addresses.joinToString { address ->
- address.addressString()
- },
- )
- }
- cause is ErrorStateCause.VpnPermissionDenied -> {
- resolveAlwaysOnVpnErrorNotificationMessage(context.getAlwaysOnVpnAppName())
- }
- isBlocking -> ErrorNotificationMessage(R.string.blocking_internet, cause.errorMessageId())
- else -> ErrorNotificationMessage(R.string.critical_error, R.string.failed_to_block_internet)
- }
-}
-
-private fun resolveAlwaysOnVpnErrorNotificationMessage(
- alwaysOnVpnAppName: String?
-): ErrorNotificationMessage {
- return if (alwaysOnVpnAppName != null) {
- ErrorNotificationMessage(
- R.string.always_on_vpn_error_notification_title,
- R.string.always_on_vpn_error_notification_content,
- alwaysOnVpnAppName,
- )
- } else {
- ErrorNotificationMessage(
- R.string.vpn_permission_error_notification_title,
- R.string.vpn_permission_error_notification_message,
- )
- }
-}
-
-fun ErrorStateCause.errorMessageId(): Int =
- when (this) {
- is ErrorStateCause.InvalidDnsServers -> R.string.invalid_dns_servers
- is ErrorStateCause.AuthFailed -> error.errorMessageId()
- is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
- is ErrorStateCause.FirewallPolicyError -> R.string.set_firewall_policy_error
- is ErrorStateCause.DnsError -> R.string.set_dns_error
- is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
- is ErrorStateCause.IsOffline -> R.string.is_offline
- is ErrorStateCause.TunnelParameterError -> {
- when (error) {
- ParameterGenerationError.NoMatchingRelay,
- ParameterGenerationError.NoMatchingBridgeRelay -> {
- R.string.no_matching_relay
- }
- ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key
- ParameterGenerationError.CustomTunnelHostResultionError -> {
- R.string.custom_tunnel_host_resolution_error
- }
- }
- }
- is ErrorStateCause.VpnPermissionDenied -> R.string.vpn_permission_denied_error
- }
-
-fun AuthFailedError.errorMessageId(): Int =
- when (this) {
- AuthFailedError.ExpiredAccount -> R.string.account_credit_has_expired
- AuthFailedError.InvalidAccount,
- AuthFailedError.TooManyConnections,
- AuthFailedError.Unknown -> R.string.auth_failed
- }
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
new file mode 100644
index 0000000000..59833cb396
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
@@ -0,0 +1,61 @@
+package net.mullvad.mullvadvpn.lib.common.util
+
+import android.content.Context
+import android.content.Intent
+import android.net.VpnService.prepare
+import arrow.core.Either
+import arrow.core.flatten
+import arrow.core.left
+import arrow.core.right
+import co.touchlab.kermit.Logger
+import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
+import net.mullvad.mullvadvpn.lib.model.PrepareError
+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
+ * 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.
+ */
+fun Context.prepareVpnSafe(): Either<PrepareError, Prepared> =
+ Either.catch {
+ val intent: Intent? = prepare(this)
+ intent
+ }
+ .mapLeft {
+ Logger.e("VpnService.prepare() failed: $it")
+ when (it) {
+ is IllegalStateException -> PrepareError.OtherLegacyAlwaysOnVpn
+ else -> throw it
+ }
+ }
+ .map { intent ->
+ if (intent == null) {
+ Prepared.right()
+ } else {
+ val alwaysOnVpnApp = getAlwaysOnVpnAppName()
+ if (alwaysOnVpnApp == null) {
+ PrepareError.NotPrepared(intent).left()
+ } else {
+ PrepareError.OtherAlwaysOnApp(alwaysOnVpnApp).left()
+ }
+ }
+ }
+ .flatten()
+
+fun Context.getAlwaysOnVpnAppName(): String? {
+ return resolveAlwaysOnVpnPackageName()
+ ?.let { currentAlwaysOnVpn ->
+ packageManager.getInstalledPackagesList(0).singleOrNull {
+ it.packageName == currentAlwaysOnVpn && it.packageName != packageName
+ }
+ }
+ ?.applicationInfo
+ ?.loadLabel(packageManager)
+ ?.toString()
+}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index fe0222596b..0412871f43 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -35,6 +35,9 @@ import net.mullvad.mullvadvpn.lib.model.DnsState
import net.mullvad.mullvadvpn.lib.model.Endpoint
import net.mullvad.mullvadvpn.lib.model.ErrorState
import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause.AuthFailed
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause.OtherAlwaysOnApp
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause.TunnelParameterError
import net.mullvad.mullvadvpn.lib.model.FeatureIndicator
import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
import net.mullvad.mullvadvpn.lib.model.GeoLocationId
@@ -76,49 +79,78 @@ import org.joda.time.Instant
internal fun ManagementInterface.TunnelState.toDomain(): TunnelState =
when (stateCase!!) {
- ManagementInterface.TunnelState.StateCase.DISCONNECTED ->
- TunnelState.Disconnected(
- location =
- with(disconnected) {
- if (hasDisconnectedLocation()) {
- disconnectedLocation.toDomain()
- } else null
- }
- )
- ManagementInterface.TunnelState.StateCase.CONNECTING ->
- TunnelState.Connecting(
- endpoint = connecting.relayInfo.tunnelEndpoint.toDomain(),
- location =
- with(connecting.relayInfo) {
- if (hasLocation()) {
- location.toDomain()
- } else null
- },
- featureIndicators = connecting.featureIndicators.toDomain(),
- )
- ManagementInterface.TunnelState.StateCase.CONNECTED ->
- TunnelState.Connected(
- endpoint = connected.relayInfo.tunnelEndpoint.toDomain(),
- location =
- with(connected.relayInfo) {
- if (hasLocation()) {
- location.toDomain()
- } else {
- null
- }
- },
- featureIndicators = connected.featureIndicators.toDomain(),
- )
- ManagementInterface.TunnelState.StateCase.DISCONNECTING ->
- TunnelState.Disconnecting(
- actionAfterDisconnect = disconnecting.afterDisconnect.toDomain()
- )
- ManagementInterface.TunnelState.StateCase.ERROR ->
- TunnelState.Error(errorState = error.errorState.toDomain())
+ ManagementInterface.TunnelState.StateCase.DISCONNECTED -> disconnected.toDomain()
+ ManagementInterface.TunnelState.StateCase.CONNECTING -> connecting.toDomain()
+ ManagementInterface.TunnelState.StateCase.CONNECTED -> connected.toDomain()
+ ManagementInterface.TunnelState.StateCase.DISCONNECTING -> disconnecting.toDomain()
+ ManagementInterface.TunnelState.StateCase.ERROR -> error.toDomain()
ManagementInterface.TunnelState.StateCase.STATE_NOT_SET ->
TunnelState.Disconnected(location = disconnected.disconnectedLocation.toDomain())
}
+private fun ManagementInterface.TunnelState.Connecting.toDomain(): TunnelState.Connecting =
+ TunnelState.Connecting(
+ endpoint = relayInfo.tunnelEndpoint.toDomain(),
+ location =
+ if (relayInfo.hasLocation()) {
+ relayInfo.location.toDomain()
+ } else null,
+ featureIndicators = featureIndicators.toDomain(),
+ )
+
+private fun ManagementInterface.TunnelState.Disconnected.toDomain(): TunnelState.Disconnected =
+ TunnelState.Disconnected(
+ location =
+ if (hasDisconnectedLocation()) {
+ disconnectedLocation.toDomain()
+ } else null
+ )
+
+private fun ManagementInterface.TunnelState.Connected.toDomain(): TunnelState.Connected =
+ TunnelState.Connected(
+ endpoint = relayInfo.tunnelEndpoint.toDomain(),
+ location =
+ if (relayInfo.hasLocation()) {
+ relayInfo.location.toDomain()
+ } else {
+ null
+ },
+ featureIndicators = featureIndicators.toDomain(),
+ )
+
+private fun ManagementInterface.TunnelState.Disconnecting.toDomain(): TunnelState.Disconnecting =
+ TunnelState.Disconnecting(actionAfterDisconnect = afterDisconnect.toDomain())
+
+private fun ManagementInterface.TunnelState.Error.toDomain(): TunnelState.Error {
+ val otherAlwaysOnAppError =
+ errorState.let {
+ if (it.hasOtherAlwaysOnAppError()) {
+ OtherAlwaysOnApp(it.otherAlwaysOnAppError.appName)
+ } else {
+ null
+ }
+ }
+
+ val invalidDnsServers =
+ errorState.let {
+ if (it.hasInvalidDnsServersError()) {
+ ErrorStateCause.InvalidDnsServers(
+ it.invalidDnsServersError.ipAddrsList.toList().map { InetAddress.getByName(it) }
+ )
+ } else {
+ null
+ }
+ }
+
+ return TunnelState.Error(
+ errorState =
+ errorState.toDomain(
+ otherAlwaysOnApp = otherAlwaysOnAppError,
+ invalidDnsServers = invalidDnsServers,
+ )
+ )
+}
+
internal fun ManagementInterface.GeoIpLocation.toDomain(): GeoIpLocation =
GeoIpLocation(
ipv4 =
@@ -198,12 +230,15 @@ internal fun ManagementInterface.AfterDisconnect.toDomain(): ActionAfterDisconne
throw IllegalArgumentException("Unrecognized action after disconnect")
}
-internal fun ManagementInterface.ErrorState.toDomain(): ErrorState =
+internal fun ManagementInterface.ErrorState.toDomain(
+ otherAlwaysOnApp: ErrorStateCause.OtherAlwaysOnApp?,
+ invalidDnsServers: ErrorStateCause.InvalidDnsServers?,
+): ErrorState =
ErrorState(
cause =
when (cause!!) {
ManagementInterface.ErrorState.Cause.AUTH_FAILED ->
- ErrorStateCause.AuthFailed(authFailedError.toDomain())
+ AuthFailed(authFailedError.toDomain())
ManagementInterface.ErrorState.Cause.IPV6_UNAVAILABLE ->
ErrorStateCause.Ipv6Unavailable
ManagementInterface.ErrorState.Cause.SET_FIREWALL_POLICY_ERROR ->
@@ -212,16 +247,20 @@ internal fun ManagementInterface.ErrorState.toDomain(): ErrorState =
ManagementInterface.ErrorState.Cause.START_TUNNEL_ERROR ->
ErrorStateCause.StartTunnelError
ManagementInterface.ErrorState.Cause.TUNNEL_PARAMETER_ERROR ->
- ErrorStateCause.TunnelParameterError(parameterError.toDomain())
+ TunnelParameterError(parameterError.toDomain())
ManagementInterface.ErrorState.Cause.IS_OFFLINE -> ErrorStateCause.IsOffline
- ManagementInterface.ErrorState.Cause.VPN_PERMISSION_DENIED ->
- ErrorStateCause.VpnPermissionDenied
ManagementInterface.ErrorState.Cause.SPLIT_TUNNEL_ERROR ->
ErrorStateCause.StartTunnelError
ManagementInterface.ErrorState.Cause.UNRECOGNIZED,
ManagementInterface.ErrorState.Cause.NEED_FULL_DISK_PERMISSIONS,
ManagementInterface.ErrorState.Cause.CREATE_TUNNEL_DEVICE ->
throw IllegalArgumentException("Unrecognized error state cause")
+
+ ManagementInterface.ErrorState.Cause.NOT_PREPARED -> ErrorStateCause.NotPrepared
+ ManagementInterface.ErrorState.Cause.OTHER_ALWAYS_ON_APP -> otherAlwaysOnApp!!
+ ManagementInterface.ErrorState.Cause.OTHER_LEGACY_ALWAYS_ON_VPN ->
+ ErrorStateCause.OtherLegacyAlwaysOnApp
+ ManagementInterface.ErrorState.Cause.INVALID_DNS_SERVERS -> invalidDnsServers!!
},
isBlocking = !hasBlockingError(),
)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt
index 307a235314..6feeeee579 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt
@@ -3,5 +3,5 @@ package net.mullvad.mullvadvpn.lib.model
sealed interface ConnectError {
data class Unknown(val throwable: Throwable) : ConnectError
- data object NoVpnPermission : ConnectError
+ data class NotPrepared(val error: PrepareError) : ConnectError
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt
index ef5947c89a..fdb7dd3a1a 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt
@@ -17,7 +17,6 @@ sealed class ErrorStateCause {
data object DnsError : ErrorStateCause()
- // Regression
data class InvalidDnsServers(val addresses: List<InetAddress>) : ErrorStateCause()
data object StartTunnelError : ErrorStateCause()
@@ -26,7 +25,11 @@ sealed class ErrorStateCause {
data object IsOffline : ErrorStateCause()
- data object VpnPermissionDenied : ErrorStateCause()
+ data object NotPrepared : ErrorStateCause()
+
+ data class OtherAlwaysOnApp(val appName: String) : ErrorStateCause()
+
+ data object OtherLegacyAlwaysOnApp : ErrorStateCause()
}
sealed interface AuthFailedError {
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt
index ec938a9fbf..141fe739f5 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt
@@ -15,6 +15,6 @@ sealed interface NotificationAction {
data object Dismiss : Tunnel
- data object RequestPermission : Tunnel
+ data object RequestVpnProfile : Tunnel
}
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt
index fffe86c247..3ca573a839 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.lib.model
sealed interface NotificationTunnelState {
- data class Disconnected(val hasVpnPermission: Boolean) : NotificationTunnelState
+ data class Disconnected(val prepareError: PrepareError?) : NotificationTunnelState
data object Connecting : NotificationTunnelState
@@ -18,7 +18,9 @@ sealed interface NotificationTunnelState {
data object VpnPermissionDenied : Error
- data object AlwaysOnVpn : Error
+ data class AlwaysOnVpn(val appName: String) : Error
+
+ data object LegacyLockdown : Error
data object Critical : Error
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PrepareError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PrepareError.kt
new file mode 100644
index 0000000000..8954c5f98a
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PrepareError.kt
@@ -0,0 +1,17 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.content.Intent
+
+sealed interface PrepareResult
+
+sealed interface PrepareError : PrepareResult {
+ // Result from VpnService.prepare() being invoked with legacy VPN app has always-on
+ data object OtherLegacyAlwaysOnVpn : PrepareError
+
+ // Prepare gives intent but there is other always VPN app
+ data class OtherAlwaysOnApp(val appName: String) : PrepareError
+
+ data class NotPrepared(val prepareIntent: Intent) : PrepareError
+}
+
+data object Prepared : PrepareResult
diff --git a/android/lib/resource/src/main/res/values-da/strings.xml b/android/lib/resource/src/main/res/values-da/strings.xml
index 88c135c304..b3cc919ca4 100644
--- a/android/lib/resource/src/main/res/values-da/strings.xml
+++ b/android/lib/resource/src/main/res/values-da/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Alle placeringer</string>
<string name="all_providers">Alle udbydere</string>
<string name="always_on_vpn_error_notification_content">Kunne ikke starte tunnelforbindelse. Deaktiver Altid-til VPN for &lt;b&gt;%1$s&lt;/b&gt;.</string>
- <string name="always_on_vpn_error_notification_title">Altid-til VPN tildelt en anden app</string>
<string name="any">Enhver</string>
<string name="api_access_description">Administrer og tilføj brugerdefinerede metoder for at få adgang til Mullvad API.</string>
<string name="api_access_method_info_first_line">Appen skal kommunikere med en Mullvad API-server for at kunne logge dig på, hente serverlister og andre kritiske operationer.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Indtast kuponkode</string>
<string name="entry">Indgang</string>
<string name="error_occurred">Der opstod en fejl.</string>
- <string name="error_state">KUNNE IKKE SIKRE FORBINDELSEN</string>
<string name="exclude_applications">Ekskluderede applikationer</string>
<string name="exit">Udgang</string>
<string name="failed_to_block_internet">Kan ikke blokere al netværkstrafik. Udfør fejlfinding, eller indsend en problemrapport.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Kuponkode er ugyldig.</string>
<string name="ipv6_unavailable">Kunne ikke konfigurere IPv6. Deaktiver det i appen, eller aktiver det på din enhed.</string>
<string name="is_offline">Din enhed er offline. Tunnelen vil oprette forbindelse automatisk, når din enhed er online igen.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Altid-til VPN tildelt en anden app</string>
<string name="less_than_a_day_left">mindre end én dag tilbage</string>
<string name="less_than_one_day">mindre end én dag</string>
<string name="list_name">Listenavn</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Det ser ud til, at du har indtastet et kontonummer i stedet for en rabatkuponkode. Hvis du vil ændre den aktive konto, skal du først logge ud.</string>
<string name="voucher_success_title">Indløsning af kuponen lykkedes.</string>
<string name="vpn_permission_denied_error">VPN-tilladelse blev nægtet, da tunnelen blev oprettet. Prøv at oprette forbindelse igen.</string>
- <string name="vpn_permission_error_notification_message">Altid-til VPN er måske aktiveret for en anden app</string>
<string name="vpn_permission_error_notification_title">VPN-tilladelsesfejl</string>
<string name="we_will_look_into_this">Vi vil undersøge dette.</string>
<string name="wireguard_custon_port_title">Brugerdefineret</string>
diff --git a/android/lib/resource/src/main/res/values-de/strings.xml b/android/lib/resource/src/main/res/values-de/strings.xml
index fdaceb2899..74331acc9a 100644
--- a/android/lib/resource/src/main/res/values-de/strings.xml
+++ b/android/lib/resource/src/main/res/values-de/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Alle Standorte</string>
<string name="all_providers">Alle Anbieter</string>
<string name="always_on_vpn_error_notification_content">Tunnelverbindung kann nicht gestartet werden. Bitte deaktivieren Sie Always-on VPN für &lt;b&gt;%1$s&lt;/b&gt;, bevor Sie Mullvad VPN verwenden.</string>
- <string name="always_on_vpn_error_notification_title">Always-on VPN ist einer anderen App zugeordnet</string>
<string name="any">Beliebige</string>
<string name="api_access_description">Verwaltung und Hinzufügen benutzerdefinierter Methoden für den Zugriff auf die Mullvad-API.</string>
<string name="api_access_method_info_first_line">Die App muss mit einem Mullvad API-Server kommunizieren, um Sie anzumelden, Serverlisten abzurufen und andere wichtige Vorgänge durchzuführen.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Gutscheincode eingeben</string>
<string name="entry">Eingang</string>
<string name="error_occurred">Ein Fehler ist aufgetreten.</string>
- <string name="error_state">SICHERE VERBINDUNG KONNTE NICHT HERGESTELLT WERDEN</string>
<string name="exclude_applications">Ausgeschlossene Anwendungen</string>
<string name="exit">Ausgang</string>
<string name="failed_to_block_internet">Der Netzwerk-Traffic konnte nicht gänzlich blockiert werden. Bitte beheben Sie den Fehler oder senden Sie einen Problembericht.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Der Gutscheincode ist ungültig.</string>
<string name="ipv6_unavailable">IPv6 konnte nicht konfiguriert werden. Deaktivieren Sie es in der App oder aktivieren Sie es auf Ihrem Gerät.</string>
<string name="is_offline">Ihr Gerät ist offline. Der Tunnel wird automatisch verbunden, sobald Ihr Gerät wieder online ist.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Always-on VPN ist einer anderen App zugeordnet</string>
<string name="less_than_a_day_left">weniger als ein Tag übrig</string>
<string name="less_than_one_day">weniger als ein Tag</string>
<string name="list_name">Name der Liste</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Anscheinend haben Sie eine Kontonummer statt eines Gutscheincodes eingegeben. Wenn Sie das aktive Konto wechseln möchten, melden Sie sich bitte zuerst ab.</string>
<string name="voucher_success_title">Der Gutschein wurde erfolgreich eingelöst.</string>
<string name="vpn_permission_denied_error">VPN-Berechtigungen wurden beim Erstellen des Tunnels abgelehnt.</string>
- <string name="vpn_permission_error_notification_message">Always-on VPN könnte für eine andere App aktiviert sein</string>
<string name="vpn_permission_error_notification_title">VPN-Berechtigungsfehler</string>
<string name="we_will_look_into_this">Wir werden uns das anschauen.</string>
<string name="wireguard_custon_port_title">Benutzerdefiniert</string>
diff --git a/android/lib/resource/src/main/res/values-es/strings.xml b/android/lib/resource/src/main/res/values-es/strings.xml
index fb5981905b..512f3a1982 100644
--- a/android/lib/resource/src/main/res/values-es/strings.xml
+++ b/android/lib/resource/src/main/res/values-es/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Todas las ubicaciones</string>
<string name="all_providers">Todos los proveedores</string>
<string name="always_on_vpn_error_notification_content">No se puede iniciar la conexión de túnel. Deshabilite la VPN siempre activa en &lt;b&gt;%1$s&lt;/b&gt; antes de utilizar la VPN de Mullvad.</string>
- <string name="always_on_vpn_error_notification_title">La VPN siempre activa se ha asignado a otra aplicación</string>
<string name="any">Cualquiera</string>
<string name="api_access_description">Gestione y añada métodos personalizados para acceder a la API de Mullvad.</string>
<string name="api_access_method_info_first_line">La aplicación necesita comunicarse con un servidor API de Mullvad para iniciar su sesión, obtener las listas de servidores y otras operaciones críticas.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Escriba el código del cupón</string>
<string name="entry">Entrada</string>
<string name="error_occurred">Se produjo un error.</string>
- <string name="error_state">NO SE PUDO PROTEGER LA CONEXIÓN</string>
<string name="exclude_applications">Aplicaciones excluidas</string>
<string name="exit">Salida</string>
<string name="failed_to_block_internet">No se puede bloquear todo el tráfico de red. Intente solucionar el problema o envíe un informe de problemas.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">El código del cupón no es válido.</string>
<string name="ipv6_unavailable">No se pudo configurar IPv6. Desactívelo en la aplicación o actívelo en el dispositivo.</string>
<string name="is_offline">El dispositivo está sin conexión. El túnel se conectará automáticamente cuando el dispositivo vuelva a estar en línea.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">La VPN siempre activa se ha asignado a otra aplicación</string>
<string name="less_than_a_day_left">queda menos de un día</string>
<string name="less_than_one_day">menos de un día</string>
<string name="list_name">Nombre de la lista</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Parece que ha introducido un número de cuenta en lugar de un código de cupón. Si desea cambiar la cuenta activa, cierre primero la sesión.</string>
<string name="voucher_success_title">El cupón se canjeó correctamente.</string>
<string name="vpn_permission_denied_error">Se denegó el permiso para usar una conexión VPN al crear el túnel. Intente volver a establecer la conexión.</string>
- <string name="vpn_permission_error_notification_message">La VPN siempre activa podría estar habilitada en otra aplicación</string>
<string name="vpn_permission_error_notification_title">Error en la autorización de la VPN</string>
<string name="we_will_look_into_this">Revisaremos esto.</string>
<string name="wireguard_custon_port_title">Personalizado</string>
diff --git a/android/lib/resource/src/main/res/values-fi/strings.xml b/android/lib/resource/src/main/res/values-fi/strings.xml
index 38e2a76a8d..6a10183e80 100644
--- a/android/lib/resource/src/main/res/values-fi/strings.xml
+++ b/android/lib/resource/src/main/res/values-fi/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Kaikki sijainnit</string>
<string name="all_providers">Kaikki palveluntarjoajat</string>
<string name="always_on_vpn_error_notification_content">Tunneliyhteyden käynnistäminen ei onnistu. Poista aina päällä oleva VPN käytöstä sovellukselle &lt;b&gt;%1$s&lt;/b&gt; ennen Mullvad VPN:n käyttämistä.</string>
- <string name="always_on_vpn_error_notification_title">Aina päällä oleva VPN on määritetty toiselle sovellukselle</string>
<string name="any">Mikä tahansa</string>
<string name="api_access_description">Hallitse ja lisää mukautettuja menetelmiä Mullvadin ohjelmointirajapinnan käyttämiseksi.</string>
<string name="api_access_method_info_first_line">Sovelluksen on kommunikoitava Mullvadin ohjelmointirajapinnan palvelimen kanssa, jotta sinut voidaan kirjata sisään, palvelinluetteloiden hakemiseksi sekä muiden tärkeiden toimintojen suorittamiseksi.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Syötä kuponkikoodi</string>
<string name="entry">Tulo</string>
<string name="error_occurred">Ilmeni virhe.</string>
- <string name="error_state">YHTEYDEN SUOJAAMINEN EPÄONNISTUI</string>
<string name="exclude_applications">Poissuljetut sovellukset</string>
<string name="exit">Lähtö</string>
<string name="failed_to_block_internet">Kaiken verkkoliikenteen estäminen ei onnistu. Käytä vianetsintää tai lähetä ongelmaraportti.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Kuponkikoodi ei kelpaa.</string>
<string name="ipv6_unavailable">IPv6:n määritys ei onnistunut. Poista se käytöstä sovelluksessa tai ota käyttöön laitteellasi.</string>
<string name="is_offline">Laitteesi on offline-tilassa. Kun laitteesi on taas online-tilassa, tunneli muodostaa yhteyden automaattisesti.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Aina päällä oleva VPN on määritetty toiselle sovellukselle</string>
<string name="less_than_a_day_left">alle vuorokausi jäljellä</string>
<string name="less_than_one_day">alle vuorokausi</string>
<string name="list_name">Luettelon nimi</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Näytät syöttäneen tilin numeron etusetelin koodin sijaan. Jos haluat vaihtaa tiliä, kirjaudu ensin ulos nykyiseltä tililtä.</string>
<string name="voucher_success_title">Kupongin lunastus onnistui.</string>
<string name="vpn_permission_denied_error">VPN-lupa evättiin tunnelia luotaessa. Yritä muodostaa yhteys uudelleen.</string>
- <string name="vpn_permission_error_notification_message">Aina päällä oleva VPN on mahdollisesti otettu käyttöön toiselle sovellukselle</string>
<string name="vpn_permission_error_notification_title">VPN-lupavirhe</string>
<string name="we_will_look_into_this">Tutkimme asiaa.</string>
<string name="wireguard_custon_port_title">Mukautettu</string>
diff --git a/android/lib/resource/src/main/res/values-fr/strings.xml b/android/lib/resource/src/main/res/values-fr/strings.xml
index cd70ac2701..8ea968806f 100644
--- a/android/lib/resource/src/main/res/values-fr/strings.xml
+++ b/android/lib/resource/src/main/res/values-fr/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Toutes les localisations</string>
<string name="all_providers">Tous les fournisseurs</string>
<string name="always_on_vpn_error_notification_content">Impossible de démarrer la connexion au tunnel. Veuillez désactiver « Toujours exiger un VPN « pour &lt;b&gt;%1$s&lt;/b&gt; avant d\'utiliser Mullvad VPN.</string>
- <string name="always_on_vpn_error_notification_title">« Toujours exiger un VPN » est assigné à une autre application</string>
<string name="any">N\'importe lequel</string>
<string name="api_access_description">Gérez et ajoutez des modes d\'accès personnalisés à l\'API Mullvad.</string>
<string name="api_access_method_info_first_line">L\'application doit communiquer avec un serveur d\'API Mullvad pour vous connecter, récupérer des listes de serveurs et effectuer d\'autres opérations critiques.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Saisir un code de bon</string>
<string name="entry">Entrée</string>
<string name="error_occurred">Une erreur est survenue.</string>
- <string name="error_state">ÉCHEC DE LA SÉCURISATION DE LA CONNEXION</string>
<string name="exclude_applications">Applications exclues</string>
<string name="exit">Sortie</string>
<string name="failed_to_block_internet">Impossible de bloquer tout le trafic réseau. Veuillez dépanner ou envoyer un rapport de problème.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Le code du bon n\'est pas valide.</string>
<string name="ipv6_unavailable">Impossible de configurer IPv6. Désactivez le protocole dans l\'application ou activez-le dans votre appareil.</string>
<string name="is_offline">Votre appareil est hors ligne. Le tunnel se connectera automatiquement une fois votre appareil à nouveau en ligne.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">« Toujours exiger un VPN » est assigné à une autre application</string>
<string name="less_than_a_day_left">moins d\'un jour restant</string>
<string name="less_than_one_day">moins d\'un jour</string>
<string name="list_name">Nom de la liste</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Vous semblez avoir saisi un numéro de compte plutôt qu\'un code de bon. Si vous souhaitez modifier le compte actif, veuillez d\'abord vous déconnecter.</string>
<string name="voucher_success_title">Le bon a bien été échangé.</string>
<string name="vpn_permission_denied_error">La permission VPN a été refusée lors de la création du tunnel. Veuillez essayer de vous reconnecter.</string>
- <string name="vpn_permission_error_notification_message">« Toujours exiger un VPN » est peut-être activé pour une autre application</string>
<string name="vpn_permission_error_notification_title">Erreur de permission VPN</string>
<string name="we_will_look_into_this">Nous allons nous pencher dessus.</string>
<string name="wireguard_custon_port_title">Personnalisé</string>
diff --git a/android/lib/resource/src/main/res/values-it/strings.xml b/android/lib/resource/src/main/res/values-it/strings.xml
index 70a08dbf7c..629a1b12df 100644
--- a/android/lib/resource/src/main/res/values-it/strings.xml
+++ b/android/lib/resource/src/main/res/values-it/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Tutti i luoghi</string>
<string name="all_providers">Tutti i fornitori</string>
<string name="always_on_vpn_error_notification_content">Impossibile avviare la connessione tunnel. Disabilita VPN sempre attiva per &lt;b&gt;%1$s&lt;/b&gt; prima di utilizzare Mullvad VPN.</string>
- <string name="always_on_vpn_error_notification_title">VPN sempre attiva assegnata a un\'altra app</string>
<string name="any">Qualsiasi</string>
<string name="api_access_description">Gestisci e aggiungi metodi personalizzati per accedere all\'API Mullvad.</string>
<string name="api_access_method_info_first_line">L\'app deve comunicare con un server API Mullvad per accedere, recuperare elenchi di server e altre operazioni critiche.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Inserisci codice voucher</string>
<string name="entry">Ingresso</string>
<string name="error_occurred">Si è verificato un errore.</string>
- <string name="error_state">IMPOSSIBILE STABILIRE UNA CONNESSIONE PROTETTA</string>
<string name="exclude_applications">Applicazioni escluse</string>
<string name="exit">Uscita</string>
<string name="failed_to_block_internet">Impossibile bloccare tutto il traffico di rete. Consulta la risoluzione dei problemi o invia una segnalazione del problema.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Il codice voucher non è valido.</string>
<string name="ipv6_unavailable">Impossibile configurare IPv6. Disabilitalo nell\'app o abilitalo sul tuo dispositivo.</string>
<string name="is_offline">Il tuo dispositivo è offline. Il tunnel si connetterà automaticamente una volta che il tuo dispositivo sarà di nuovo online.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">VPN sempre attiva assegnata a un\'altra app</string>
<string name="less_than_a_day_left">meno di un giorno rimanente</string>
<string name="less_than_one_day">meno di un giorno</string>
<string name="list_name">Nome dell\'elenco</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Sembra che tu abbia inserito un numero di account anziché un codice voucher. Se desideri modificare l\'account attivo, effettua prima la disconnessione.</string>
<string name="voucher_success_title">Il voucher è stato riscattato correttamente.</string>
<string name="vpn_permission_denied_error">L\'autorizzazione VPN è stata negata durante la creazione del tunnel. Prova a connetterti di nuovo.</string>
- <string name="vpn_permission_error_notification_message">La VPN sempre attiva potrebbe essere abilitata per un\'altra app</string>
<string name="vpn_permission_error_notification_title">Errore di autorizzazione VPN</string>
<string name="we_will_look_into_this">Verificheremo.</string>
<string name="wireguard_custon_port_title">Personalizzato</string>
diff --git a/android/lib/resource/src/main/res/values-ja/strings.xml b/android/lib/resource/src/main/res/values-ja/strings.xml
index 2fb24ebe8e..f7efc1af4a 100644
--- a/android/lib/resource/src/main/res/values-ja/strings.xml
+++ b/android/lib/resource/src/main/res/values-ja/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">すべての場所</string>
<string name="all_providers">すべてのプロバイダ</string>
<string name="always_on_vpn_error_notification_content">トンネル接続を開始できません。Mullvad VPNを使用する前に&lt;b&gt;%1$s&lt;/b&gt;のAlways-on VPNを無効にしてください。</string>
- <string name="always_on_vpn_error_notification_title">Always-on VPNは他のアプリに割り当てられています</string>
<string name="any">すべて</string>
<string name="api_access_description">Mullvad APIへのカスタムのアクセス方法を管理・追加します。</string>
<string name="api_access_method_info_first_line">アプリはユーザーのログイン、サーバーリストの取得、およびその他の重要な操作を行うためにMullvad APIサーバーと通信する必要があります。</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">バウチャーコードを入力</string>
<string name="entry">入口</string>
<string name="error_occurred">エラー発生。</string>
- <string name="error_state">セキュリティ保護接続を確立できませんでした</string>
<string name="exclude_applications">除外対象アプリケーション</string>
<string name="exit">出口</string>
<string name="failed_to_block_internet">すべてのネットワークトラフィックをブロックできません。問題に対処するか、問題の報告を送信してください。</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">バウチャーコードが無効です。</string>
<string name="ipv6_unavailable">IPv6を設定できませんでした。アプリで無効にするか、デバイスで有効にしてください。</string>
<string name="is_offline">デバイスがオフラインになっています。トンネルはデバイスがオンラインに戻ると自動的に接続されます。</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Always-on VPNは他のアプリに割り当てられています</string>
<string name="less_than_a_day_left">残り1日未満</string>
<string name="less_than_one_day">1日未満</string>
<string name="list_name">リスト名</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">バウチャーコードではなくアカウント番号を入力したようです。有効なアカウントを変更する場合は、先にログアウトしてください。</string>
<string name="voucher_success_title">バウチャーを正常に使用しました。</string>
<string name="vpn_permission_denied_error">トンネルを作成中にVPNへのアクセスが拒否されました。もう一度接続してみてください。</string>
- <string name="vpn_permission_error_notification_message">Always-on VPNが別のアプリで有効になっている可能性があります</string>
<string name="vpn_permission_error_notification_title">VPN許可エラー</string>
<string name="we_will_look_into_this">この問題を調査いたします。</string>
<string name="wireguard_custon_port_title">カスタム</string>
diff --git a/android/lib/resource/src/main/res/values-ko/strings.xml b/android/lib/resource/src/main/res/values-ko/strings.xml
index f8727aabbc..c6d2c3cf3f 100644
--- a/android/lib/resource/src/main/res/values-ko/strings.xml
+++ b/android/lib/resource/src/main/res/values-ko/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">모든 위치</string>
<string name="all_providers">모든 제공업체</string>
<string name="always_on_vpn_error_notification_content">터널 연결을 시작할 수 없습니다. Mullvad VPN을 사용하기 전에 &lt;b&gt;%1$s&lt;/b&gt;에 대한 상시 접속 VPN을 비활성화하세요.</string>
- <string name="always_on_vpn_error_notification_title">상시 접속 VPN이 다른 앱에 할당됨</string>
<string name="any">모두</string>
<string name="api_access_description">Mullvad API에 액세스하기 위한 사용자 지정 방법을 관리하고 추가합니다.</string>
<string name="api_access_method_info_first_line">이 앱은 로그인, 서버 목록 가져오기 및 기타 중요한 작업을 위해 Mullvad API 서버와 통신해야 합니다.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">바우처 코드 입력</string>
<string name="entry">시작</string>
<string name="error_occurred">오류가 발생했습니다.</string>
- <string name="error_state">보안 연결 실패</string>
<string name="exclude_applications">제외된 애플리케이션</string>
<string name="exit">종료</string>
<string name="failed_to_block_internet">모든 네트워크 트래픽을 차단할 수는 없습니다. 문제를 해결하거나 문제 보고서를 보내주세요.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">유효하지 않은 바우처 코드입니다.</string>
<string name="ipv6_unavailable">IPv6을 구성할 수 없습니다. 앱에서 비활성화하거나 장치에서 활성화하세요.</string>
<string name="is_offline">장치가 오프라인 상태입니다. 장치가 다시 온라인 상태가 되면 터널이 자동으로 연결됩니다.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">상시 접속 VPN이 다른 앱에 할당됨</string>
<string name="less_than_a_day_left">1일 이내</string>
<string name="less_than_one_day">1일 미만</string>
<string name="list_name">목록 이름</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">바우처 코드 대신 계정 번호를 입력한 것 같습니다. 활성 계정을 변경하려면 먼저 로그아웃하세요.</string>
<string name="voucher_success_title">바우처가 성공적으로 사용되었습니다.</string>
<string name="vpn_permission_denied_error">터널을 만드는 동안 VPN 사용 권한이 거부되었습니다. 다시 연결해 보세요.</string>
- <string name="vpn_permission_error_notification_message">상시 접속 VPN이 다른 앱에 활성화되었을 수 있습니다.</string>
<string name="vpn_permission_error_notification_title">VPN 권한 오류</string>
<string name="we_will_look_into_this">조사해보겠습니다.</string>
<string name="wireguard_custon_port_title">사용자 지정</string>
diff --git a/android/lib/resource/src/main/res/values-my/strings.xml b/android/lib/resource/src/main/res/values-my/strings.xml
index 5c9cd497f2..a73a327cb9 100644
--- a/android/lib/resource/src/main/res/values-my/strings.xml
+++ b/android/lib/resource/src/main/res/values-my/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">တည်နေရာအာလုံး</string>
<string name="all_providers">ပံ့ပိုးသူအားလုံး</string>
<string name="always_on_vpn_error_notification_content">Tunnel ချိတ်ဆက်မှုကို စတင်၍ မရနိုင်ပါ။ Mullvad VPN ကို မသုံးမီ &lt;b&gt;%1$s&lt;/b&gt; အတွက် VPN အမြဲဖွင့်ထားမှုကို ပိတ်ပေးပါ။</string>
- <string name="always_on_vpn_error_notification_title">အမြဲဖွင့် VPN ကို အခြားအက်ပ်တစ်ခုသို့ သတ်မှတ်ထားပါသည်</string>
<string name="any">တစ်ခုခု</string>
<string name="api_access_description">Mullvad API ကို ရယူသုံးစွဲရန် စိတ်ကြိုက် နည်းလမ်းများကို ပေါင်းထည့်၍ စီမံပါ။</string>
<string name="api_access_method_info_first_line">Mullvad API ဆာဗာသို့ သင်ဝင်ရောက်ရန်၊ ဆာဗာစာရင်းများ ရယူရန်နှင့် အလွန်အရေးပါသည့် အခြားလုပ်ဆောင်မှုများအတွက် ဤအက်ပ်သည် ၎င်းနှင့်ဆက်သွယ်ရန် လိုအပ်ပါသည်။</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">ဘောက်ချာကုဒ် ဖြည့်သွင်းရန်</string>
<string name="entry">အဝင်</string>
<string name="error_occurred">ချို့ယွင်းချက် ဖြစ်ပေါ်ခဲ့ပါသည်။</string>
- <string name="error_state">ချိတ်ဆက်မှုကို ကာကွယ်ရန် မအောင်မြင်ပါ</string>
<string name="exclude_applications">အပလီကေးရှင်းများ ဖယ်ထားပြီး</string>
<string name="exit">အထွက်</string>
<string name="failed_to_block_internet">ကွန်ရက် ကူးလူးမှု အားလုံးကို ပိတ်ဆို့၍ မရနိုင်ပါ။ ပြစ်ချက် ရှာဖွေဖယ်ရှားပေးပါ သို့မဟုတ် ပြဿနာ ရီပို့တ်တစ်ခု ပေးပို့ပါ။</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">ဘောက်ချာကုဒ် မှားနေပါသည်။</string>
<string name="ipv6_unavailable">IPv6 ကို သတ်မှတ်ချိန်ညှိ၍ မရနိုင်ပါ။ အက်ပ်တွင် ၎င်းကို ပိတ်ပါ သို့မဟုတ် သင့်စက်တွင် ၎င်းကို ဖွင့်ပါ။</string>
<string name="is_offline">သင့်စက်သည် အော့ဖ်လိုင်း ဖြစ်နေပါသည်။ သင့်စက် အွန်လိုင်း ပြန်ဖြစ်သည်နှင့် Tunnel သည် အော်တို ချိတ်ဆက်သွားပါမည်။</string>
+ <string name="legacy_always_on_vpn_error_notification_title">အမြဲဖွင့် VPN ကို အခြားအက်ပ်တစ်ခုသို့ သတ်မှတ်ထားပါသည်</string>
<string name="less_than_a_day_left">တစ်ရက်အောက်သာ ကျန်တော့သည်</string>
<string name="less_than_one_day">တစ်ရက်အောက် နည်းသည်။</string>
<string name="list_name">စာရင်း အမည်</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">ဘောက်ချာကုဒ်အစား အကောင့်နံပါတ်တစ်ခုကို ထည့်သွင်းထားပုံရသည်။ အသုံးပြုနေသောအကောင့်ကို ပြောင်းလဲလိုပါက ဦးစွာ အကောင့်မှထွက်ပါ။</string>
<string name="voucher_success_title">ဘောက်ချာကို အောင်မြင်စွာ လဲယူခဲ့ပါသည်။</string>
<string name="vpn_permission_denied_error">Tunnel ဖန်တီးနေစဉ် VPN ခွင့်ပြုချက်ကို ပယ်ချခဲ့ပါသည်။ ထပ်မံချိတ်ဆက်ပေးပါ။</string>
- <string name="vpn_permission_error_notification_message">အမြဲဖွင့် VPN ကို နောက်ထပ်အက်ပ်အတွက် ဖွင့်ထားနိုင်ပါသည်</string>
<string name="vpn_permission_error_notification_title">VPN ခွင့်ပြုချက် ချို့ယွင်းချက်</string>
<string name="we_will_look_into_this">ဤသည်ကို စစ်ဆေးလိုက်ပါမည်။</string>
<string name="wireguard_custon_port_title">စိတ်ကြိုက်</string>
diff --git a/android/lib/resource/src/main/res/values-nb/strings.xml b/android/lib/resource/src/main/res/values-nb/strings.xml
index bef18f4629..01a2e1c0cf 100644
--- a/android/lib/resource/src/main/res/values-nb/strings.xml
+++ b/android/lib/resource/src/main/res/values-nb/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Alle steder</string>
<string name="all_providers">Alle leverandører</string>
<string name="always_on_vpn_error_notification_content">Kunne ikke starte tunneltilkobling. Deaktiver VPN som alltid er på, for &lt;b&gt;%1$s&lt;/b&gt; før du bruker Mullvad VPN.</string>
- <string name="always_on_vpn_error_notification_title">VPN som alltid er på, er tilordnet en annen app</string>
<string name="any">Hvilken som helst</string>
<string name="api_access_description">Administrer og legg til tilpassede metoder for tilgang til Mullvad API.</string>
<string name="api_access_method_info_first_line">Appen må kunne kommunisere med en Mullvad API-server for å logge deg inn, hente serverlister og utføre andre kritiske operasjoner.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Skriv inn kupongkode</string>
<string name="entry">Inngang</string>
<string name="error_occurred">Det oppstod en feil.</string>
- <string name="error_state">KUNNE IKKE OPPRETTE SIKKER TILKOBLING</string>
<string name="exclude_applications">Ekskluder applikasjoner</string>
<string name="exit">Utgang</string>
<string name="failed_to_block_internet">Kunne ikke blokkere all nettverkstrafikk. Feilsøk eller send inn en problemrapport.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Ugyldig kupongkode.</string>
<string name="ipv6_unavailable">Kunne ikke konfigurere IPv6. Deaktiver den i appen eller aktiver den på enheten din.</string>
<string name="is_offline">Enheten er frakoblet. Tunnelen vil koble til automatisk når enheten er tilkoblet internett igjen.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">VPN som alltid er på, er tilordnet en annen app</string>
<string name="less_than_a_day_left">mindre enn én dag igjen</string>
<string name="less_than_one_day">mindre enn én dag</string>
<string name="list_name">Listenavn</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Det ser ut til at du har oppgitt et kontonummer i stedet for en kupongkode. Hvis du vil endre den aktive kontoen, må du først logge ut.</string>
<string name="voucher_success_title">Kupongkoden er løst inn.</string>
<string name="vpn_permission_denied_error">VPN-tillatelse ble avvist under opprettelsen av tunnelen. Prøv å koble til igjen.</string>
- <string name="vpn_permission_error_notification_message">VPN som alltid er på, kan være aktivert for en annen app</string>
<string name="vpn_permission_error_notification_title">Feil med VPN-tillatelse</string>
<string name="we_will_look_into_this">Dette skal vi følge opp.</string>
<string name="wireguard_custon_port_title">Egendefinert</string>
diff --git a/android/lib/resource/src/main/res/values-nl/strings.xml b/android/lib/resource/src/main/res/values-nl/strings.xml
index e965388f40..84ee272e08 100644
--- a/android/lib/resource/src/main/res/values-nl/strings.xml
+++ b/android/lib/resource/src/main/res/values-nl/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Alle locaties</string>
<string name="all_providers">Alle aanbieders</string>
<string name="always_on_vpn_error_notification_content">Kan de tunnelverbinding niet starten. Schakel Altijd-aan VPN uit voor &lt;b&gt;%1$s&lt;/b&gt; voordat u Mullvad VPN gebruikt.</string>
- <string name="always_on_vpn_error_notification_title">Altijd-aan VPN toegewezen aan andere app</string>
<string name="any">Elke</string>
<string name="api_access_description">Beheer en voeg aangepaste methoden toe om toegang te krijgen tot de Mullvad-API.</string>
<string name="api_access_method_info_first_line">De app moet communiceren met een Mullvad-API-server om u aan te melden, serverlijsten op te halen en andere belangrijke handelingen uit te voeren.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Vouchercode invoeren</string>
<string name="entry">Ingang</string>
<string name="error_occurred">Er is een fout opgetreden.</string>
- <string name="error_state">VERBINDING BEVEILIGEN MISLUKT</string>
<string name="exclude_applications">Uitgesloten toepassingen</string>
<string name="exit">Uitgang</string>
<string name="failed_to_block_internet">Kan niet alle netwerkverkeer blokkeren. Los problemen op of stuur een probleemmelding.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Vouchercode is ongeldig.</string>
<string name="ipv6_unavailable">Kon IPv6 niet configureren. Schakel het uit in de app of schakel het in op uw apparaat.</string>
<string name="is_offline">Uw apparaat is offline. De tunnel wordt automatisch verbonden wanneer uw apparaat weer online is.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Altijd-aan VPN toegewezen aan andere app</string>
<string name="less_than_a_day_left">minder dan een dag over</string>
<string name="less_than_one_day">minder dan één dag</string>
<string name="list_name">Lijstnaam</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Het lijkt erop dat u een accountnummer hebt ingevoerd in plaats van een vouchercode. Als u het actieve account wilt wijzigen, meld u dan eerst af.</string>
<string name="voucher_success_title">Voucher is ingewisseld.</string>
<string name="vpn_permission_denied_error">VPN-toestemming is geweigerd tijdens maken van de tunnel. Probeer opnieuw verbinding te maken.</string>
- <string name="vpn_permission_error_notification_message">Altijd-aan VPN is mogelijk ingeschakeld voor een andere app</string>
<string name="vpn_permission_error_notification_title">VPN-machtigingsfout</string>
<string name="we_will_look_into_this">We gaan het bekijken.</string>
<string name="wireguard_custon_port_title">Aangepast</string>
diff --git a/android/lib/resource/src/main/res/values-pl/strings.xml b/android/lib/resource/src/main/res/values-pl/strings.xml
index 9fd1e9de55..e19d2fddd4 100644
--- a/android/lib/resource/src/main/res/values-pl/strings.xml
+++ b/android/lib/resource/src/main/res/values-pl/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Wszystkie lokalizacje</string>
<string name="all_providers">Wszyscy dostawcy</string>
<string name="always_on_vpn_error_notification_content">Nie można uruchomić połączenia tunelowego. Przed rozpoczęciem użytkowania usługi Mullvad VPN wyłącz opcję „Zawsze włączony VPN” w &lt;b&gt;%1$s&lt;/b&gt;.</string>
- <string name="always_on_vpn_error_notification_title">Opcja „Zawsze włączony VPN” przypisana jest do innej aplikacji</string>
<string name="any">Dowolny</string>
<string name="api_access_description">Zarządzaj i dodawaj niestandardowe metody dostępu do interfejsu API Mullvad.</string>
<string name="api_access_method_info_first_line">Aplikacja musi komunikować się z serwerem API Mullvad, aby można było się zalogować, pobrać listy serwerów i wykonywać inne krytyczne operacje.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Wprowadź kod kuponu</string>
<string name="entry">Wejście</string>
<string name="error_occurred">Wystąpił błąd.</string>
- <string name="error_state">BŁĄD ZABEZPIECZANIA POŁĄCZENIA</string>
<string name="exclude_applications">Wykluczone aplikacje</string>
<string name="exit">Wyjście</string>
<string name="failed_to_block_internet">Nie można zablokować całego ruchu sieciowego. Rozwiąż problem lub wyślij zgłoszenie problemu.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Nieprawidłowy kod kuponu.</string>
<string name="ipv6_unavailable">Nie można skonfigurować protokołu IPv6. Wyłącz go w aplikacji lub włącz na urządzeniu.</string>
<string name="is_offline">Twoje urządzenie jest offline. Tunel zostanie automatycznie połączony, gdy urządzenie ponownie przejdzie w tryb online.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Opcja „Zawsze włączony VPN” przypisana jest do innej aplikacji</string>
<string name="less_than_a_day_left">pozostał mniej niż jeden dzień</string>
<string name="less_than_one_day">mniej niż jeden dzień</string>
<string name="list_name">Nazwa listy</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Wygląda na to, że wpisano numer konta zamiast kodu kuponu. Jeśli chcesz zmienić aktywne konto, najpierw się wyloguj.</string>
<string name="voucher_success_title">Kupon został zrealizowany.</string>
<string name="vpn_permission_denied_error">Uprawnienie VPN zostało odrzucone podczas tworzenia tunelu. Spróbuj połączyć się ponownie.</string>
- <string name="vpn_permission_error_notification_message">Opcja „Zawsze włączony VPN” może być włączona dla innej aplikacji</string>
<string name="vpn_permission_error_notification_title">Błąd uprawnienia VPN</string>
<string name="we_will_look_into_this">Sprawdzimy to.</string>
<string name="wireguard_custon_port_title">Niestandardowy</string>
diff --git a/android/lib/resource/src/main/res/values-pt/strings.xml b/android/lib/resource/src/main/res/values-pt/strings.xml
index 34175af582..0e390b6a30 100644
--- a/android/lib/resource/src/main/res/values-pt/strings.xml
+++ b/android/lib/resource/src/main/res/values-pt/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Todas as localizações</string>
<string name="all_providers">Todos os fornecedores</string>
<string name="always_on_vpn_error_notification_content">Não foi possível iniciar a ligação de túnel. Desative a VPN sempre ligada para &lt;b&gt;%1$s&lt;/b&gt; antes de utilizar a Mullvad VPN.</string>
- <string name="always_on_vpn_error_notification_title">VPN sempre ligada atribuída a outra app</string>
<string name="any">Qualquer</string>
<string name="api_access_description">Gerir e adicionar métodos personalizados para aceder à Mullvad API.</string>
<string name="api_access_method_info_first_line">A app precisa de comunicar com um servidor da Mullvad API para iniciar a sua sessão, obter listas de servidores e outras operações críticas.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Introduza o código do voucher</string>
<string name="entry">Entrada</string>
<string name="error_occurred">Ocorreu um erro.</string>
- <string name="error_state">ERRO AO ESTABELECER LIGAÇÃO SEGURA</string>
<string name="exclude_applications">Aplicações excluídas</string>
<string name="exit">Saída</string>
<string name="failed_to_block_internet">Não foi possível bloquear todo o tráfego de rede. Experimente a resolução de problemas ou envie um relatório do problema.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Código do voucher inválido.</string>
<string name="ipv6_unavailable">Não foi possível configurar o IPv6. Desative-o ou ative-o no seu dispositivo.</string>
<string name="is_offline">O seu dispositivo está offline. O túnel irá ligar-se automaticamente assim que o seu dispositivo estiver novamente online.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">VPN sempre ligada atribuída a outra app</string>
<string name="less_than_a_day_left">menos de um dia restante</string>
<string name="less_than_one_day">menos de um dia</string>
<string name="list_name">Nome da lista</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Parece que introduziu um número de conta em vez de um código de voucher. Se pretender alterar a conta ativa, termine a sessão primeiro.</string>
<string name="voucher_success_title">O voucher foi reclamado com sucesso.</string>
<string name="vpn_permission_denied_error">A transmissão foi negada durante a criação do túnel. Tente fazer novamente a ligação.</string>
- <string name="vpn_permission_error_notification_message">A VPN sempre ligada pode estar ativada para outra app</string>
<string name="vpn_permission_error_notification_title">Erro de permissão da VPN</string>
<string name="we_will_look_into_this">Vamos analisar esta situação.</string>
<string name="wireguard_custon_port_title">Personalizado</string>
diff --git a/android/lib/resource/src/main/res/values-ru/strings.xml b/android/lib/resource/src/main/res/values-ru/strings.xml
index 4d7e03a968..060597a298 100644
--- a/android/lib/resource/src/main/res/values-ru/strings.xml
+++ b/android/lib/resource/src/main/res/values-ru/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Все местоположения</string>
<string name="all_providers">Все провайдеры</string>
<string name="always_on_vpn_error_notification_content">Не удалось запустить туннельное подключение. Перед использованием Mullvad VPN отключите опцию «Постоянная VPN» для приложения &lt;b&gt;%1$s&lt;/b&gt;.</string>
- <string name="always_on_vpn_error_notification_title">Опция «Постоянная VPN» назначена другому приложению</string>
<string name="any">Все</string>
<string name="api_access_description">Добавление пользовательских методов для доступа к API Mullvad и управление ими.</string>
<string name="api_access_method_info_first_line">Приложение должно взаимодействовать с сервером API Mullvad для входа в учетную запись, получения списков серверов и других важных операций.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Введите код ваучера</string>
<string name="entry">Вход</string>
<string name="error_occurred">Произошла ошибка.</string>
- <string name="error_state">НЕ УДАЛОСЬ УСТАНОВИТЬ БЕЗОПАСНОЕ ПОДКЛЮЧЕНИЕ</string>
<string name="exclude_applications">Исключенные приложения</string>
<string name="exit">Выход</string>
<string name="failed_to_block_internet">Не удалось заблокировать весь сетевой трафик. Устраните неполадки или отправьте сообщение о проблеме.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Код ваучера недействителен.</string>
<string name="ipv6_unavailable">Не удается сконфигурировать IPv6. Отключите этот протокол в приложении или включите на устройстве.</string>
<string name="is_offline">Устройство отключено от Интернета. Туннельное подключение автоматически запустится, как только ваше устройство подключится к Интернету.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Опция «Постоянная VPN» назначена другому приложению</string>
<string name="less_than_a_day_left">осталось менее суток</string>
<string name="less_than_one_day">менее суток</string>
<string name="list_name">Имя списка</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Вы ввели номер учетной записи вместо кода ваучера. Чтобы изменить активную учетную запись, сначала выйдите из системы.</string>
<string name="voucher_success_title">Ваучер погашен.</string>
<string name="vpn_permission_denied_error">При создании туннеля в доступе к VPN было отказано. Попробуйте подключиться снова.</string>
- <string name="vpn_permission_error_notification_message">Опцию «Постоянная VPN» может быть включена для другого приложения</string>
<string name="vpn_permission_error_notification_title">Ошибка разрешения для VPN</string>
<string name="we_will_look_into_this">Мы рассмотрим эту проблему.</string>
<string name="wireguard_custon_port_title">Пользовательский</string>
diff --git a/android/lib/resource/src/main/res/values-sv/strings.xml b/android/lib/resource/src/main/res/values-sv/strings.xml
index a1f0557de7..35517f1c95 100644
--- a/android/lib/resource/src/main/res/values-sv/strings.xml
+++ b/android/lib/resource/src/main/res/values-sv/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Alla platser</string>
<string name="all_providers">Alla leverantörer</string>
<string name="always_on_vpn_error_notification_content">Det går inte att starta tunnelanslutning. Aktivera VPN som alltid är på för &lt;b&gt;%1$s&lt;/b&gt; innan du använder Mullvad VPN.</string>
- <string name="always_on_vpn_error_notification_title">VPN som alltid är på har tilldelats till annan app</string>
<string name="any">Valfri</string>
<string name="api_access_description">Hantera och lägg till anpassade metoder för att komma åt Mullvad API.</string>
<string name="api_access_method_info_first_line">Appen måste kommunicera med en Mullvad API-server för att logga in dig, hämta serverlistor och andra viktiga åtgärder.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Ange kupongkod</string>
<string name="entry">Ingång</string>
<string name="error_occurred">Ett fel har inträffat.</string>
- <string name="error_state">DET GICK INTE ATT SÄKRA ANSLUTNINGEN</string>
<string name="exclude_applications">Exkluderade applikationer</string>
<string name="exit">Utgång</string>
<string name="failed_to_block_internet">Det går inte att blockera all nätverkstrafik. Felsök eller skicka en problemrapport.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Kupongkoden är ogiltig.</string>
<string name="ipv6_unavailable">Det gick inte att konfigurera IPv6. Inaktivera det i appen eller aktivera det på din enhet.</string>
<string name="is_offline">Din enhet är offline. Tunneln ansluts automatiskt när din enhet är online igen.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">VPN som alltid är på har tilldelats till annan app</string>
<string name="less_than_a_day_left">mindre än en dag kvar</string>
<string name="less_than_one_day">mindre än en dag</string>
<string name="list_name">Listnamn</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Det verkar som om du angett ett kontonummer istället för en kupongkod. Logga först ut om du vill ändra den aktiva koden.</string>
<string name="voucher_success_title">Kupongen har lösts in.</string>
<string name="vpn_permission_denied_error">VPN-behörighet nekades när tunneln skapades. Försök att ansluta igen.</string>
- <string name="vpn_permission_error_notification_message">VPN som alltid är på kan ha aktiverats för annan app</string>
<string name="vpn_permission_error_notification_title">Behörighetsfel med VPN</string>
<string name="we_will_look_into_this">Vi kommer att undersöka detta.</string>
<string name="wireguard_custon_port_title">Anpassad</string>
diff --git a/android/lib/resource/src/main/res/values-th/strings.xml b/android/lib/resource/src/main/res/values-th/strings.xml
index 9664d0a917..f91d3ed15c 100644
--- a/android/lib/resource/src/main/res/values-th/strings.xml
+++ b/android/lib/resource/src/main/res/values-th/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">ตำแหน่งที่ตั้งทั้งหมด</string>
<string name="all_providers">ผู้ให้บริการทั้งหมด</string>
<string name="always_on_vpn_error_notification_content">ไม่สามารถเริ่มการเชื่อมต่ออุโมงค์ได้ โปรดปิดใช้งาน Always-on VPN เป็นเวลา &lt;b&gt;%1$s&lt;/b&gt; ก่อนที่จะใช้งาน Mullvad VPN</string>
- <string name="always_on_vpn_error_notification_title">Always-on VPN ได้รับการมอบหมายไปยังแอปอื่นแล้ว</string>
<string name="any">อะไรก็ได้</string>
<string name="api_access_description">จัดการและเพิ่มวิธีแบบกำหนดเอง เพื่อเข้าถึง Mullvad API</string>
<string name="api_access_method_info_first_line">แอปจำเป็นต้องสื่อสารกับเซิร์ฟเวอร์ Mullvad API เพื่อนำคุณเข้าสู่ระบบ ดึงข้อมูลรายการเซิร์ฟเวอร์ และการดำเนินการที่สำคัญอื่นๆ</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">ป้อนรหัสบัตรกำนัล</string>
<string name="entry">เข้า</string>
<string name="error_occurred">เกิดข้อผิดพลาดขึ้น</string>
- <string name="error_state">ไม่สามารถเชื่อมต่ออย่างปลอดภัยได้</string>
<string name="exclude_applications">แอปพลิเคชันที่แยกออก</string>
<string name="exit">ออก</string>
<string name="failed_to_block_internet">ไม่สามารถบล็อกการรับส่งข้อมูลทางเครือข่ายทั้งหมดได้ โปรดแก้ไขปัญหาหรือส่งรายงานปัญหา</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">รหัสบัตรกำนัลไม่ถูกต้อง</string>
<string name="ipv6_unavailable">ไม่สามารถกำหนดค่า IPv6 ได้ โปรดปิดใช้งานรายการนี้ในแอป หรือเปิดใช้งานบนอุปกรณ์ของคุณ</string>
<string name="is_offline">อุปกรณ์ของคุณออฟไลน์อยู่ อุโมงค์จะเชื่อมต่อโดยอัตโนมัติ หลังจากที่อุปกรณ์ของคุณกลับมาออนไลน์</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Always-on VPN ได้รับการมอบหมายไปยังแอปอื่นแล้ว</string>
<string name="less_than_a_day_left">เหลือเวลาน้อยกว่าหนึ่งวัน</string>
<string name="less_than_one_day">น้อยกว่าหนึ่งวัน</string>
<string name="list_name">ชื่อรายการ</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">ดูเหมือนว่า คุณได้ป้อนหมายเลขบัญชีแทนรหัสบัตรกำนัล หากคุณต้องการเปลี่ยนบัญชีที่ใช้งานอยู่ โปรดออกจากระบบก่อน</string>
<string name="voucher_success_title">แลกบัตรกำนัลสำเร็จแล้ว</string>
<string name="vpn_permission_denied_error">การให้สิทธิ์ VPN ถูกปฏิเสธ ในขณะที่สร้างอุโมงค์ โปรดลองเชื่อมต่อใหม่อีกครั้ง</string>
- <string name="vpn_permission_error_notification_message">Always-on VPN อาจได้รับการเปิดใช้งานสำหรับแอปอื่น</string>
<string name="vpn_permission_error_notification_title">เกิดข้อผิดพลาดในการอนุญาต VPN</string>
<string name="we_will_look_into_this">เราจะตรวจสอบปัญหานี้</string>
<string name="wireguard_custon_port_title">กำหนดเอง</string>
diff --git a/android/lib/resource/src/main/res/values-tr/strings.xml b/android/lib/resource/src/main/res/values-tr/strings.xml
index cdb01b9d2f..2f7e1d1dad 100644
--- a/android/lib/resource/src/main/res/values-tr/strings.xml
+++ b/android/lib/resource/src/main/res/values-tr/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">Tüm konumlar</string>
<string name="all_providers">Tüm hizmet sağlayıcılar</string>
<string name="always_on_vpn_error_notification_content">Tünel bağlantısı başlatılamıyor. Mullvad VPN\'i kullanmadan önce lütfen Her zaman açık VPN\'i &lt;b&gt;%1$s&lt;/b&gt; için devre dışı bırakın.</string>
- <string name="always_on_vpn_error_notification_title">Her zaman açık VPN başka bir uygulamaya atandı</string>
<string name="any">Tümü</string>
<string name="api_access_description">Mullvad API\'sine erişim için özel yöntemler ekleyip yönetin.</string>
<string name="api_access_method_info_first_line">Uygulamanın oturumunuzu açmak, sunucu listelerini almak ve diğer kritik işlemleri yapmak için bir Mullvad API sunucusuyla iletişim kurması gerekir.</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">Kupon kodunu girin</string>
<string name="entry">Giriş</string>
<string name="error_occurred">Bir hata oluştu.</string>
- <string name="error_state">GÜVENLİ BAĞLANTI OLUŞTURULAMADI</string>
<string name="exclude_applications">Hariç tutulan uygulamalar</string>
<string name="exit">Çıkış</string>
<string name="failed_to_block_internet">Tüm ağ trafiği engellenemiyor. Lütfen sorunu çözmeyi deneyin veya bir hata raporu gönderin.</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">Kupon kodu geçersiz.</string>
<string name="ipv6_unavailable">IPv6 yapılandırılamadı. IPv6\'yı uygulamada devre dışı bırakın veya cihazınızda etkinleştirin.</string>
<string name="is_offline">Cihazınız çevrimdışı. Cihazınız tekrar çevrimiçi olduğunda tünel otomatik olarak bağlanacak.</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Her zaman açık VPN başka bir uygulamaya atandı</string>
<string name="less_than_a_day_left">bir günden az kaldı</string>
<string name="less_than_one_day">bir günden az</string>
<string name="list_name">Liste adı</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">Kupon kodu yerine hesap numarası girdiniz. Aktif hesabı değiştirmek istiyorsanız lütfen önce çıkış yapın.</string>
<string name="voucher_success_title">Kupon başarıyla kullanıldı.</string>
<string name="vpn_permission_denied_error">Tünel oluşturulurken VPN izni reddedildi. Lütfen tekrar bağlanmayı deneyin.</string>
- <string name="vpn_permission_error_notification_message">Her zaman açık VPN başka bir uygulama için etkinleştirilebilir</string>
<string name="vpn_permission_error_notification_title">VPN izin hatası</string>
<string name="we_will_look_into_this">Bunu araştıracağız.</string>
<string name="wireguard_custon_port_title">Özel</string>
diff --git a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
index 39306a64ae..f9c87e9191 100644
--- a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
+++ b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">所有位置</string>
<string name="all_providers">所有提供商</string>
<string name="always_on_vpn_error_notification_content">无法启动隧道连接。在使用 Mullvad VPN 之前,请为 &lt;b&gt;%1$s&lt;/b&gt; 禁用“始终开启 VPN”。</string>
- <string name="always_on_vpn_error_notification_title">“始终开启 VPN”已分配给其他应用</string>
<string name="any">任何</string>
<string name="api_access_description">管理和添加访问 Mulvad API 的自定义方法。</string>
<string name="api_access_method_info_first_line">该应用需要与 Mulvad API 服务器通信,以便您登录、获取服务器列表和执行其他关键操作。</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">输入优惠码</string>
<string name="entry">入口</string>
<string name="error_occurred">出错了。</string>
- <string name="error_state">无法保护连接</string>
<string name="exclude_applications">排除的应用程序</string>
<string name="exit">出口</string>
<string name="failed_to_block_internet">无法阻止所有网络流量。请排查问题或发送问题报告。</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">该优惠券码无效。</string>
<string name="ipv6_unavailable">无法配置 IPv6。请在应用中将其禁用或在您的设备上将其启用。</string>
<string name="is_offline">您的设备已离线。在您的设备重新上线后,隧道将自动连接。</string>
+ <string name="legacy_always_on_vpn_error_notification_title">“始终开启 VPN”已分配给其他应用</string>
<string name="less_than_a_day_left">剩余时间不足 1 天</string>
<string name="less_than_one_day">不足 1 天</string>
<string name="list_name">列表名称</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">您输入的似乎是帐号,而不是代金券码。如果您想更改有效帐户,请先退出登录。</string>
<string name="voucher_success_title">优惠券已成功兑换。</string>
<string name="vpn_permission_denied_error">创建隧道时,VPN 权限被拒绝。请尝试重新连接。</string>
- <string name="vpn_permission_error_notification_message">可能为另一个应用启用了“始终开启 VPN”</string>
<string name="vpn_permission_error_notification_title">VPN 权限错误</string>
<string name="we_will_look_into_this">我们将对此进行调查。</string>
<string name="wireguard_custon_port_title">自定义</string>
diff --git a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
index 8be3c88b59..6cd3b5d8fc 100644
--- a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
+++ b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
@@ -22,7 +22,6 @@
<string name="all_locations">所有位置</string>
<string name="all_providers">所有供應商</string>
<string name="always_on_vpn_error_notification_content">無法啟動通道連線。在使用 Mullvad VPN 之前,請先為 &lt;b&gt;%1$s&lt;/b&gt; 停用「始終啟用 VPN」。</string>
- <string name="always_on_vpn_error_notification_title">「始終啟用 VPN」已指派給其他應用程式</string>
<string name="any">任何</string>
<string name="api_access_description">管理並新增自訂方式以存取 Mullvad API。</string>
<string name="api_access_method_info_first_line">該應用程式需要與 Mulvad API 伺服器通訊,以便您登入、取得伺服器清單並執行其他重要作業。</string>
@@ -134,7 +133,6 @@
<string name="enter_voucher_code">輸入優惠券兌換碼</string>
<string name="entry">入口</string>
<string name="error_occurred">發生錯誤了。</string>
- <string name="error_state">保護連線失敗</string>
<string name="exclude_applications">已排除的應用程式</string>
<string name="exit">出口</string>
<string name="failed_to_block_internet">無法封鎖所有網路流量。請排除故障或傳送問題回報。</string>
@@ -170,6 +168,7 @@
<string name="invalid_voucher">憑證兌換碼無效。</string>
<string name="ipv6_unavailable">無法配置 IPv6。請在應用程式中將其停用,或是在您的裝置上啟用它。</string>
<string name="is_offline">您的裝置已離線。在您的裝置重新上線後,通道將自動連線。</string>
+ <string name="legacy_always_on_vpn_error_notification_title">「始終啟用 VPN」已指派給其他應用程式</string>
<string name="less_than_a_day_left">剩餘時間不足 1 天</string>
<string name="less_than_one_day">不足 1 天</string>
<string name="list_name">清單名稱</string>
@@ -334,7 +333,6 @@
<string name="voucher_is_account_number">您輸入的似乎是帳戶,而不是憑證代碼。如果您想變更有效帳戶,請先登出。</string>
<string name="voucher_success_title">憑證已成功兌換。</string>
<string name="vpn_permission_denied_error">建立通道時,VPN 權限被拒絕。請嘗試重新連線。</string>
- <string name="vpn_permission_error_notification_message">可能已為另一個應用程式啟用了「始終啟用 VPN」</string>
<string name="vpn_permission_error_notification_title">VPN 權限錯誤</string>
<string name="we_will_look_into_this">我們會對此進行調查。</string>
<string name="wireguard_custon_port_title">自訂</string>
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index 4625fb3b5f..fc2cf0cce1 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -105,7 +105,7 @@
<string name="edit_message">Edit message</string>
<string name="try_again">Try again</string>
<string name="blocked_connection">BLOCKED CONNECTION</string>
- <string name="error_state">FAILED TO SECURE CONNECTION</string>
+ <string name="error_state">FAILED TO CONNECT</string>
<string name="connected">Connected</string>
<string name="cancel">Cancel</string>
<string name="disconnect">Disconnect</string>
@@ -164,12 +164,15 @@
<string name="hide_account_number">Hide account number</string>
<string name="failed_to_remove_device">Failed to remove device</string>
<string name="changes_dialog_subtitle">Changes in this version:</string>
- <string name="always_on_vpn_error_notification_title">Always-on VPN assigned to other app</string>
+ <string name="always_on_vpn_error_notification_title">Always-on VPN assigned to %s</string>
<string name="always_on_vpn_error_notification_content">
<![CDATA[Unable to start tunnel connection. Please disable Always-on VPN for <b>%s</b> before using Mullvad VPN.]]>
</string>
+ <string name="legacy_always_on_vpn_error_notification_title">Always-on VPN assigned to other app</string>
+ <string name="legacy_always_on_vpn_error_notification_content">Unable to start tunnel connection. Please disable Always-on VPN before using Mullvad VPN.</string>
<string name="vpn_permission_error_notification_title">VPN permission error</string>
- <string name="vpn_permission_error_notification_message">Always-on VPN might be enabled for another app</string>
+ <string name="vpn_permission_error_notification_message">Please press connect to request VPN permission</string>
+
<string name="new_device_notification_title">NEW DEVICE CREATED</string>
<string name="new_device_notification_message">
<![CDATA[Welcome, this device is now called <b>%s</b>. For more details see the info button in Account.]]>
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt
index 2dbd15ec03..08a0a517f0 100644
--- a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt
@@ -2,7 +2,6 @@ package net.mullvad.mullvadvpn.lib.shared
import arrow.core.Either
import arrow.core.raise.either
-import arrow.core.raise.ensure
import kotlinx.coroutines.flow.combine
import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
import net.mullvad.mullvadvpn.lib.model.ConnectError
@@ -12,7 +11,7 @@ import net.mullvad.mullvadvpn.lib.model.TunnelState
class ConnectionProxy(
private val managementService: ManagementService,
translationRepository: RelayLocationTranslationRepository,
- private val vpnPermissionRepository: VpnPermissionRepository,
+ private val vpnProfileUseCase: VpnProfileUseCase,
) {
val tunnelState =
combine(managementService.tunnelState, translationRepository.translations) {
@@ -35,7 +34,7 @@ class ConnectionProxy(
copy(city = translations[city] ?: city, country = translations[country] ?: country)
suspend fun connect(): Either<ConnectError, Boolean> = either {
- ensure(vpnPermissionRepository.hasVpnPermission()) { ConnectError.NoVpnPermission }
+ vpnProfileUseCase.prepareVpn().mapLeft(ConnectError::NotPrepared).bind()
managementService.connect().bind()
}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt
deleted file mode 100644
index b97c60316c..0000000000
--- a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.lib.shared
-
-import android.content.Context
-import android.net.VpnService
-import net.mullvad.mullvadvpn.lib.common.util.getAlwaysOnVpnAppName
-
-class VpnPermissionRepository(private val applicationContext: Context) {
- fun hasVpnPermission(): Boolean = VpnService.prepare(applicationContext) == null
-
- fun getAlwaysOnVpnAppName() = applicationContext.getAlwaysOnVpnAppName()
-}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnProfileUseCase.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnProfileUseCase.kt
new file mode 100644
index 0000000000..cebac0be04
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnProfileUseCase.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import android.content.Context
+import arrow.core.Either
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
+import net.mullvad.mullvadvpn.lib.model.PrepareError
+import net.mullvad.mullvadvpn.lib.model.Prepared
+
+class VpnProfileUseCase(private val applicationContext: Context) {
+ fun prepareVpn(): Either<PrepareError, Prepared> = applicationContext.prepareVpnSafe()
+}
diff --git a/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt b/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt
index 0652867105..24a14ce782 100644
--- a/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt
+++ b/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt
@@ -1,31 +1,36 @@
package net.mullvad.mullvadvpn.lib.shared
+import android.content.Intent
+import arrow.core.left
+import arrow.core.right
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.unmockkAll
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.PrepareError
+import net.mullvad.mullvadvpn.lib.model.Prepared
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
class ConnectionProxyTest {
private val mockManagementService: ManagementService = mockk(relaxed = true)
- private val mockVpnPermissionRepository: VpnPermissionRepository = mockk()
+ private val mockVpnPermissionRepository: VpnProfileUseCase = mockk()
private val mockTranslationRepository: RelayLocationTranslationRepository =
mockk(relaxed = true)
private val connectionProxy: ConnectionProxy =
ConnectionProxy(
managementService = mockManagementService,
- vpnPermissionRepository = mockVpnPermissionRepository,
+ vpnProfileUseCase = mockVpnPermissionRepository,
translationRepository = mockTranslationRepository,
)
@Test
fun `connect with vpn permission allowed should call managementService connect`() = runTest {
- every { mockVpnPermissionRepository.hasVpnPermission() } returns true
+ every { mockVpnPermissionRepository.prepareVpn() } returns Prepared.right()
connectionProxy.connect()
coVerify(exactly = 1) { mockManagementService.connect() }
}
@@ -33,7 +38,8 @@ class ConnectionProxyTest {
@Test
fun `connect with vpn permission not allowed should not call managementService connect`() =
runTest {
- every { mockVpnPermissionRepository.hasVpnPermission() } returns false
+ every { mockVpnPermissionRepository.prepareVpn() } returns
+ PrepareError.NotPrepared(Intent()).left()
connectionProxy.connect()
coVerify(exactly = 0) { mockManagementService.connect() }
}
diff --git a/android/lib/talpid/build.gradle.kts b/android/lib/talpid/build.gradle.kts
index c53c2add28..24ba625ff2 100644
--- a/android/lib/talpid/build.gradle.kts
+++ b/android/lib/talpid/build.gradle.kts
@@ -30,9 +30,11 @@ android {
dependencies {
implementation(projects.lib.model)
+ implementation(projects.lib.common)
implementation(libs.androidx.ktx)
implementation(libs.androidx.lifecycle.service)
+ implementation(libs.arrow)
implementation(libs.kermit)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
index dc1f8d23ca..74d44005cd 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
@@ -10,6 +10,8 @@ import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import kotlin.properties.Delegates.observable
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
+import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.talpid.model.CreateTunResult
import net.mullvad.talpid.model.TunConfig
import net.mullvad.talpid.util.TalpidSdkUtils.setMeteredIfSupported
@@ -76,10 +78,11 @@ open class TalpidVpnService : LifecycleVpnService() {
// Function is to be cleaned up and lint suppression to be removed.
@Suppress("ReturnCount")
private fun createTun(config: TunConfig): CreateTunResult {
- if (prepare(this) != null) {
- // VPN permission wasn't granted
- return CreateTunResult.PermissionDenied
- }
+ prepareVpnSafe()
+ .mapLeft { it.toCreateTunResult() }
+ .onLeft {
+ return it
+ }
val invalidDnsServerAddresses = ArrayList<InetAddress>()
@@ -149,6 +152,13 @@ open class TalpidVpnService : LifecycleVpnService() {
return protect(socket)
}
+ private fun PrepareError.toCreateTunResult() =
+ when (this) {
+ is PrepareError.OtherLegacyAlwaysOnVpn -> CreateTunResult.OtherLegacyAlwaysOnVpn
+ is PrepareError.NotPrepared -> CreateTunResult.NotPrepared
+ is PrepareError.OtherAlwaysOnApp -> CreateTunResult.OtherAlwaysOnApp(appName)
+ }
+
private fun InetAddress.prefixLength(): Int =
when (this) {
is Inet4Address -> IPV4_PREFIX_LENGTH
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt
index 089112e3ab..3cd73685f7 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt
@@ -17,7 +17,13 @@ sealed class CreateTunResult {
get() = true
}
- data object PermissionDenied : CreateTunResult()
-
+ // Establish error
data object TunnelDeviceError : CreateTunResult()
+
+ // Prepare errors
+ data object OtherLegacyAlwaysOnVpn : CreateTunResult()
+
+ data class OtherAlwaysOnApp(val appName: String) : CreateTunResult()
+
+ data object NotPrepared : CreateTunResult()
}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt
deleted file mode 100644
index d310deb884..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.talpid.util
-
-import java.net.InetAddress
-
-fun InetAddress.addressString(): String {
- val hostNameAndAddress = this.toString().split('/', limit = 2)
- val address = hostNameAndAddress[1]
-
- return address
-}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt
index 5745377254..cf324e6023 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt
@@ -2,9 +2,9 @@ package net.mullvad.mullvadvpn.service.notifications
import android.app.Service
import android.content.pm.ServiceInfo
-import android.net.VpnService
import android.os.Build
import co.touchlab.kermit.Logger
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
import net.mullvad.mullvadvpn.lib.model.Notification
import net.mullvad.mullvadvpn.lib.model.NotificationChannel
import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState
@@ -40,7 +40,7 @@ class ForegroundNotificationManager(
private fun notifyForeground(tunnelStateNotification: Notification.Tunnel) {
val androidNotification = tunnelStateNotification.toNotification(vpnService)
- if (VpnService.prepare(vpnService) != null) {
+ if (vpnService.prepareVpnSafe().isLeft()) {
// Got connect/disconnect intent, but we don't have permission to go in foreground.
// tunnel state will return permission and we will eventually get stopped by system.
Logger.i("Did not start foreground: VPN permission not granted")
@@ -65,7 +65,7 @@ class ForegroundNotificationManager(
private val defaultNotification =
Notification.Tunnel(
NotificationChannel.TunnelUpdates.id,
- NotificationTunnelState.Disconnected(true),
+ NotificationTunnelState.Disconnected(null),
emptyList(),
false,
)
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt
index c66839ddc5..f836cbcd1b 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt
@@ -6,18 +6,19 @@ import android.content.Intent
import androidx.core.app.NotificationCompat
import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
import net.mullvad.mullvadvpn.lib.common.constant.KEY_DISCONNECT_ACTION
-import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PERMISSION
+import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PROFILE
import net.mullvad.mullvadvpn.lib.common.constant.MAIN_ACTIVITY_CLASS
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
import net.mullvad.mullvadvpn.lib.model.Notification
import net.mullvad.mullvadvpn.lib.model.NotificationAction
import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState
+import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.service.R
internal fun Notification.Tunnel.toNotification(context: Context) =
NotificationCompat.Builder(context, channelId.value)
.setContentIntent(contentIntent(context))
- .setContentTitle(context.getString(state.contentTitleResourceId()))
+ .setContentTitle(state.contentTitleResourceId(context))
.setSmallIcon(R.drawable.small_logo_white)
.apply { actions.forEach { addAction(it.toCompatAction(context)) } }
.setOngoing(ongoing)
@@ -35,37 +36,41 @@ private fun Notification.Tunnel.contentIntent(context: Context): PendingIntent {
return PendingIntent.getActivity(context, 1, intent, SdkUtils.getSupportedPendingIntentFlags())
}
-private fun NotificationTunnelState.contentTitleResourceId(): Int =
+private fun NotificationTunnelState.contentTitleResourceId(context: Context): String =
when (this) {
- NotificationTunnelState.Connected -> R.string.connected
- NotificationTunnelState.Connecting -> R.string.connecting
+ NotificationTunnelState.Connected -> context.getString(R.string.connected)
+ NotificationTunnelState.Connecting -> context.getString(R.string.connecting)
is NotificationTunnelState.Disconnected -> {
- if (this.hasVpnPermission) {
- R.string.disconnected
- } else {
- R.string.disconnected_vpn_permission_error
+ when (prepareError) {
+ is PrepareError.NotPrepared ->
+ context.getString(R.string.disconnected_vpn_permission_error)
+ else -> context.getString(R.string.disconnected)
}
}
- NotificationTunnelState.Disconnecting -> R.string.disconnecting
- NotificationTunnelState.Reconnecting -> R.string.reconnecting
- NotificationTunnelState.Error.Blocking -> R.string.blocking_internet
- is NotificationTunnelState.Error.Critical -> R.string.critical_error
- NotificationTunnelState.Error.DeviceOffline -> R.string.blocking_internet_device_offline
+ NotificationTunnelState.Disconnecting -> context.getString(R.string.disconnecting)
+ NotificationTunnelState.Reconnecting -> context.getString(R.string.reconnecting)
+ NotificationTunnelState.Error.Blocking -> context.getString(R.string.blocking_internet)
+ is NotificationTunnelState.Error.Critical -> context.getString(R.string.critical_error)
+ NotificationTunnelState.Error.DeviceOffline ->
+ context.getString(R.string.blocking_internet_device_offline)
NotificationTunnelState.Error.VpnPermissionDenied ->
- R.string.vpn_permission_error_notification_title
- NotificationTunnelState.Error.AlwaysOnVpn -> R.string.always_on_vpn_error_notification_title
+ context.getString(R.string.vpn_permission_error_notification_title)
+ is NotificationTunnelState.Error.AlwaysOnVpn ->
+ context.getString(R.string.always_on_vpn_error_notification_title, appName)
+ NotificationTunnelState.Error.LegacyLockdown ->
+ context.getString(R.string.legacy_always_on_vpn_error_notification_title)
}
internal fun NotificationAction.Tunnel.toCompatAction(context: Context): NotificationCompat.Action {
val pendingIntent =
- if (this is NotificationAction.Tunnel.RequestPermission) {
+ if (this is NotificationAction.Tunnel.RequestVpnProfile) {
val intent =
Intent().apply {
setClassName(context.packageName, MAIN_ACTIVITY_CLASS)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- setAction(KEY_REQUEST_VPN_PERMISSION)
+ setAction(KEY_REQUEST_VPN_PROFILE)
}
PendingIntent.getActivity(context, 1, intent, SdkUtils.getSupportedPendingIntentFlags())
@@ -85,7 +90,7 @@ fun NotificationAction.Tunnel.titleResource() =
when (this) {
NotificationAction.Tunnel.Cancel -> R.string.cancel
NotificationAction.Tunnel.Connect,
- NotificationAction.Tunnel.RequestPermission -> R.string.connect
+ is NotificationAction.Tunnel.RequestVpnProfile -> R.string.connect
NotificationAction.Tunnel.Disconnect -> R.string.disconnect
NotificationAction.Tunnel.Dismiss -> R.string.dismiss
}
@@ -93,7 +98,7 @@ fun NotificationAction.Tunnel.titleResource() =
fun NotificationAction.Tunnel.toKey() =
when (this) {
NotificationAction.Tunnel.Connect -> KEY_CONNECT_ACTION
- NotificationAction.Tunnel.RequestPermission -> KEY_REQUEST_VPN_PERMISSION
+ is NotificationAction.Tunnel.RequestVpnProfile -> KEY_REQUEST_VPN_PROFILE
NotificationAction.Tunnel.Cancel,
NotificationAction.Tunnel.Disconnect,
NotificationAction.Tunnel.Dismiss -> KEY_DISCONNECT_ACTION
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt
index 2068f1adff..e9c52bba48 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt
@@ -19,15 +19,16 @@ import net.mullvad.mullvadvpn.lib.model.NotificationChannelId
import net.mullvad.mullvadvpn.lib.model.NotificationId
import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState
import net.mullvad.mullvadvpn.lib.model.NotificationUpdate
+import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
-import net.mullvad.mullvadvpn.lib.shared.VpnPermissionRepository
+import net.mullvad.mullvadvpn.lib.shared.VpnProfileUseCase
import net.mullvad.mullvadvpn.service.notifications.NotificationProvider
class TunnelStateNotificationProvider(
connectionProxy: ConnectionProxy,
- vpnPermissionRepository: VpnPermissionRepository,
+ vpnPermissionRepository: VpnProfileUseCase,
deviceRepository: DeviceRepository,
channelId: NotificationChannelId,
scope: CoroutineScope,
@@ -49,8 +50,7 @@ class TunnelStateNotificationProvider(
tunnelState(
tunnelState,
actionAfterDisconnect,
- vpnPermissionRepository.hasVpnPermission(),
- vpnPermissionRepository.getAlwaysOnVpnAppName(),
+ vpnPermissionRepository.prepareVpn().leftOrNull(),
)
return@combine NotificationUpdate.Notify(
@@ -68,14 +68,9 @@ class TunnelStateNotificationProvider(
private fun tunnelState(
tunnelState: TunnelState,
actionAfterDisconnect: ActionAfterDisconnect?,
- hasVpnPermission: Boolean,
- alwaysOnVpnPermissionName: String?,
+ prepareError: PrepareError?,
): NotificationTunnelState =
- tunnelState.toNotificationTunnelState(
- actionAfterDisconnect,
- hasVpnPermission,
- alwaysOnVpnPermissionName,
- )
+ tunnelState.toNotificationTunnelState(actionAfterDisconnect, prepareError)
private fun Flow<TunnelState>.actionAfterDisconnect(): Flow<ActionAfterDisconnect?> =
filterIsInstance<TunnelState.Disconnecting>()
@@ -84,11 +79,10 @@ class TunnelStateNotificationProvider(
private fun TunnelState.toNotificationTunnelState(
actionAfterDisconnect: ActionAfterDisconnect?,
- hasVpnPermission: Boolean,
- alwaysOnVpnPermissionName: String?,
+ prepareError: PrepareError?,
) =
when (this) {
- is TunnelState.Disconnected -> NotificationTunnelState.Disconnected(hasVpnPermission)
+ is TunnelState.Disconnected -> NotificationTunnelState.Disconnected(prepareError)
is TunnelState.Connecting -> {
if (actionAfterDisconnect == ActionAfterDisconnect.Reconnect) {
NotificationTunnelState.Reconnecting
@@ -104,20 +98,21 @@ class TunnelStateNotificationProvider(
}
}
is TunnelState.Connected -> NotificationTunnelState.Connected
- is TunnelState.Error -> toNotificationTunnelState(alwaysOnVpnPermissionName)
+ is TunnelState.Error -> toNotificationTunnelState()
}
- private fun TunnelState.Error.toNotificationTunnelState(
- alwaysOnVpnPermissionName: String?
- ): NotificationTunnelState.Error {
+ private fun TunnelState.Error.toNotificationTunnelState(): NotificationTunnelState.Error {
val cause = errorState.cause
return when {
cause is ErrorStateCause.IsOffline && errorState.isBlocking ->
NotificationTunnelState.Error.DeviceOffline
cause is ErrorStateCause.InvalidDnsServers -> NotificationTunnelState.Error.Blocking
- cause is ErrorStateCause.VpnPermissionDenied ->
- alwaysOnVpnPermissionName?.let { NotificationTunnelState.Error.AlwaysOnVpn }
- ?: NotificationTunnelState.Error.VpnPermissionDenied
+ cause is ErrorStateCause.OtherLegacyAlwaysOnApp ->
+ NotificationTunnelState.Error.LegacyLockdown
+ cause is ErrorStateCause.NotPrepared ->
+ NotificationTunnelState.Error.VpnPermissionDenied
+ cause is ErrorStateCause.OtherAlwaysOnApp ->
+ NotificationTunnelState.Error.AlwaysOnVpn(cause.appName)
errorState.isBlocking -> NotificationTunnelState.Error.Blocking
else -> NotificationTunnelState.Error.Critical
}
@@ -126,10 +121,11 @@ class TunnelStateNotificationProvider(
private fun NotificationTunnelState.toAction(): NotificationAction.Tunnel =
when (this) {
is NotificationTunnelState.Disconnected -> {
- if (this.hasVpnPermission) {
- NotificationAction.Tunnel.Connect
- } else {
- NotificationAction.Tunnel.RequestPermission
+ when (prepareError) {
+ is PrepareError.OtherAlwaysOnApp,
+ is PrepareError.OtherLegacyAlwaysOnVpn,
+ null -> NotificationAction.Tunnel.Connect
+ is PrepareError.NotPrepared -> NotificationAction.Tunnel.RequestVpnProfile
}
}
NotificationTunnelState.Disconnecting -> NotificationAction.Tunnel.Connect
@@ -140,6 +136,7 @@ class TunnelStateNotificationProvider(
is NotificationTunnelState.Error.Critical,
NotificationTunnelState.Error.DeviceOffline,
NotificationTunnelState.Error.VpnPermissionDenied,
- NotificationTunnelState.Error.AlwaysOnVpn -> NotificationAction.Tunnel.Dismiss
+ is NotificationTunnelState.Error.AlwaysOnVpn,
+ NotificationTunnelState.Error.LegacyLockdown -> NotificationAction.Tunnel.Dismiss
}
}
diff --git a/android/tile/build.gradle.kts b/android/tile/build.gradle.kts
index b1c558685c..816c7de883 100644
--- a/android/tile/build.gradle.kts
+++ b/android/tile/build.gradle.kts
@@ -40,6 +40,7 @@ dependencies {
implementation(libs.koin.android)
implementation(libs.androidx.appcompat)
+ implementation(libs.arrow)
implementation(libs.kermit)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
diff --git a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
index b1fa9ae311..9fe3c06dfc 100644
--- a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
+++ b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Icon
-import android.net.VpnService
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
@@ -26,6 +25,7 @@ import net.mullvad.mullvadvpn.lib.common.constant.MAIN_ACTIVITY_CLASS
import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.setSubtitleIfSupported
+import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState
import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
@@ -88,9 +88,26 @@ class MullvadTileService : TileService() {
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun toggleTunnel() {
- val isSetup = VpnService.prepare(applicationContext) == null
+ val isSetup = applicationContext.prepareVpnSafe().isRight()
// TODO This logic should be more advanced, we should ensure user has an account setup etc.
- if (!isSetup) {
+ if (isSetup) {
+ Logger.i("TileService: VPN service is setup")
+
+ val intent =
+ Intent().apply {
+ setClassName(applicationContext.packageName, VPN_SERVICE_CLASS)
+ action =
+ if (qsTile.state == Tile.STATE_INACTIVE) {
+ KEY_CONNECT_ACTION
+ } else {
+ KEY_DISCONNECT_ACTION
+ }
+ }
+
+ // Always start as foreground, e.g if app is dead we won't be allowed to start if not
+ // in foreground.
+ startForegroundService(intent)
+ } else {
Logger.i("TileService: VPN service not setup, starting main activity")
val intent =
@@ -103,24 +120,7 @@ class MullvadTileService : TileService() {
action = Intent.ACTION_MAIN
}
startActivityAndCollapseCompat(intent)
- return
- } else {
- Logger.i("TileService: VPN service is setup")
}
- val intent =
- Intent().apply {
- setClassName(applicationContext.packageName, VPN_SERVICE_CLASS)
- action =
- if (qsTile.state == Tile.STATE_INACTIVE) {
- KEY_CONNECT_ACTION
- } else {
- KEY_DISCONNECT_ACTION
- }
- }
-
- // Always start as foreground, e.g if app is dead we won't be allowed to start if not
- // in foreground.
- startForegroundService(intent)
}
@SuppressLint("StartActivityAndCollapseDeprecated")