Skip to content

Fix tunnel state notification behavior when logged out #6581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ import arrow.atomic.AtomicInt
import co.touchlab.kermit.Logger
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
Expand All @@ -35,17 +32,14 @@ import net.mullvad.mullvadvpn.service.migration.MigrateSplitTunneling
import net.mullvad.mullvadvpn.service.notifications.ForegroundNotificationManager
import net.mullvad.mullvadvpn.service.notifications.NotificationChannelFactory
import net.mullvad.mullvadvpn.service.notifications.NotificationManager
import net.mullvad.mullvadvpn.service.notifications.ShouldBeOnForegroundProvider
import net.mullvad.talpid.TalpidVpnService
import org.koin.android.ext.android.getKoin
import org.koin.core.context.loadKoinModules
import org.koin.core.qualifier.named

private const val RELAYS_FILE = "relays.json"

class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
private val _shouldBeOnForeground = MutableStateFlow(false)
override val shouldBeOnForeground: StateFlow<Boolean> = _shouldBeOnForeground
class MullvadVpnService : TalpidVpnService() {

private lateinit var keyguardManager: KeyguardManager

Expand Down Expand Up @@ -74,7 +68,7 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
managementService = get()

foregroundNotificationHandler =
ForegroundNotificationManager(this@MullvadVpnService, get(), lifecycleScope)
ForegroundNotificationManager(this@MullvadVpnService, get())
get<NotificationManager>()

apiEndpointConfiguration = get()
Expand All @@ -86,8 +80,6 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {

keyguardManager = getSystemService<KeyguardManager>()!!

lifecycleScope.launch { foregroundNotificationHandler.start(this@MullvadVpnService) }

// TODO We should avoid lifecycleScope.launch (current needed due to InetSocketAddress
// with intent from API)
lifecycleScope.launch(context = Dispatchers.IO) {
Expand Down Expand Up @@ -118,7 +110,7 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
intent.isFromSystem() || intent?.action == KEY_CONNECT_ACTION -> {
// Only show on foreground if we have permission
if (prepare(this) == null) {
_shouldBeOnForeground.update { true }
foregroundNotificationHandler.startForeground()
}
lifecycleScope.launch { connectionProxy.connectWithoutPermissionCheck() }
}
Expand All @@ -131,12 +123,12 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
}

override fun onBind(intent: Intent?): IBinder {
bindCount.incrementAndGet()
Logger.i("onBind: $intent")
val count = bindCount.incrementAndGet()
Logger.i("onBind: $intent, bindCount: $count")

if (intent.isFromSystem()) {
Logger.i("onBind from system")
_shouldBeOnForeground.update { true }
Logger.i("onBind was from system")
foregroundNotificationHandler.startForeground()
}

// We always need to return a binder. If the system binds to our VPN service, VpnService
Expand All @@ -145,6 +137,17 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
return super.onBind(intent) ?: emptyBinder()
}

override fun onRebind(intent: Intent?) {
super.onRebind(intent)
val count = bindCount.incrementAndGet()
Logger.i("onRebind: $intent, bindCount: $count")

if (intent.isFromSystem()) {
Logger.i("onRebind from system")
foregroundNotificationHandler.startForeground()
}
}

private fun startDaemon() {
val apiEndpointConfiguration =
if (Build.TYPE == BuildTypes.DEBUG) {
Expand Down Expand Up @@ -172,16 +175,18 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
}

override fun onRevoke() {
Logger.d("onRevoke")
runBlocking { connectionProxy.disconnect() }
}

override fun onUnbind(intent: Intent): Boolean {
val count = bindCount.decrementAndGet()
Logger.i("onUnbind: $intent, bindCount: $count")

// Foreground?
if (intent.isFromSystem()) {
Logger.i("onUnbind from system")
_shouldBeOnForeground.update { false }
foregroundNotificationHandler.stopForeground()
}

if (count == 0) {
Expand All @@ -203,7 +208,7 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
}
}
}
return false
return true
}

override fun onDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import android.content.pm.ServiceInfo
import android.net.VpnService
import android.os.Build
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.lib.model.Notification
import net.mullvad.mullvadvpn.lib.model.NotificationChannel
import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState
Expand All @@ -18,20 +16,15 @@ import net.mullvad.mullvadvpn.service.notifications.tunnelstate.toNotification
class ForegroundNotificationManager(
private val vpnService: MullvadVpnService,
private val tunnelStateNotificationProvider: TunnelStateNotificationProvider,
private val scope: CoroutineScope,
) {
suspend fun start(foregroundProvider: ShouldBeOnForegroundProvider) {
scope.launch {
foregroundProvider.shouldBeOnForeground.collect {
if (it) {
Logger.i("Starting foreground")
notifyForeground(getTunnelStateNotificationOrDefault())
} else {
Logger.i("Stopping foreground")
vpnService.stopForeground(Service.STOP_FOREGROUND_DETACH)
}
}
}
fun startForeground() {
Logger.d("startForeground")
notifyForeground(getTunnelStateNotificationOrDefault())
}

fun stopForeground() {
Logger.d("stopForeground")
vpnService.stopForeground(Service.STOP_FOREGROUND_DETACH)
}

private fun getTunnelStateNotificationOrDefault(): Notification.Tunnel {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.service.notifications

import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.Flow

interface ShouldBeOnForegroundProvider {
val shouldBeOnForeground: StateFlow<Boolean>
val shouldBeOnForeground: Flow<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ class TunnelStateNotificationProvider(
deviceRepository.deviceState
) { tunnelState: TunnelState, actionAfterDisconnect: ActionAfterDisconnect?, deviceState
->
if (deviceState is DeviceState.LoggedOut) {
if (
deviceState is DeviceState.LoggedOut && tunnelState is TunnelState.Disconnected
) {
return@combine NotificationUpdate.Cancel(notificationId)
}
val notificationTunnelState =
Expand Down
Loading