summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-06-27 17:26:29 +0200
committerAlbin <albin@mullvad.net>2022-06-27 17:26:29 +0200
commit8ca56fdc02e369aff199111e0ce6af8c2393ab3a (patch)
treea9f8a5b8e92458c5f39b6ca725d802366e4e001b
parent467cfab4dbf4d1cfe79f00718fe7129f9e1b3f8b (diff)
parenta9988280101b6e3ef364ee1fe5703fa7be4a81bd (diff)
downloadmullvadvpn-8ca56fdc02e369aff199111e0ce6af8c2393ab3a.tar.xz
mullvadvpn-8ca56fdc02e369aff199111e0ce6af8c2393ab3a.zip
Merge branch 'fix-android-foreground-and-tile-state'
-rw-r--r--CHANGELOG.md3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt13
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt114
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt6
4 files changed, 92 insertions, 44 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2ba3f2e8c..8d4d5c2d51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,9 @@ Line wrap the file at 100 chars. Th
#### Android
- Fix unused dependencies loaded in the service/tile DI graph.
- Fix missing IPC message unregistration causing multiple copies of some messages to be received.
+- Fix quick settings tile being unresponsive and causing crashes on some devices.
+- Fix quick settings tile not working when the device is locked. It will now prompt the user to
+ unlock the device before attempting to toggle the tunnel state.
### Security
#### Android
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
index e98646d34f..0bf7e54606 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
@@ -8,6 +8,7 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onStart
import net.mullvad.mullvadvpn.model.DeviceState
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.service.endpoint.ConnectionProxy
@@ -55,9 +56,13 @@ class ForegroundNotificationManager(
intermittentDaemon.registerListener(this) { daemon ->
jobTracker.newBackgroundJob("notificationLoggedInJob") {
- daemon?.deviceStateUpdates?.collect { deviceState ->
- loggedIn = deviceState is DeviceState.LoggedIn
- }
+ daemon?.deviceStateUpdates
+ ?.onStart {
+ emit(daemon.getAndEmitDeviceState())
+ }
+ ?.collect { deviceState ->
+ loggedIn = deviceState is DeviceState.LoggedIn
+ }
}
}
@@ -87,7 +92,7 @@ class ForegroundNotificationManager(
}
}
- private fun showOnForeground() {
+ fun showOnForeground() {
service.startForeground(
TunnelStateNotification.NOTIFICATION_ID,
tunnelStateNotification.build()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
index 3fbc627b7e..8b64face8f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
@@ -5,14 +5,17 @@ import android.graphics.drawable.Icon
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.CoroutineScope
+import android.util.Log
import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.ipc.ServiceConnection
import net.mullvad.mullvadvpn.model.ServiceResult
@@ -20,47 +23,66 @@ import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
class MullvadTileService : TileService() {
- private var secured by observable(false) { _, _, _ ->
- updateTileState()
- }
-
- private lateinit var scope: CoroutineScope
+ private val scope = MainScope()
+ private var listenerJob: Job? = null
private lateinit var securedIcon: Icon
private lateinit var unsecuredIcon: Icon
override fun onCreate() {
- super.onCreate()
-
securedIcon = Icon.createWithResource(this, R.drawable.small_logo_white)
unsecuredIcon = Icon.createWithResource(this, R.drawable.small_logo_black)
}
- override fun onStartListening() {
- super.onStartListening()
+ override fun onClick() {
+ // Workaround for the reported bug: https://issuetracker.google.com/issues/236862865
+ suspend fun isUnlockStatusPropagatedWithinTimeout(
+ unlockTimeoutMillis: Long,
+ unlockCheckDelayMillis: Long
+ ): Boolean {
+ return withTimeoutOrNull(unlockTimeoutMillis) {
+ while (isLocked) {
+ delay(unlockCheckDelayMillis)
+ }
+ return@withTimeoutOrNull true
+ } ?: false
+ }
- scope = MainScope()
+ unlockAndRun {
+ runBlocking {
+ val isUnlockStatusPropagated = isUnlockStatusPropagatedWithinTimeout(
+ unlockTimeoutMillis = 1000L,
+ unlockCheckDelayMillis = 100L
+ )
- scope.launch { listenToTunnelState() }
+ if (isUnlockStatusPropagated) {
+ toggleTunnel()
+ } else {
+ Log.e("mullvad", "Unable to toggle tunnel state")
+ }
+ }
+ }
}
- override fun onClick() {
- super.onClick()
+ override fun onStartListening() {
+ listenerJob = scope.launch { listenToTunnelState() }
+ }
- val intent = Intent(this, MullvadVpnService::class.java)
+ override fun onStopListening() {
+ listenerJob?.cancel()
+ }
- if (secured) {
- intent.action = MullvadVpnService.KEY_DISCONNECT_ACTION
- startService(intent)
- } else {
- intent.action = MullvadVpnService.KEY_CONNECT_ACTION
- startForegroundService(intent)
+ private fun toggleTunnel() {
+ val intent = Intent(this, MullvadVpnService::class.java).apply {
+ action = if (qsTile.state == Tile.STATE_INACTIVE) {
+ MullvadVpnService.KEY_CONNECT_ACTION
+ } else {
+ MullvadVpnService.KEY_DISCONNECT_ACTION
+ }
}
- }
- override fun onStopListening() {
- scope.cancel()
- super.onStopListening()
+ // Always start as foreground in case tile is out-of-sync.
+ startForegroundService(intent)
}
@OptIn(FlowPreview::class)
@@ -68,31 +90,44 @@ class MullvadTileService : TileService() {
ServiceConnection(this@MullvadTileService, scope)
.tunnelState
.debounce(300L)
- .collect { updateTunnelState(it.first, it.second) }
+ .map { (tunnelState, connectionState) -> mapToTileState(tunnelState, connectionState) }
+ .collect {
+ updateTileState(it)
+ }
}
- private fun updateTunnelState(
+ private fun mapToTileState(
tunnelState: TunnelState,
connectionState: ServiceResult.ConnectionState
- ) {
- secured = if (connectionState == ServiceResult.ConnectionState.CONNECTED) {
+ ): Int {
+ return if (connectionState == ServiceResult.ConnectionState.CONNECTED) {
when (tunnelState) {
- is TunnelState.Disconnected -> false
- is TunnelState.Connecting -> true
- is TunnelState.Connected -> true
+ is TunnelState.Disconnected -> Tile.STATE_INACTIVE
+ is TunnelState.Connecting -> Tile.STATE_ACTIVE
+ is TunnelState.Connected -> Tile.STATE_ACTIVE
is TunnelState.Disconnecting -> {
- tunnelState.actionAfterDisconnect == ActionAfterDisconnect.Reconnect
+ if (tunnelState.actionAfterDisconnect == ActionAfterDisconnect.Reconnect) {
+ Tile.STATE_ACTIVE
+ } else {
+ Tile.STATE_INACTIVE
+ }
+ }
+ is TunnelState.Error -> {
+ if (tunnelState.errorState.isBlocking) {
+ Tile.STATE_ACTIVE
+ } else {
+ Tile.STATE_INACTIVE
+ }
}
- is TunnelState.Error -> tunnelState.errorState.isBlocking
}
} else {
- false
+ Tile.STATE_INACTIVE
}
}
- private fun updateTileState() {
+ private fun updateTileState(newState: Int) {
qsTile?.apply {
- if (secured) {
+ if (newState == Tile.STATE_ACTIVE) {
state = Tile.STATE_ACTIVE
icon = securedIcon
@@ -107,7 +142,6 @@ class MullvadTileService : TileService() {
subtitle = resources.getText(R.string.unsecured)
}
}
-
updateTile()
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index 9ff1e9f6a5..0a6c29b44e 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -114,6 +114,12 @@ class MullvadVpnService : TalpidVpnService() {
val startResult = super.onStartCommand(intent, flags, startId)
var quitCommand = false
+ // Always promote to foreground if connect/disconnect actions are provided to mitigate cases
+ // where the service would potentially otherwise be too slow running `startForeground`.
+ if (intent?.action == KEY_CONNECT_ACTION || intent?.action == KEY_DISCONNECT_ACTION) {
+ notificationManager.showOnForeground()
+ }
+
notificationManager.updateNotification()
if (!keyguardManager.isDeviceLocked) {