diff options
| author | Albin <albin@mullvad.net> | 2023-07-26 11:49:22 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-07-27 10:41:29 +0200 |
| commit | d4e7a6f0b63027d752e19a55d6df9dcf4a7095ff (patch) | |
| tree | 797dc237e0b5dc51f8b7135cc95e7472d16ab085 /android/app | |
| parent | e320571c388aacc74e7c9e73a54374ec189c4792 (diff) | |
| download | mullvadvpn-d4e7a6f0b63027d752e19a55d6df9dcf4a7095ff.tar.xz mullvadvpn-d4e7a6f0b63027d752e19a55d6df9dcf4a7095ff.zip | |
Move tile classes to tile module
Diffstat (limited to 'android/app')
3 files changed, 1 insertions, 276 deletions
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4671de2d88..7eede7ed33 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -73,7 +73,7 @@ Tile services must be exported and protected by the bind tile permission (android.permission.BIND_QUICK_SETTINGS_TILE). --> - <service android:name="net.mullvad.mullvadvpn.service.MullvadTileService" + <service android:name="net.mullvad.mullvadvpn.tile.MullvadTileService" android:exported="true" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:label="@string/toggle_vpn" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/ServiceConnection.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/ServiceConnection.kt deleted file mode 100644 index 17682911e3..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/ServiceConnection.kt +++ /dev/null @@ -1,132 +0,0 @@ -package net.mullvad.mullvadvpn.ipc - -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.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 -import net.mullvad.mullvadvpn.service.MullvadVpnService - -@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(context, MullvadVpnService::class.java) - - 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) -} 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 deleted file mode 100644 index 021c20bc05..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt +++ /dev/null @@ -1,143 +0,0 @@ -package net.mullvad.mullvadvpn.service - -import android.content.Intent -import android.graphics.drawable.Icon -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.MainScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -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.lib.common.util.SdkUtils.setSubtitleIfSupported -import net.mullvad.mullvadvpn.model.ServiceResult -import net.mullvad.mullvadvpn.model.TunnelState -import net.mullvad.talpid.tunnel.ActionAfterDisconnect - -class MullvadTileService : TileService() { - private var scope: CoroutineScope? = null - - private lateinit var securedIcon: Icon - private lateinit var unsecuredIcon: Icon - - override fun onCreate() { - securedIcon = Icon.createWithResource(this, R.drawable.small_logo_white) - unsecuredIcon = Icon.createWithResource(this, R.drawable.small_logo_black) - } - - 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 - } - - unlockAndRun { - runBlocking { - val isUnlockStatusPropagated = - isUnlockStatusPropagatedWithinTimeout( - unlockTimeoutMillis = 1000L, - unlockCheckDelayMillis = 100L - ) - - if (isUnlockStatusPropagated) { - toggleTunnel() - } else { - Log.e("mullvad", "Unable to toggle tunnel state") - } - } - } - } - - override fun onStartListening() { - scope = MainScope().apply { launchListenToTunnelState() } - } - - override fun onStopListening() { - scope?.cancel() - } - - 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 - } - } - - // Always start as foreground in case tile is out-of-sync. - startForegroundService(intent) - } - - @OptIn(FlowPreview::class) - private fun CoroutineScope.launchListenToTunnelState() = launch { - ServiceConnection(this@MullvadTileService, this) - .tunnelState - .debounce(300L) - .map { (tunnelState, connectionState) -> mapToTileState(tunnelState, connectionState) } - .collect { updateTileState(it) } - } - - private fun mapToTileState( - tunnelState: TunnelState, - connectionState: ServiceResult.ConnectionState - ): Int { - return if (connectionState == ServiceResult.ConnectionState.CONNECTED) { - when (tunnelState) { - is TunnelState.Disconnected -> Tile.STATE_INACTIVE - is TunnelState.Connecting -> Tile.STATE_ACTIVE - is TunnelState.Connected -> Tile.STATE_ACTIVE - is TunnelState.Disconnecting -> { - 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 - } - } - } - } else { - Tile.STATE_INACTIVE - } - } - - private fun updateTileState(newState: Int) { - qsTile?.apply { - if (newState == Tile.STATE_ACTIVE) { - state = Tile.STATE_ACTIVE - icon = securedIcon - setSubtitleIfSupported(resources.getText(R.string.secured)) - } else { - state = Tile.STATE_INACTIVE - icon = unsecuredIcon - setSubtitleIfSupported(resources.getText(R.string.unsecured)) - } - updateTile() - } - } -} |
