Skip to content

Commit 6274589

Browse files
committed
Merge branch 'toggletunnel-crash-in-tile-droid-1283'
2 parents b0c1bcc + 370e49b commit 6274589

File tree

4 files changed

+43
-29
lines changed

4 files changed

+43
-29
lines changed

android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt

+25-24
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import arrow.atomic.AtomicInt
1212
import co.touchlab.kermit.Logger
1313
import java.io.File
1414
import kotlinx.coroutines.flow.filter
15-
import kotlinx.coroutines.flow.first
15+
import kotlinx.coroutines.flow.filterIsInstance
1616
import kotlinx.coroutines.launch
1717
import kotlinx.coroutines.runBlocking
1818
import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
@@ -90,6 +90,15 @@ class MullvadVpnService : TalpidVpnService() {
9090

9191
Logger.i("Start management service")
9292
managementService.start()
93+
94+
lifecycleScope.launch {
95+
// If the service is started with a connect command and a non-blocking error occur (e.g.
96+
// unable to start the tunnel) then the service is demoted from foreground.
97+
managementService.tunnelState
98+
.filterIsInstance<TunnelState.Error>()
99+
.filter { !it.errorState.isBlocking }
100+
.collect { foregroundNotificationHandler.stopForeground() }
101+
}
93102
}
94103

95104
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -105,15 +114,24 @@ class MullvadVpnService : TalpidVpnService() {
105114
keyguardManager.isKeyguardLocked -> {
106115
Logger.i("Keyguard is locked, ignoring command")
107116
}
117+
108118
intent.isFromSystem() || intent?.action == KEY_CONNECT_ACTION -> {
109-
// Only show on foreground if we have permission
110-
if (prepare(this) == null) {
111-
foregroundNotificationHandler.startForeground()
112-
}
119+
foregroundNotificationHandler.startForeground()
113120
lifecycleScope.launch { connectionProxy.connectWithoutPermissionCheck() }
114121
}
122+
115123
intent?.action == KEY_DISCONNECT_ACTION -> {
124+
// MullvadTileService might have launched this service with the expectancy of it
125+
// being foreground, thus it must go into foreground to please the android system
126+
// requirements.
127+
foregroundNotificationHandler.startForeground()
116128
lifecycleScope.launch { connectionProxy.disconnect() }
129+
130+
// If disconnect intent is received and no one is using this service, simply stop
131+
// foreground and let system stop service when it deems it not to be necessary.
132+
if (bindCount.get() == 0) {
133+
foregroundNotificationHandler.stopForeground()
134+
}
117135
}
118136
}
119137

@@ -180,25 +198,6 @@ class MullvadVpnService : TalpidVpnService() {
180198
foregroundNotificationHandler.stopForeground()
181199
}
182200

183-
if (count == 0) {
184-
Logger.i("No one bound to the service, stopSelf()")
185-
lifecycleScope.launch {
186-
Logger.i("Waiting for disconnected state")
187-
// TODO This needs reworking, we should not wait for the disconnected state, what we
188-
// want is the notification of disconnected to go out before we start shutting down
189-
connectionProxy.tunnelState
190-
.filter {
191-
it is TunnelState.Disconnected ||
192-
(it is TunnelState.Error && !it.errorState.isBlocking)
193-
}
194-
.first()
195-
196-
if (bindCount.get() == 0) {
197-
Logger.i("Stopping service")
198-
stopSelf()
199-
}
200-
}
201-
}
202201
return true
203202
}
204203

@@ -209,8 +208,10 @@ class MullvadVpnService : TalpidVpnService() {
209208

210209
Logger.i("Shutdown MullvadDaemon")
211210
MullvadDaemon.shutdown()
211+
212212
Logger.i("Enter Idle")
213213
managementService.enterIdle()
214+
214215
Logger.i("Shutdown complete")
215216
super.onDestroy()
216217
}

android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationManager.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ import android.content.Context
55
import android.content.pm.PackageManager
66
import androidx.core.app.ActivityCompat
77
import androidx.core.app.NotificationManagerCompat
8+
import kotlin.time.Duration.Companion.milliseconds
89
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.FlowPreview
11+
import kotlinx.coroutines.flow.debounce
912
import kotlinx.coroutines.flow.merge
1013
import kotlinx.coroutines.launch
1114
import net.mullvad.mullvadvpn.lib.model.Notification
1215
import net.mullvad.mullvadvpn.lib.model.NotificationUpdate
1316
import net.mullvad.mullvadvpn.service.notifications.accountexpiry.toNotification
1417
import net.mullvad.mullvadvpn.service.notifications.tunnelstate.toNotification
1518

19+
@OptIn(FlowPreview::class)
1620
class NotificationManager(
1721
private val notificationManagerCompat: NotificationManagerCompat,
1822
notificationProviders: List<NotificationProvider<Notification>>,
@@ -23,14 +27,15 @@ class NotificationManager(
2327
init {
2428
scope.launch {
2529
notificationProviders
26-
.map { it.notifications }
30+
.map { it.notifications.debounce(NOTIFICATION_DEBOUNCE) }
2731
.merge()
2832
.collect { notificationUpdate ->
2933
when (notificationUpdate) {
3034
is NotificationUpdate.Cancel ->
3135
notificationManagerCompat.cancel(
3236
notificationUpdate.notificationId.value
3337
)
38+
3439
is NotificationUpdate.Notify -> {
3540
val notification = notificationUpdate.value
3641
val androidNotification = notification.toAndroidNotification(context)
@@ -56,4 +61,11 @@ class NotificationManager(
5661
is Notification.Tunnel -> toNotification(context)
5762
is Notification.AccountExpiry -> toNotification(context)
5863
}
64+
65+
companion object {
66+
// According to testing we are only allowed to send 5 notifications per second at most,
67+
// otherwise the system will start dropping them. To ensure we don't drop the latest
68+
// notification debounce if we spam too much.
69+
val NOTIFICATION_DEBOUNCE = 200.milliseconds
70+
}
5971
}

android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ class TunnelStateNotificationProvider(
3939
connectionProxy.tunnelState,
4040
connectionProxy.tunnelState.actionAfterDisconnect().distinctUntilChanged(),
4141
deviceRepository.deviceState,
42-
) { tunnelState: TunnelState, actionAfterDisconnect: ActionAfterDisconnect?, deviceState
43-
->
42+
) { tunnelState, actionAfterDisconnect, deviceState ->
4443
if (
4544
deviceState is DeviceState.LoggedOut && tunnelState is TunnelState.Disconnected
4645
) {
@@ -113,7 +112,8 @@ class TunnelStateNotificationProvider(
113112
): NotificationTunnelState.Error {
114113
val cause = errorState.cause
115114
return when {
116-
cause is ErrorStateCause.IsOffline -> NotificationTunnelState.Error.DeviceOffline
115+
cause is ErrorStateCause.IsOffline && errorState.isBlocking ->
116+
NotificationTunnelState.Error.DeviceOffline
117117
cause is ErrorStateCause.InvalidDnsServers -> NotificationTunnelState.Error.Blocking
118118
cause is ErrorStateCause.VpnPermissionDenied ->
119119
alwaysOnVpnPermissionName?.let { NotificationTunnelState.Error.AlwaysOnVpn }

android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ class MullvadTileService : TileService() {
118118
}
119119
}
120120

121-
// Always start as foreground in case tile is out-of-sync.
121+
// Always start as foreground, e.g if app is dead we won't be allowed to start if not
122+
// in foreground.
122123
startForegroundService(intent)
123124
}
124125

0 commit comments

Comments
 (0)