diff options
| author | David Göransson <david.goransson@mullvad.net> | 2024-05-29 17:18:29 +0200 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2024-05-29 17:18:29 +0200 |
| commit | ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch) | |
| tree | 9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/tile/src | |
| parent | 8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff) | |
| parent | 767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff) | |
| download | mullvadvpn-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.kt | 48 | ||||
| -rw-r--r-- | android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt | 132 |
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) -} |
