diff options
| author | saber safavi <saber.safavi@codic.se> | 2022-10-20 09:01:13 +0200 |
|---|---|---|
| committer | saber safavi <saber.safavi@codic.se> | 2023-01-03 17:33:19 +0100 |
| commit | 4d6e88045c62a37db20f7fa6ab8d0f4ca52d0000 (patch) | |
| tree | d38abac4a85a3e8f27c4de231448834b3581c2c1 /android | |
| parent | 7a0aa5b8ff03d7bfdf0c463f9ff6579e494990ff (diff) | |
| download | mullvadvpn-4d6e88045c62a37db20f7fa6ab8d0f4ca52d0000.tar.xz mullvadvpn-4d6e88045c62a37db20f7fa6ab8d0f4ca52d0000.zip | |
Add always-on in-app notification
Diffstat (limited to 'android')
9 files changed, 158 insertions, 68 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 b9c935a9cc..33121b0502 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -187,9 +187,10 @@ <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> +<![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 might be enabled for another - app</string> + <string name="vpn_permission_error_notification_message">Always-on VPN might be enabled for + another app</string> </resources> |
