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 index c5ede203275b..e1079807f169 100644 --- 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 @@ -5,16 +5,16 @@ 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.PlayPurchaseInitResult +import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult 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() { @@ -61,6 +61,11 @@ sealed class Event : Message.EventMessage() { val result: net.mullvad.mullvadvpn.model.VoucherSubmissionResult ) : Event() + @Parcelize data class PlayPurchaseInitResultEvent(val result: PlayPurchaseInitResult) : Event() + + @Parcelize + data class PlayPurchaseVerifyResultEvent(val result: PlayPurchaseVerifyResult) : Event() + @Parcelize object VpnPermissionRequest : Event() companion object { 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 index 2a8636fa95bf..3d037b2d465e 100644 --- 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 @@ -7,6 +7,7 @@ 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.PlayPurchase import net.mullvad.mullvadvpn.model.QuantumResistantState import net.mullvad.mullvadvpn.model.WireguardConstraints @@ -80,6 +81,10 @@ sealed class Request : Message.RequestMessage() { @Parcelize data class SubmitVoucher(val voucher: String) : Request() + @Parcelize data object InitPlayPurchase : Request() + + @Parcelize data class VerifyPlayPurchase(val playPurchase: PlayPurchase) : Request() + @Parcelize data class UnregisterListener(val listenerId: Int) : Request() @Parcelize data class VpnPermissionResponse(val isGranted: Boolean) : Request() diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/PlayPurchaseHandler.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/PlayPurchaseHandler.kt new file mode 100644 index 000000000000..464bf2953265 --- /dev/null +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/PlayPurchaseHandler.kt @@ -0,0 +1,64 @@ +package net.mullvad.mullvadvpn.service.endpoint + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.channels.actor +import kotlinx.coroutines.channels.trySendBlocking +import net.mullvad.mullvadvpn.lib.ipc.Event +import net.mullvad.mullvadvpn.lib.ipc.Request +import net.mullvad.mullvadvpn.model.PlayPurchase + +// WIP - adapted copy of VoucherRedeemer +class PlayPurchaseHandler(private val endpoint: ServiceEndpoint) { + private val daemon + get() = endpoint.intermittentDaemon + + private val playPurchaseChannel = spawnActor() + + init { + endpoint.dispatcher.registerHandler(Request.InitPlayPurchase::class) { + playPurchaseChannel.trySendBlocking(Command.InitPlayPurchase) + } + endpoint.dispatcher.registerHandler(Request.VerifyPlayPurchase::class) { request -> + playPurchaseChannel.trySendBlocking(Command.VerifyPlayPurchase(request.playPurchase)) + } + } + + fun onDestroy() { + playPurchaseChannel.close() + } + + private fun spawnActor() = + GlobalScope.actor(Dispatchers.Default, Channel.UNLIMITED) { + try { + for (command in channel) { + when (command) { + is Command.InitPlayPurchase -> initializePurchase() + is Command.VerifyPlayPurchase -> verifyPlayPurchase(command.playPurchase) + } + } + } catch (exception: ClosedReceiveChannelException) { + // Channel was closed, stop the actor + } + } + + private suspend fun initializePurchase() { + val result = daemon.await().initPlayPurchase() + endpoint.sendEvent(Event.PlayPurchaseInitResultEvent(result)) + } + + private suspend fun verifyPlayPurchase(playPurchase: PlayPurchase) { + val result = daemon.await().verifyPlayPurchase(playPurchase) + endpoint.sendEvent(Event.PlayPurchaseVerifyResultEvent(result)) + } + + companion object { + private sealed class Command { + data object InitPlayPurchase : Command() + + data class VerifyPlayPurchase(val playPurchase: PlayPurchase) : Command() + } + } +} diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt index fe7ddb75e9cc..67614b7b1c86 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt @@ -51,6 +51,8 @@ class ServiceEndpoint( val splitTunneling = SplitTunneling(SplitTunnelingPersistence(context), this) val voucherRedeemer = VoucherRedeemer(this) + private val playPurchaseHandler = PlayPurchaseHandler(this) + private val deviceRepositoryBackend = DaemonDeviceDataSource(this) init { @@ -80,6 +82,7 @@ class ServiceEndpoint( settingsListener.onDestroy() splitTunneling.onDestroy() voucherRedeemer.onDestroy() + playPurchaseHandler.onDestroy() } internal fun sendEvent(event: Event) {