diff options
| author | Albin <albin@mullvad.net> | 2023-07-26 13:24:34 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-07-27 10:41:29 +0200 |
| commit | e65ec2a5a99cf1de9a99822b79c01ad90f0e2bc5 (patch) | |
| tree | 2418f16fcbdcb2a4c1fd35babf608d0f3e19c6ae /android/lib/ipc | |
| parent | 67d97bbf838d25151b1739960a74bb53d18c828c (diff) | |
| download | mullvadvpn-e65ec2a5a99cf1de9a99822b79c01ad90f0e2bc5.tar.xz mullvadvpn-e65ec2a5a99cf1de9a99822b79c01ad90f0e2bc5.zip | |
Move ipc classes to ipc module
Diffstat (limited to 'android/lib/ipc')
7 files changed, 297 insertions, 1 deletions
diff --git a/android/lib/ipc/build.gradle.kts b/android/lib/ipc/build.gradle.kts index c5f8eafa0c..d850780b1b 100644 --- a/android/lib/ipc/build.gradle.kts +++ b/android/lib/ipc/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "net.mullvad.mullvadvpn.ipc" + namespace = "net.mullvad.mullvadvpn.lib.ipc" compileSdk = Versions.Android.compileSdkVersion defaultConfig { @@ -30,6 +30,7 @@ android { } dependencies { + implementation(project(Dependencies.Mullvad.commonLib)) implementation(project(Dependencies.Mullvad.modelLib)) implementation(Dependencies.Kotlin.stdlib) diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt new file mode 100644 index 0000000000..7f6195c61e --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt @@ -0,0 +1,41 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.util.Log +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.withLock +import kotlin.reflect.KClass + +class DispatchingHandler<T : Any>(looper: Looper, private val extractor: (Message) -> T?) : + Handler(looper), MessageDispatcher<T> { + private val handlers = HashMap<KClass<out T>, (T) -> Unit>() + private val lock = ReentrantReadWriteLock() + + override fun <V : T> registerHandler(variant: KClass<V>, handler: (V) -> Unit) { + lock.writeLock().withLock { + handlers.put(variant) { instance -> @Suppress("UNCHECKED_CAST") handler(instance as V) } + } + } + + override fun handleMessage(message: Message) { + lock.readLock().withLock { + val instance = extractor(message) + + if (instance != null) { + val handler = handlers.get(instance::class) + + handler?.invoke(instance) + } else { + Log.e("mullvad", "Dispatching handler received an unexpected message") + } + } + } + + fun onDestroy() { + lock.writeLock().withLock { handlers.clear() } + + removeCallbacksAndMessages(null) + } +} diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt new file mode 100644 index 0000000000..9ce1ccc698 --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt @@ -0,0 +1,73 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import android.os.Messenger +import kotlinx.parcelize.Parcelize +import net.mullvad.mullvadvpn.model.AccountCreationResult +import net.mullvad.mullvadvpn.model.AccountExpiry +import net.mullvad.mullvadvpn.model.AccountHistory +import net.mullvad.mullvadvpn.model.AppVersionInfo +import net.mullvad.mullvadvpn.model.DeviceListEvent +import net.mullvad.mullvadvpn.model.DeviceState +import net.mullvad.mullvadvpn.model.GeoIpLocation +import net.mullvad.mullvadvpn.model.LoginResult +import net.mullvad.mullvadvpn.model.RelayList +import net.mullvad.mullvadvpn.model.RemoveDeviceResult +import net.mullvad.mullvadvpn.model.Settings +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.mullvadvpn.model.VoucherSubmissionResult + +// Events that can be sent from the service +sealed class Event : Message.EventMessage() { + protected override val messageKey = MESSAGE_KEY + + @Parcelize data class AccountCreationEvent(val result: AccountCreationResult) : Event() + + @Parcelize data class AccountExpiryEvent(val expiry: AccountExpiry) : Event() + + @Parcelize data class AccountHistoryEvent(val history: AccountHistory) : Event() + + @Parcelize + data class AppVersionInfo(val versionInfo: net.mullvad.mullvadvpn.model.AppVersionInfo?) : + Event() + + @Parcelize data class AuthToken(val token: String?) : Event() + + @Parcelize data class CurrentVersion(val version: String?) : Event() + + @Parcelize data class DeviceStateEvent(val newState: DeviceState) : Event() + + @Parcelize data class DeviceListUpdate(val event: DeviceListEvent) : Event() + + @Parcelize + data class DeviceRemovalEvent(val deviceId: String, val result: RemoveDeviceResult) : Event() + + @Parcelize data class ListenerReady(val connection: Messenger, val listenerId: Int) : Event() + + @Parcelize data class LoginEvent(val result: LoginResult) : Event() + + @Parcelize data class NewLocation(val location: GeoIpLocation?) : Event() + + @Parcelize data class NewRelayList(val relayList: RelayList?) : Event() + + @Parcelize data class SettingsUpdate(val settings: Settings?) : Event() + + @Parcelize data class SplitTunnelingUpdate(val excludedApps: List<String>?) : Event() + + @Parcelize data class TunnelStateChange(val tunnelState: TunnelState) : Event() + + @Parcelize + data class VoucherSubmissionResult( + val voucher: String, + val result: net.mullvad.mullvadvpn.model.VoucherSubmissionResult + ) : Event() + + @Parcelize object VpnPermissionRequest : Event() + + companion object { + private const val MESSAGE_KEY = "event" + + fun fromMessage(message: android.os.Message): Event? = fromMessage(message, MESSAGE_KEY) + } +} + +typealias EventDispatcher = MessageDispatcher<Event> diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt new file mode 100644 index 0000000000..4d60cf4d7b --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt @@ -0,0 +1,41 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.util.Log +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedSendChannelException +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.onCompletion + +class HandlerFlow<T>(looper: Looper, private val extractor: (Message) -> T) : + Handler(looper), Flow<T> { + private val channel = Channel<T>(Channel.UNLIMITED) + private val flow = channel.consumeAsFlow().onCompletion { removeCallbacksAndMessages(null) } + + @InternalCoroutinesApi + override suspend fun collect(collector: FlowCollector<T>) = flow.collect(collector) + + override fun handleMessage(message: Message) { + val extractedData = extractor(message) + + try { + channel.trySendBlocking(extractedData) + } catch (exception: Exception) { + when (exception) { + is ClosedSendChannelException, + is CancellationException -> { + Log.w("mullvad", "Received a message after HandlerFlow was closed", exception) + removeCallbacksAndMessages(null) + } + else -> throw exception + } + } + } +} diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt new file mode 100644 index 0000000000..dd5f9442e1 --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt @@ -0,0 +1,30 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import android.os.Bundle +import android.os.Message as RawMessage +import android.os.Parcelable + +sealed class Message(private val messageId: Int) : Parcelable { + abstract class EventMessage : Message(1) + abstract class RequestMessage : Message(2) + + protected abstract val messageKey: String + + val message: RawMessage + get() = + RawMessage.obtain().also { message -> + message.what = messageId + message.data = Bundle() + message.data.putParcelable(messageKey, this) + } + + companion object { + internal fun <T : Parcelable> fromMessage(message: RawMessage, key: String): T? { + val data = message.data + + data.classLoader = Message::class.java.classLoader + + return data.getParcelable(key) + } + } +} diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt new file mode 100644 index 0000000000..8bb6703479 --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt @@ -0,0 +1,7 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import kotlin.reflect.KClass + +interface MessageDispatcher<T : Any> { + fun <V : T> registerHandler(variant: KClass<V>, handler: (V) -> Unit) +} diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt new file mode 100644 index 0000000000..ad91632cbf --- /dev/null +++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt @@ -0,0 +1,103 @@ +package net.mullvad.mullvadvpn.lib.ipc + +import android.os.Message as RawMessage +import android.os.Messenger +import java.net.InetAddress +import kotlinx.parcelize.Parcelize +import net.mullvad.mullvadvpn.model.DnsOptions +import net.mullvad.mullvadvpn.model.GeographicLocationConstraint +import net.mullvad.mullvadvpn.model.ObfuscationSettings +import net.mullvad.mullvadvpn.model.QuantumResistantState +import net.mullvad.mullvadvpn.model.WireguardConstraints + +// Requests that the service can handle +sealed class Request : Message.RequestMessage() { + protected override val messageKey = MESSAGE_KEY + + @Parcelize + @Deprecated("Use SetDnsOptions") + data class AddCustomDnsServer(val address: InetAddress) : Request() + + @Parcelize object Connect : Request() + + @Parcelize object CreateAccount : Request() + + @Parcelize object Disconnect : Request() + + @Parcelize data class ExcludeApp(val packageName: String) : Request() + + @Parcelize object FetchAccountExpiry : Request() + + @Parcelize object FetchAccountHistory : Request() + + @Parcelize object FetchAuthToken : Request() + + @Parcelize data class IncludeApp(val packageName: String) : Request() + + @Parcelize data class Login(val account: String?) : Request() + + @Parcelize object RefreshDeviceState : Request() + + @Parcelize object GetDevice : Request() + + @Parcelize data class GetDeviceList(val accountToken: String) : Request() + + @Parcelize data class RemoveDevice(val accountToken: String, val deviceId: String) : Request() + + @Parcelize object Logout : Request() + + @Parcelize object PersistExcludedApps : Request() + + @Parcelize object Reconnect : Request() + + @Parcelize data class RegisterListener(val listener: Messenger) : Request() + + @Parcelize object ClearAccountHistory : Request() + + @Parcelize + @Deprecated("Use SetDnsOptions") + data class RemoveCustomDnsServer(val address: InetAddress) : Request() + + @Parcelize + @Deprecated("Use SetDnsOptions") + data class ReplaceCustomDnsServer(val oldAddress: InetAddress, val newAddress: InetAddress) : + Request() + + @Parcelize data class SetAllowLan(val allow: Boolean) : Request() + + @Parcelize data class SetAutoConnect(val autoConnect: Boolean) : Request() + + @Parcelize + @Deprecated("Use SetDnsOptions") + data class SetEnableCustomDns(val enable: Boolean) : Request() + + @Parcelize data class SetEnableSplitTunneling(val enable: Boolean) : Request() + + @Parcelize + data class SetRelayLocation(val relayLocation: GeographicLocationConstraint?) : Request() + + @Parcelize data class SetWireGuardMtu(val mtu: Int?) : Request() + + @Parcelize data class SubmitVoucher(val voucher: String) : Request() + + @Parcelize data class UnregisterListener(val listenerId: Int) : Request() + + @Parcelize data class VpnPermissionResponse(val isGranted: Boolean) : Request() + + @Parcelize data class SetDnsOptions(val dnsOptions: DnsOptions) : Request() + + @Parcelize data class SetObfuscationSettings(val settings: ObfuscationSettings?) : Request() + + @Parcelize + data class SetWireguardConstraints(val wireguardConstraints: WireguardConstraints?) : Request() + + @Parcelize + data class SetWireGuardQuantumResistant(val quantumResistant: QuantumResistantState) : + Request() + + companion object { + private const val MESSAGE_KEY = "request" + + fun fromMessage(message: RawMessage): Request? = fromMessage(message, MESSAGE_KEY) + } +} |
