summaryrefslogtreecommitdiffhomepage
path: root/android/tile/src
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
commitad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch)
tree9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/tile/src
parent8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff)
parent767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff)
downloadmullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.tar.xz
mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.zip
Merge branch 'android-grpc'
Diffstat (limited to 'android/tile/src')
-rw-r--r--android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt48
-rw-r--r--android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt132
2 files changed, 32 insertions, 148 deletions
diff --git a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
index ae80bedef8..f796690f18 100644
--- a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
+++ b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/MullvadTileService.kt
@@ -9,32 +9,40 @@ import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
import net.mullvad.mullvadvpn.lib.common.constant.KEY_DISCONNECT_ACTION
import net.mullvad.mullvadvpn.lib.common.constant.MAIN_ACTIVITY_CLASS
+import net.mullvad.mullvadvpn.lib.common.constant.TAG
import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.setSubtitleIfSupported
-import net.mullvad.mullvadvpn.model.ServiceResult
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
+import org.koin.android.ext.android.get
class MullvadTileService : TileService() {
- private var scope: CoroutineScope? = null
+ private var job: Job? = null
private lateinit var securedIcon: Icon
private lateinit var unsecuredIcon: Icon
+ private val connectionProxy = get<ConnectionProxy>()
+ private val managementService = get<ManagementService>()
+
override fun onCreate() {
securedIcon = Icon.createWithResource(this, R.drawable.small_logo_white)
unsecuredIcon = Icon.createWithResource(this, R.drawable.small_logo_black)
@@ -72,11 +80,11 @@ class MullvadTileService : TileService() {
}
override fun onStartListening() {
- scope = MainScope().apply { launchListenToTunnelState() }
+ job = MainScope().launch { launchListenToTunnelState() }
}
override fun onStopListening() {
- scope?.cancel()
+ job?.cancel()
}
@SuppressLint("StartActivityAndCollapseDeprecated")
@@ -84,7 +92,7 @@ class MullvadTileService : TileService() {
val isSetup = VpnService.prepare(applicationContext) == null
// TODO This logic should be more advanced, we should ensure user has an account setup etc.
if (!isSetup) {
- Log.d("MullvadTileService", "VPN service not setup, starting main activity")
+ Log.d(TAG, "TileService: VPN service not setup, starting main activity")
val intent =
Intent().apply {
@@ -98,7 +106,7 @@ class MullvadTileService : TileService() {
startActivityAndCollapseCompat(intent)
return
} else {
- Log.d("MullvadTileService", "VPN service is setup")
+ Log.d(TAG, "TileService: VPN service is setup")
}
val intent =
Intent().apply {
@@ -132,19 +140,23 @@ class MullvadTileService : TileService() {
}
@OptIn(FlowPreview::class)
- private fun CoroutineScope.launchListenToTunnelState() = launch {
- ServiceConnection(this@MullvadTileService, this)
- .tunnelState
- .debounce(300L)
+ private suspend fun launchListenToTunnelState() {
+ combine(
+ connectionProxy.tunnelState.onStart { emit(TunnelState.Disconnected(null)) },
+ managementService.connectionState
+ ) { tunnelState, connectionState ->
+ tunnelState to connectionState
+ }
+ .debounce(TUNNEL_STATE_DEBOUNCE_MS)
.map { (tunnelState, connectionState) -> mapToTileState(tunnelState, connectionState) }
.collect { updateTileState(it) }
}
private fun mapToTileState(
tunnelState: TunnelState,
- connectionState: ServiceResult.ConnectionState
+ connectionState: GrpcConnectivityState
): Int {
- return if (connectionState == ServiceResult.ConnectionState.CONNECTED) {
+ return if (connectionState == GrpcConnectivityState.Ready) {
when (tunnelState) {
is TunnelState.Disconnected -> Tile.STATE_INACTIVE
is TunnelState.Connecting -> Tile.STATE_ACTIVE
@@ -183,4 +195,8 @@ class MullvadTileService : TileService() {
updateTile()
}
}
+
+ companion object {
+ private const val TUNNEL_STATE_DEBOUNCE_MS = 300L
+ }
}
diff --git a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt
deleted file mode 100644
index 93218c66dc..0000000000
--- a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package net.mullvad.mullvadvpn.tile
-
-import android.content.Context
-import android.content.Intent
-import android.os.IBinder
-import android.os.Looper
-import android.os.Messenger
-import kotlin.reflect.KClass
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.consumeAsFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS
-import net.mullvad.mullvadvpn.lib.common.util.DispatchingFlow
-import net.mullvad.mullvadvpn.lib.common.util.bindServiceFlow
-import net.mullvad.mullvadvpn.lib.common.util.dispatchTo
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.HandlerFlow
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.model.ServiceResult
-import net.mullvad.mullvadvpn.model.TunnelState
-
-@FlowPreview
-class ServiceConnection(context: Context, scope: CoroutineScope) {
- private val activeListeners = MutableStateFlow<Pair<Messenger, Int>?>(null)
- private val handler = HandlerFlow(Looper.getMainLooper(), Event.Companion::fromMessage)
- private val listener = Messenger(handler)
- private val listenerId = MutableStateFlow<Int?>(null)
-
- private lateinit var listenerRegistrations: StateFlow<Pair<Messenger, Int>?>
-
- lateinit var tunnelState: Flow<Pair<TunnelState, ServiceResult.ConnectionState>>
- private set
-
- private val serviceConnectionStateChannel =
- Channel<ServiceResult.ConnectionState>(Channel.RENDEZVOUS)
-
- init {
- val dispatcher =
- handler.filterNotNull().dispatchTo {
- listenerRegistrations =
- subscribeToState(Event.ListenerReady::class, scope) {
- Pair(connection, listenerId)
- }
-
- val tunnelStateEvents =
- subscribeToState(
- Event.TunnelStateChange::class,
- scope,
- TunnelState.Disconnected()
- ) {
- tunnelState
- }
-
- tunnelState =
- tunnelStateEvents.combine(serviceConnectionStateChannel.consumeAsFlow()) {
- tunnelState,
- serviceConnectionState ->
- tunnelState to serviceConnectionState
- }
- }
-
- scope.launch { connect(context) }
- scope.launch { dispatcher.collect() }
- scope.launch { unregisterOldListeners() }
- scope.launch { listenerRegistrations.collect { activeListeners.value = it } }
- }
-
- private suspend fun connect(context: Context) {
- val intent = Intent().apply { setClassName(context.packageName, VPN_SERVICE_CLASS) }
-
- context
- .bindServiceFlow(intent)
- .onStart { emit(ServiceResult.NOT_CONNECTED) }
- .onEach { result -> serviceConnectionStateChannel.send(result.connectionState) }
- .collect { result ->
- activeListeners.value = null
- result.binder?.let(::registerListener)
- }
- }
-
- private fun registerListener(binder: IBinder) {
- val request = Request.RegisterListener(listener)
- val messenger = Messenger(binder)
-
- messenger.send(request.message)
- }
-
- private suspend fun unregisterOldListeners() {
- var oldListener: Pair<Messenger, Int>? = null
-
- activeListeners
- .onCompletion { oldListener?.let(::unregisterListener) }
- .collect { newListener ->
- oldListener?.let(::unregisterListener)
- oldListener = newListener
- }
- }
-
- private fun unregisterListener(registration: Pair<Messenger, Int>) {
- val (messenger, listenerId) = registration
- val request = Request.UnregisterListener(listenerId)
-
- messenger.send(request.message)
- }
-
- private fun <V : Any, D> DispatchingFlow<in V>.subscribeToState(
- event: KClass<V>,
- scope: CoroutineScope,
- dataExtractor: suspend V.() -> D
- ) = subscribe(event).map(dataExtractor).stateIn(scope, SharingStarted.Lazily, null)
-
- private fun <V : Any, D> DispatchingFlow<in V>.subscribeToState(
- event: KClass<V>,
- scope: CoroutineScope,
- initialValue: D,
- dataExtractor: suspend V.() -> D
- ) = subscribe(event).map(dataExtractor).stateIn(scope, SharingStarted.Lazily, initialValue)
-}