summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-01-03 17:52:00 +0100
committerAlbin <albin@mullvad.net>2023-01-03 17:52:00 +0100
commit4faab6b77aebc18238bd377458eecf9f708dc44f (patch)
treed38abac4a85a3e8f27c4de231448834b3581c2c1
parent8ad5aa6a71f55b7d4b6996a848698bd521a1311f (diff)
parent4d6e88045c62a37db20f7fa6ab8d0f4ca52d0000 (diff)
downloadmullvadvpn-4faab6b77aebc18238bd377458eecf9f708dc44f.tar.xz
mullvadvpn-4faab6b77aebc18238bd377458eecf9f708dc44f.zip
Merge branch 'detect-always-on-vpn'
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt27
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt71
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt21
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt30
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt48
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SdkUtils.kt9
-rw-r--r--android/app/src/main/res/values/strings.xml9
-rw-r--r--gui/locales/messages.pot12
10 files changed, 174 insertions, 64 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
index ed14fceae9..06c77e5960 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
@@ -11,6 +11,7 @@ import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.ui.MainActivity
import net.mullvad.mullvadvpn.util.SdkUtils
+import net.mullvad.mullvadvpn.util.getErrorNotificationResources
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
class TunnelStateNotification(val context: Context) {
@@ -47,11 +48,7 @@ class TunnelStateNotification(val context: Context) {
}
}
is TunnelState.Error -> {
- if (state.errorState.isBlocking) {
- R.string.blocking_all_connections
- } else {
- R.string.critical_error
- }
+ state.errorState.getErrorNotificationResources(context).titleResourceId
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
index 6314c3eaef..3362aef52f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
@@ -3,9 +3,13 @@ package net.mullvad.mullvadvpn.ui.extension
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.provider.Settings
import androidx.fragment.app.Fragment
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.ui.MainActivity
+import net.mullvad.mullvadvpn.util.SdkUtils.getInstalledPackagesList
+
+private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
fun Context.openAccountPageInBrowser(authToken: String) {
startActivity(
@@ -16,6 +20,16 @@ fun Context.openAccountPageInBrowser(authToken: String) {
)
}
+fun Context.getAlwaysOnVpnAppName(): String? {
+ return resolveAlwaysOnVpnPackageName()
+ ?.let { currentAlwaysOnVpn ->
+ packageManager.getInstalledPackagesList(0)
+ .singleOrNull {
+ it.packageName == currentAlwaysOnVpn && it.packageName != packageName
+ }
+ }?.applicationInfo?.loadLabel(packageManager)?.toString()
+}
+
fun Fragment.requireMainActivity(): MainActivity {
return if (this.activity is MainActivity) {
this.activity as MainActivity
@@ -25,3 +39,16 @@ fun Fragment.requireMainActivity(): MainActivity {
)
}
}
+
+// NOTE: This function will return the current Always-on VPN package's name. In case of either
+// Always-on VPN being disabled or not being able to read the state, NULL will be returned.
+fun Context.resolveAlwaysOnVpnPackageName(): String? {
+ return try {
+ Settings.Secure.getString(
+ contentResolver,
+ ALWAYS_ON_VPN_APP
+ )
+ } catch (ex: SecurityException) {
+ null
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
index 10927cb5e7..f84a6b0553 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
@@ -3,18 +3,13 @@ package net.mullvad.mullvadvpn.ui.notification
import android.content.Context
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.util.getErrorNotificationResources
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.tunnel.ParameterGenerationError
-import net.mullvad.talpid.util.addressString
class TunnelStateNotification(
private val context: Context,
) : InAppNotification() {
- private val blockingTitle = context.getString(R.string.blocking_internet)
- private val notBlockingTitle = context.getString(R.string.not_blocking_internet)
-
init {
status = StatusLevel.Error
onClick = null
@@ -26,72 +21,32 @@ class TunnelStateNotification(
is TunnelState.Disconnecting -> {
when (state.actionAfterDisconnect) {
ActionAfterDisconnect.Nothing -> hide()
- ActionAfterDisconnect.Block -> show(null)
- ActionAfterDisconnect.Reconnect -> show(null)
+ ActionAfterDisconnect.Block -> showGenericBlockingMessage()
+ ActionAfterDisconnect.Reconnect -> showGenericBlockingMessage()
}
}
is TunnelState.Disconnected -> hide()
- is TunnelState.Connecting -> show(null)
+ is TunnelState.Connecting -> showGenericBlockingMessage()
is TunnelState.Connected -> hide()
- is TunnelState.Error -> show(state.errorState)
+ is TunnelState.Error -> showError(state.errorState)
}
update()
}
- private fun show(error: ErrorState?) {
+ private fun showError(error: ErrorState) {
// if the error state is null, we can assume that we are secure
- if (error?.isBlocking ?: true) {
- title = blockingTitle
- message = error?.cause?.let { cause -> blockingErrorMessage(cause) }
- } else {
- title = notBlockingTitle
- message = notBlockingErrorMessage(error?.cause)
+ error.getErrorNotificationResources(context).apply {
+ title = this.getTitleText(context.resources)
+ message = this.getMessageText(context.resources)
}
-
shouldShow = true
}
- private fun blockingErrorMessage(cause: ErrorStateCause): String {
- val messageId = when (cause) {
- is ErrorStateCause.InvalidDnsServers -> {
- val addresses = cause.addresses
- .map { address -> address.addressString() }
- .joinToString()
-
- return context.getString(R.string.invalid_dns_servers, addresses)
- }
- is ErrorStateCause.AuthFailed -> R.string.auth_failed
- is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
- is ErrorStateCause.SetFirewallPolicyError -> R.string.set_firewall_policy_error
- is ErrorStateCause.SetDnsError -> R.string.set_dns_error
- is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
- is ErrorStateCause.IsOffline -> R.string.is_offline
- is ErrorStateCause.TunnelParameterError -> {
- when (cause.error) {
- ParameterGenerationError.NoMatchingRelay -> R.string.no_matching_relay
- ParameterGenerationError.NoMatchingBridgeRelay -> {
- R.string.no_matching_bridge_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
- }
-
- return context.getString(messageId)
- }
-
- private fun notBlockingErrorMessage(cause: ErrorStateCause?): String {
- val messageId = when (cause) {
- is ErrorStateCause.VpnPermissionDenied -> R.string.vpn_permission_denied_error
- else -> R.string.failed_to_block_internet
- }
-
- return context.getString(messageId)
+ private fun showGenericBlockingMessage() {
+ title = context.getString(R.string.blocking_all_connections)
+ message = null
+ shouldShow = true
}
private fun hide() {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
index 502d7d2302..db1c8cf43c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
@@ -4,12 +4,14 @@ import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.animation.ObjectAnimator
import android.content.Context
+import android.text.Html
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.ui.notification.InAppNotification
@@ -132,7 +134,7 @@ class NotificationBanner : FrameLayout {
title.text = notification.title
if (notificationMessage != null) {
- message.text = notificationMessage
+ message.text = Html.fromHtml(notificationMessage, HtmlCompat.FROM_HTML_MODE_LEGACY)
message.visibility = View.VISIBLE
} else {
message.visibility = View.GONE
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt
new file mode 100644
index 0000000000..96b991fcf3
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.util
+
+import android.content.res.Resources
+
+data class ErrorNotificationMessage(
+ val titleResourceId: Int,
+ val messageResourceId: Int,
+ val optionalMessageArgument: String? = null
+) {
+ fun getTitleText(resources: Resources): String {
+ return resources.getString(titleResourceId)
+ }
+
+ fun getMessageText(resources: Resources): String {
+ return if (optionalMessageArgument != null) {
+ resources.getString(messageResourceId, optionalMessageArgument)
+ } else {
+ resources.getString(messageResourceId)
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt
new file mode 100644
index 0000000000..0e8c2c09b4
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt
@@ -0,0 +1,30 @@
+package net.mullvad.mullvadvpn.util
+
+import net.mullvad.mullvadvpn.R
+import net.mullvad.talpid.tunnel.ErrorStateCause
+import net.mullvad.talpid.tunnel.ParameterGenerationError
+
+fun ErrorStateCause.errorMessageId(): Int {
+ return when (this) {
+ is ErrorStateCause.InvalidDnsServers -> R.string.invalid_dns_servers
+ is ErrorStateCause.AuthFailed -> R.string.auth_failed
+ is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
+ is ErrorStateCause.SetFirewallPolicyError -> R.string.set_firewall_policy_error
+ is ErrorStateCause.SetDnsError -> 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 -> R.string.no_matching_relay
+ ParameterGenerationError.NoMatchingBridgeRelay -> {
+ R.string.no_matching_bridge_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
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt
new file mode 100644
index 0000000000..2e9c96a169
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt
@@ -0,0 +1,48 @@
+package net.mullvad.mullvadvpn.util
+
+import android.content.Context
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.ui.extension.getAlwaysOnVpnAppName
+import net.mullvad.talpid.tunnel.ErrorState
+import net.mullvad.talpid.tunnel.ErrorStateCause
+import net.mullvad.talpid.util.addressString
+
+fun ErrorState.getErrorNotificationResources(context: Context): ErrorNotificationMessage {
+ return when {
+ cause is ErrorStateCause.InvalidDnsServers -> {
+ ErrorNotificationMessage(
+ R.string.blocking_all_connections,
+ cause.errorMessageId(),
+ cause.addresses.joinToString { address -> address.addressString() }
+ )
+ }
+ cause is ErrorStateCause.VpnPermissionDenied -> {
+ resolveAlwaysOnVpnErrorNotificationMessage(context.getAlwaysOnVpnAppName())
+ }
+ isBlocking -> ErrorNotificationMessage(
+ R.string.blocking_all_connections,
+ 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
+ )
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SdkUtils.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SdkUtils.kt
index ad0bb1d0d5..67491ad6fe 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SdkUtils.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SdkUtils.kt
@@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.util
import android.Manifest
import android.app.PendingIntent
import android.content.Context
+import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.VpnService
import android.os.Build
@@ -34,4 +35,12 @@ object SdkUtils {
this.subtitle = subtitleText
}
}
+
+ fun PackageManager.getInstalledPackagesList(flags: Int = 0): List<PackageInfo> =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getInstalledPackages(PackageManager.PackageInfoFlags.of(flags.toLong()))
+ } else {
+ @Suppress("DEPRECATION")
+ getInstalledPackages(flags)
+ }
}
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 5e9a768c8b..33121b0502 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -184,4 +184,13 @@
<string name="failed_to_remove_device">Failed to remove device</string>
<string name="changes_dialog_subtitle">Changes in this version:</string>
<string name="changes_dialog_dismiss_button">Got it!</string>
+ <string name="always_on_vpn_error_notification_title">Always-on VPN assigned to other
+ app</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="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>
</resources>
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index 62e195a631..1ed2f4ed1f 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -1536,6 +1536,12 @@ msgstr ""
msgid "All applications"
msgstr ""
+msgid "Always-on VPN assigned to other app"
+msgstr ""
+
+msgid "Always-on VPN might be enabled for another app"
+msgstr ""
+
msgid "Blocking all connections"
msgstr ""
@@ -1626,12 +1632,18 @@ msgstr ""
msgid "Toggle VPN"
msgstr ""
+msgid "Unable to start tunnel connection. Please disable Always-on VPN for <b>%s</b> before using Mullvad VPN."
+msgstr ""
+
msgid "Unsecured"
msgstr ""
msgid "Update available, download to remain safe."
msgstr ""
+msgid "VPN permission error"
+msgstr ""
+
msgid "VPN permission was denied when creating the tunnel. Please try connecting again."
msgstr ""