summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt23
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt40
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt92
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt81
6 files changed, 238 insertions, 14 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
index 4868f80fd2..4230aa14f1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
@@ -1,7 +1,6 @@
package net.mullvad.mullvadvpn.service.endpoint
import android.content.Context
-import android.os.DeadObjectException
import android.os.Looper
import android.os.Messenger
import kotlinx.coroutines.Dispatchers
@@ -17,8 +16,11 @@ import net.mullvad.mullvadvpn.ipc.Request
import net.mullvad.mullvadvpn.service.MullvadDaemon
import net.mullvad.mullvadvpn.service.persistence.SplitTunnelingPersistence
import net.mullvad.mullvadvpn.util.Intermittent
+import net.mullvad.mullvadvpn.util.trySendEvent
import net.mullvad.talpid.ConnectivityListener
+const val SHOULD_LOG_DEAD_OBJECT_EXCEPTION = true
+
class ServiceEndpoint(
looper: Looper,
internal val intermittentDaemon: Intermittent<MullvadDaemon>,
@@ -93,13 +95,14 @@ class ServiceEndpoint(
val deadListeners = mutableSetOf<Int>()
for ((id, listener) in listeners) {
- try {
- listener.send(event.message)
- } catch (_: DeadObjectException) {
+ if (!listener.trySendEvent(
+ event,
+ SHOULD_LOG_DEAD_OBJECT_EXCEPTION
+ )
+ ) {
deadListeners.add(id)
}
}
-
deadListeners.forEach { listeners.remove(it) }
}
}
@@ -147,8 +150,14 @@ class ServiceEndpoint(
initialEvents.add(Event.VpnPermissionRequest)
}
- initialEvents.forEach { event ->
- listener.send(event.message)
+ val didSuccessfullySendAllMessages = initialEvents.all { event ->
+ listener.trySendEvent(
+ event,
+ SHOULD_LOG_DEAD_OBJECT_EXCEPTION
+ )
+ }
+ if (didSuccessfullySendAllMessages.not()) {
+ listeners.remove(listenerId)
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
index 5b4b88ad94..fcd8565550 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
@@ -10,6 +10,7 @@ import net.mullvad.mullvadvpn.ipc.Event
import net.mullvad.mullvadvpn.ipc.EventDispatcher
import net.mullvad.mullvadvpn.ipc.Request
import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.util.trySendRequest
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.util.EventNotifier
@@ -34,19 +35,19 @@ class ConnectionProxy(private val connection: Messenger, eventDispatcher: EventD
fun connect() {
if (anticipateConnectingState()) {
- connection.send(Request.Connect.message)
+ connection.trySendRequest(Request.Connect, true)
}
}
fun disconnect() {
if (anticipateReconnectingState()) {
- connection.send(Request.Disconnect.message)
+ connection.trySendRequest(Request.Disconnect, true)
}
}
fun reconnect() {
if (anticipateDisconnectingState()) {
- connection.send(Request.Reconnect.message)
+ connection.trySendRequest(Request.Reconnect, true)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt
index 6e445a6d34..56e7ee4d28 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.callbackFlow
import net.mullvad.mullvadvpn.ipc.Event
import net.mullvad.mullvadvpn.ipc.EventDispatcher
import net.mullvad.mullvadvpn.ipc.Request
+import net.mullvad.mullvadvpn.util.trySendRequest
class ServiceConnectionDeviceDataSource(
private val connection: Messenger,
@@ -43,18 +44,18 @@ class ServiceConnectionDeviceDataSource(
// Async result: Event.DeviceChanged
fun refreshDevice() {
- connection.send(Request.RefreshDeviceState.message)
+ connection.trySendRequest(Request.RefreshDeviceState, true)
}
fun getDevice() {
- connection.send(Request.GetDevice.message)
+ connection.trySendRequest(Request.GetDevice, true)
}
fun removeDevice(accountToken: String, deviceId: String) {
- connection.send(Request.RemoveDevice(accountToken, deviceId).message)
+ connection.trySendRequest(Request.RemoveDevice(accountToken, deviceId), true)
}
fun refreshDeviceList(accountToken: String) {
- connection.send(Request.GetDeviceList(accountToken).message)
+ connection.trySendRequest(Request.GetDeviceList(accountToken), true)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt
new file mode 100644
index 0000000000..71ce51ad79
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt
@@ -0,0 +1,40 @@
+package net.mullvad.mullvadvpn.util
+
+import android.os.DeadObjectException
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.Request
+
+fun Messenger.trySendEvent(event: Event, logErrors: Boolean): Boolean {
+ return trySend(event.message, logErrors, event::class.qualifiedName)
+}
+
+fun Messenger.trySendRequest(request: Request, logErrors: Boolean): Boolean {
+ return trySend(request.message, logErrors, request::class.qualifiedName)
+}
+
+private fun Messenger.trySend(message: Message, logErrors: Boolean, messageName: String?): Boolean {
+ return try {
+ this.send(message)
+ true
+ } catch (deadObjectException: DeadObjectException) {
+ if (logErrors) {
+ Log.e(
+ "mullvad",
+ "Failed to send message ${messageName ?: "<missing>"} due to DeadObjectException"
+ )
+ }
+ false
+ } catch (remoteException: RemoteException) {
+ if (logErrors) {
+ Log.e(
+ "mullvad",
+ "Failed to send message ${messageName ?: "<missing>"} due to RemoteException"
+ )
+ }
+ false
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt
new file mode 100644
index 0000000000..44ccdf013b
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt
@@ -0,0 +1,92 @@
+package net.mullvad.mullvadvpn.service
+
+import android.os.DeadObjectException
+import android.os.Looper
+import android.os.Messenger
+import android.util.Log
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.mockkStatic
+import io.mockk.slot
+import io.mockk.unmockkAll
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.EventDispatcher
+import net.mullvad.mullvadvpn.ipc.Request
+import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class ConnectionProxyTest {
+
+ @MockK
+ private lateinit var mockedMainLooper: Looper
+
+ @MockK
+ private lateinit var connection: Messenger
+
+ @MockK
+ private lateinit var mockedDispatchingHandler: EventDispatcher
+ lateinit var connectionProxy: ConnectionProxy
+
+ @Before
+ fun setup() {
+ mockkStatic(Looper::class)
+ mockkStatic(Log::class)
+ MockKAnnotations.init(this)
+ mockkObject(Request.Connect, Request.Disconnect)
+ every { Request.Connect.message } returns mockk()
+ every { Request.Disconnect.message } returns mockk()
+ every { Looper.getMainLooper() } returns mockedMainLooper
+ every { Log.e(any(), any()) } returns mockk(relaxed = true)
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_initialize_connection_proxy() {
+ // Arrange
+ val eventType = slot<KClass<Event.TunnelStateChange>>()
+ every {
+ mockedDispatchingHandler.registerHandler(capture(eventType), any())
+ } just Runs
+ // Create ConnectionProxy instance and assert initial Event type
+ connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
+ assertEquals(Event.TunnelStateChange::class, eventType.captured.java.kotlin)
+ }
+
+ @Test
+ fun test_tunnel_normal_connect_disconnect() {
+ // Arrange
+ every { connection.send(any()) } just Runs
+ every {
+ mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any())
+ } just Runs
+ // Act and Assert no crashes
+ connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
+ connectionProxy.connect()
+ connectionProxy.disconnect()
+ }
+
+ @Test
+ fun test_tunnel_handle_crash_on_connect() {
+ // Arrange
+ every { connection.send(any()) } throws DeadObjectException()
+ every {
+ mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any())
+ } just Runs
+ // Act and Assert no crashes
+ connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
+ connectionProxy.connect()
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt
new file mode 100644
index 0000000000..8b737cba69
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt
@@ -0,0 +1,81 @@
+package net.mullvad.mullvadvpn.service
+
+import android.os.DeadObjectException
+import android.os.Looper
+import android.os.Messenger
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import kotlin.reflect.KClass
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.EventDispatcher
+import net.mullvad.mullvadvpn.ipc.Request
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionDeviceDataSource
+import net.mullvad.mullvadvpn.util.JobTracker
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class ServiceConnectionDeviceDataSourceTest {
+ private val tracker = JobTracker()
+
+ @MockK
+ private lateinit var mockedMainLooper: Looper
+
+ @MockK
+ private lateinit var mockedDispatchingHandler: EventDispatcher
+
+ @MockK
+ private lateinit var connection: Messenger
+
+ lateinit var serviceConnectionDeviceDataSource: ServiceConnectionDeviceDataSource
+
+ @Before
+ fun setup() {
+ mockkStatic(Looper::class)
+ mockkStatic(android.util.Log::class)
+ MockKAnnotations.init(this)
+ mockkObject(Request.GetDevice, Request.RefreshDeviceState)
+ every { Request.GetDevice.message } returns mockk()
+ every { Request.RefreshDeviceState.message } returns mockk()
+ every { Looper.getMainLooper() } returns mockedMainLooper
+ every { android.util.Log.e(any(), any()) } returns mockk(relaxed = true)
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_get_devices_list() {
+ // Arrange
+ every { connection.send(any()) } just Runs
+ every {
+ mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any())
+ } just Runs
+ // Act and Assert no crashes
+ serviceConnectionDeviceDataSource =
+ ServiceConnectionDeviceDataSource(connection, mockedDispatchingHandler)
+ serviceConnectionDeviceDataSource.getDevice()
+ }
+
+ @Test
+ fun test_catch_exception_on_devices_list() {
+ // Arrange
+ every { connection.send(any()) } throws DeadObjectException()
+ every {
+ mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any())
+ } just Runs
+ // Act and Assert no crashes
+ serviceConnectionDeviceDataSource =
+ ServiceConnectionDeviceDataSource(connection, mockedDispatchingHandler)
+ serviceConnectionDeviceDataSource.getDevice()
+ }
+}