summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/build.gradle3
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/DispatchingHandler.kt48
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt20
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Message.kt25
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt21
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt9
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt2
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt84
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt12
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceConnection.kt30
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt60
15 files changed, 285 insertions, 33 deletions
diff --git a/android/build.gradle b/android/build.gradle
index 60b831d41c..6271a20bef 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'com.github.triplet.play'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-parcelize'
def repoRootPath = projectDir.absoluteFile.parentFile.absolutePath
def extraAssetsDirectory = "$project.buildDir/extraAssets"
@@ -120,7 +121,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.github.triplet.gradle:play-publisher:2.7.5'
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20'
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/DispatchingHandler.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/DispatchingHandler.kt
new file mode 100644
index 0000000000..0f3820ff48
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/DispatchingHandler.kt
@@ -0,0 +1,48 @@
+package net.mullvad.mullvadvpn.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) {
+ private val handlers = HashMap<KClass<out T>, (T) -> Unit>()
+ private val lock = ReentrantReadWriteLock()
+
+ 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/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt
new file mode 100644
index 0000000000..fa6aa22081
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt
@@ -0,0 +1,20 @@
+package net.mullvad.mullvadvpn.ipc
+
+import android.os.Message as RawMessage
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+// Events that can be sent from the service
+sealed class Event : Message(), Parcelable {
+ protected override val messageId = 1
+ protected override val messageKey = MESSAGE_KEY
+
+ @Parcelize
+ object ListenerReady : Event(), Parcelable
+
+ companion object {
+ private const val MESSAGE_KEY = "event"
+
+ fun fromMessage(message: RawMessage): Event? = Message.fromMessage(message, MESSAGE_KEY)
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Message.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Message.kt
new file mode 100644
index 0000000000..872acba8e7
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Message.kt
@@ -0,0 +1,25 @@
+package net.mullvad.mullvadvpn.ipc
+
+import android.os.Bundle
+import android.os.Message as RawMessage
+import android.os.Parcelable
+
+abstract class Message : Parcelable {
+ protected abstract val messageId: Int
+ 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
+
+ return data.getParcelable(key)
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt
new file mode 100644
index 0000000000..47b613816b
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.ipc
+
+import android.os.Message as RawMessage
+import android.os.Messenger
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+// Requests that the service can handle
+sealed class Request : Message(), Parcelable {
+ protected override val messageId = 2
+ protected override val messageKey = MESSAGE_KEY
+
+ @Parcelize
+ class RegisterListener(val listener: Messenger) : Request(), Parcelable
+
+ companion object {
+ private const val MESSAGE_KEY = "request"
+
+ fun fromMessage(message: RawMessage): Request? = Message.fromMessage(message, MESSAGE_KEY)
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index 052f65ebd3..0e43e7aa64 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -6,6 +6,7 @@ import android.content.Intent
import android.net.VpnService
import android.os.Binder
import android.os.IBinder
+import android.os.Looper
import android.util.Log
import kotlin.properties.Delegates.observable
import kotlinx.coroutines.CompletableDeferred
@@ -14,6 +15,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.model.Settings
+import net.mullvad.mullvadvpn.service.endpoint.ServiceEndpoint
import net.mullvad.mullvadvpn.service.notifications.AccountExpiryNotification
import net.mullvad.mullvadvpn.service.tunnelstate.TunnelStateUpdater
import net.mullvad.mullvadvpn.ui.MainActivity
@@ -69,6 +71,7 @@ class MullvadVpnService : TalpidVpnService() {
}
private lateinit var daemonInstance: DaemonInstance
+ private lateinit var endpoint: ServiceEndpoint
private lateinit var keyguardManager: KeyguardManager
private lateinit var notificationManager: ForegroundNotificationManager
private lateinit var tunnelStateUpdater: TunnelStateUpdater
@@ -98,13 +101,16 @@ class MullvadVpnService : TalpidVpnService() {
initializeSplitTunneling()
+ daemonInstance = DaemonInstance(this)
keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
notificationManager = ForegroundNotificationManager(this, serviceNotifier, keyguardManager)
tunnelStateUpdater = TunnelStateUpdater(this, serviceNotifier)
+ endpoint = ServiceEndpoint(Looper.getMainLooper(), daemonInstance.intermittentDaemon)
+
notificationManager.acknowledgeStartForegroundService()
- daemonInstance = DaemonInstance(this).apply {
+ daemonInstance.apply {
intermittentDaemon.registerListener(this@MullvadVpnService) { daemon ->
handleDaemonInstance(daemon)
}
@@ -241,6 +247,7 @@ class MullvadVpnService : TalpidVpnService() {
if (state == State.Running) {
instance = ServiceInstance(
+ endpoint.messenger,
daemon,
connectionProxy,
connectivityListener,
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt
index 135ea9ef28..19d4443162 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt
@@ -1,8 +1,10 @@
package net.mullvad.mullvadvpn.service
+import android.os.Messenger
import net.mullvad.talpid.ConnectivityListener
class ServiceInstance(
+ val messenger: Messenger,
val daemon: MullvadDaemon,
val connectionProxy: ConnectionProxy,
val connectivityListener: ConnectivityListener,
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
new file mode 100644
index 0000000000..6541ef2ba7
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
@@ -0,0 +1,84 @@
+package net.mullvad.mullvadvpn.service.endpoint
+
+import android.os.DeadObjectException
+import android.os.Looper
+import android.os.Messenger
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.actor
+import kotlinx.coroutines.channels.sendBlocking
+import net.mullvad.mullvadvpn.ipc.DispatchingHandler
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.Request
+import net.mullvad.mullvadvpn.service.MullvadDaemon
+import net.mullvad.mullvadvpn.util.Intermittent
+
+class ServiceEndpoint(looper: Looper, private val intermittentDaemon: Intermittent<MullvadDaemon>) {
+ private val listeners = mutableSetOf<Messenger>()
+ private val registrationQueue: SendChannel<Messenger> = startRegistrator()
+
+ internal val dispatcher = DispatchingHandler(looper) { message ->
+ Request.fromMessage(message)
+ }
+
+ val messenger = Messenger(dispatcher)
+
+ init {
+ dispatcher.registerHandler(Request.RegisterListener::class) { request ->
+ registrationQueue.sendBlocking(request.listener)
+ }
+ }
+
+ fun onDestroy() {
+ dispatcher.onDestroy()
+ registrationQueue.close()
+ }
+
+ internal fun sendEvent(event: Event) {
+ synchronized(this) {
+ val deadListeners = mutableSetOf<Messenger>()
+
+ for (listener in listeners) {
+ try {
+ listener.send(event.message)
+ } catch (_: DeadObjectException) {
+ deadListeners.add(listener)
+ }
+ }
+
+ deadListeners.forEach { listeners.remove(it) }
+ }
+ }
+
+ private fun startRegistrator() = GlobalScope.actor<Messenger>(
+ Dispatchers.Default,
+ Channel.UNLIMITED
+ ) {
+ try {
+ while (true) {
+ val listener = channel.receive()
+
+ intermittentDaemon.await()
+
+ registerListener(listener)
+ }
+ } catch (exception: ClosedReceiveChannelException) {
+ // Registration queue closed; stop registrator
+ }
+ }
+
+ private fun registerListener(listener: Messenger) {
+ synchronized(this) {
+ listeners.add(listener)
+
+ val initialEvents = listOf(Event.ListenerReady)
+
+ initialEvents.forEach { event ->
+ listener.send(event.message)
+ }
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
index 795a3bddb7..ac4470520f 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.CompletableDeferred
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
class LaunchFragment : ServiceAwareFragment() {
private val hasAccountToken = CompletableDeferred<Boolean>()
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index 5929490065..2d597929dd 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -19,7 +19,9 @@ import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
+import net.mullvad.mullvadvpn.ipc.Event
import net.mullvad.mullvadvpn.service.MullvadVpnService
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
import net.mullvad.talpid.util.EventNotifier
class MainActivity : FragmentActivity() {
@@ -60,7 +62,14 @@ class MainActivity : FragmentActivity() {
}
serviceConnection = newConnection
- serviceNotifier.notify(newConnection)
+
+ if (newConnection != null) {
+ newConnection.dispatcher.registerHandler(Event.ListenerReady::class) { _ ->
+ serviceNotifier.notify(newConnection)
+ }
+ } else {
+ serviceNotifier.notify(null)
+ }
if (shouldConnect) {
tryToConnect()
@@ -71,6 +80,7 @@ class MainActivity : FragmentActivity() {
override fun onServiceDisconnected(className: ComponentName) {
android.util.Log.d("mullvad", "UI lost the connection to the service")
service?.serviceNotifier?.unsubscribe(this@MainActivity)
+ serviceConnection?.onDestroy()
service = null
serviceConnection = null
serviceNotifier.notify(null)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
index a1003f511d..5788c60ad8 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.ui
import android.content.Context
import net.mullvad.mullvadvpn.ui.fragments.BaseFragment
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
import net.mullvad.mullvadvpn.util.JobTracker
abstract class ServiceAwareFragment : BaseFragment() {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceConnection.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceConnection.kt
deleted file mode 100644
index 43a0e24dc8..0000000000
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceConnection.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package net.mullvad.mullvadvpn.ui
-
-import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
-import net.mullvad.mullvadvpn.dataproxy.RelayListListener
-import net.mullvad.mullvadvpn.service.ServiceInstance
-
-class ServiceConnection(private val service: ServiceInstance, val mainActivity: MainActivity) {
- val daemon = service.daemon
- val accountCache = service.accountCache
- val connectionProxy = service.connectionProxy
- val customDns = service.customDns
- val keyStatusListener = service.keyStatusListener
- val locationInfoCache = service.locationInfoCache
- val settingsListener = service.settingsListener
- val splitTunneling = service.splitTunneling
-
- val appVersionInfoCache = AppVersionInfoCache(mainActivity, daemon, settingsListener)
- var relayListListener = RelayListListener(daemon, settingsListener)
-
- init {
- appVersionInfoCache.onCreate()
- connectionProxy.mainActivity = mainActivity
- }
-
- fun onDestroy() {
- appVersionInfoCache.onDestroy()
- relayListListener.onDestroy()
- connectionProxy.mainActivity = null
- }
-}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
index e523ba481b..30c0a6db30 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
@@ -15,6 +15,7 @@ import net.mullvad.mullvadvpn.service.LocationInfoCache
import net.mullvad.mullvadvpn.service.MullvadDaemon
import net.mullvad.mullvadvpn.service.SettingsListener
import net.mullvad.mullvadvpn.service.SplitTunneling
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
abstract class ServiceDependentFragment(val onNoService: OnNoService) : ServiceAwareFragment() {
enum class OnNoService {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
index 6725430052..4f823fada2 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
@@ -8,6 +8,7 @@ import android.widget.ImageButton
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
import net.mullvad.mullvadvpn.service.AccountCache
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
import net.mullvad.mullvadvpn.ui.widget.AccountCell
import net.mullvad.mullvadvpn.ui.widget.AppVersionCell
import net.mullvad.mullvadvpn.ui.widget.NavigateCell
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt
new file mode 100644
index 0000000000..d9071ca2b9
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt
@@ -0,0 +1,60 @@
+package net.mullvad.mullvadvpn.ui.serviceconnection
+
+import android.os.Looper
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
+import net.mullvad.mullvadvpn.dataproxy.RelayListListener
+import net.mullvad.mullvadvpn.ipc.DispatchingHandler
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.Request
+import net.mullvad.mullvadvpn.service.ServiceInstance
+import net.mullvad.mullvadvpn.ui.MainActivity
+
+// Container of classes that communicate with the service through an active connection
+//
+// The properties of this class can be used to send events to the service, to listen for events from
+// the service and to get values received from events.
+class ServiceConnection(private val service: ServiceInstance, val mainActivity: MainActivity) {
+ val dispatcher = DispatchingHandler(Looper.getMainLooper()) { message ->
+ Event.fromMessage(message)
+ }
+
+ val daemon = service.daemon
+ val accountCache = service.accountCache
+ val connectionProxy = service.connectionProxy
+ val customDns = service.customDns
+ val keyStatusListener = service.keyStatusListener
+ val locationInfoCache = service.locationInfoCache
+ val settingsListener = service.settingsListener
+ val splitTunneling = service.splitTunneling
+
+ val appVersionInfoCache = AppVersionInfoCache(mainActivity, daemon, settingsListener)
+ var relayListListener = RelayListListener(daemon, settingsListener)
+
+ init {
+ appVersionInfoCache.onCreate()
+ connectionProxy.mainActivity = mainActivity
+ registerListener()
+ }
+
+ fun onDestroy() {
+ dispatcher.onDestroy()
+
+ appVersionInfoCache.onDestroy()
+ relayListListener.onDestroy()
+ connectionProxy.mainActivity = null
+ }
+
+ private fun registerListener() {
+ val listener = Messenger(dispatcher)
+ val request = Request.RegisterListener(listener)
+
+ try {
+ service.messenger.send(request.message)
+ } catch (exception: RemoteException) {
+ Log.e("mullvad", "Failed to register listener for service events", exception)
+ }
+ }
+}