summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-10-16 17:10:31 +0200
committerAlbin <albin@mullvad.net>2023-10-16 17:10:31 +0200
commitd32b6f81ceb8c383588678e8fbabe613fbac4142 (patch)
tree2a33ca74411ee5208619d763dcde2b21b49988c2 /android
parentdc70ef89771a89d1b978516e3415ddf5d50cc033 (diff)
parent989f57e29d1f694d8bfda186d52697aaf85dcae4 (diff)
downloadmullvadvpn-d32b6f81ceb8c383588678e8fbabe613fbac4142.tar.xz
mullvadvpn-d32b6f81ceb8c383588678e8fbabe613fbac4142.zip
Merge branch 'expose-flexible-ipc-message-handling'
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AccountRepository.kt56
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/MessageHandler.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionAccountDataSource.kt58
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt21
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManagerExtensions.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt4
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt7
10 files changed, 86 insertions, 94 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 7134f7b7d2..1d4421b063 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -15,6 +15,7 @@ import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
+import net.mullvad.mullvadvpn.ui.serviceconnection.MessageHandler
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
import net.mullvad.mullvadvpn.util.ChangelogDataProvider
@@ -40,6 +41,7 @@ import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named
+import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.dsl.onClose
@@ -59,7 +61,7 @@ val uiModule = module {
SplitTunneling(messenger, dispatcher)
}
- single { ServiceConnectionManager(androidContext()) }
+ single { ServiceConnectionManager(androidContext()) } bind MessageHandler::class
single { InetAddressValidator.getInstance() }
single { androidContext().resources }
single { androidContext().assets }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AccountRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AccountRepository.kt
index de72fa8d93..0bf8b1b8ec 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AccountRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AccountRepository.kt
@@ -10,43 +10,41 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import net.mullvad.mullvadvpn.lib.ipc.Event
+import net.mullvad.mullvadvpn.lib.ipc.Request
import net.mullvad.mullvadvpn.model.AccountCreationResult
import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.AccountHistory
import net.mullvad.mullvadvpn.model.LoginResult
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.accountDataSource
-import net.mullvad.mullvadvpn.util.flatMapReadyConnectionOrDefault
+import net.mullvad.mullvadvpn.ui.serviceconnection.MessageHandler
+import net.mullvad.mullvadvpn.ui.serviceconnection.events
class AccountRepository(
- private val serviceConnectionManager: ServiceConnectionManager,
- val dispatcher: CoroutineDispatcher = Dispatchers.IO
+ private val messageHandler: MessageHandler,
+ private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private val _cachedCreatedAccount = MutableStateFlow<String?>(null)
val cachedCreatedAccount = _cachedCreatedAccount.asStateFlow()
private val accountCreationEvents: SharedFlow<AccountCreationResult> =
- serviceConnectionManager.connectionState
- .flatMapReadyConnectionOrDefault(flowOf()) { state ->
- state.container.accountDataSource.accountCreationResult
- }
+ messageHandler
+ .events<Event.AccountCreationEvent>()
+ .map { it.result }
.onEach {
_cachedCreatedAccount.value = (it as? AccountCreationResult.Success)?.accountToken
}
.shareIn(CoroutineScope(dispatcher), SharingStarted.WhileSubscribed())
val accountExpiryState: StateFlow<AccountExpiry> =
- serviceConnectionManager.connectionState
- .flatMapReadyConnectionOrDefault(flowOf()) { state ->
- state.container.accountDataSource.accountExpiry
- }
+ messageHandler
+ .events<Event.AccountExpiryEvent>()
+ .map { it.expiry }
.stateIn(
CoroutineScope(dispatcher),
SharingStarted.WhileSubscribed(),
@@ -54,10 +52,9 @@ class AccountRepository(
)
val accountHistory: StateFlow<AccountHistory> =
- serviceConnectionManager.connectionState
- .flatMapReadyConnectionOrDefault(flowOf()) { state ->
- state.container.accountDataSource.accountHistory
- }
+ messageHandler
+ .events<Event.AccountHistoryEvent>()
+ .map { it.history }
.onStart { fetchAccountHistory() }
.stateIn(
CoroutineScope(dispatcher),
@@ -65,42 +62,41 @@ class AccountRepository(
AccountHistory.Missing
)
- private val loginEvents: SharedFlow<Event.LoginEvent> =
- serviceConnectionManager.connectionState
- .flatMapReadyConnectionOrDefault(flowOf()) { state ->
- state.container.accountDataSource.loginEvents
- }
+ private val loginEvents: SharedFlow<LoginResult> =
+ messageHandler
+ .events<Event.LoginEvent>()
+ .map { it.result }
.shareIn(CoroutineScope(dispatcher), SharingStarted.WhileSubscribed())
suspend fun createAccount(): AccountCreationResult =
withContext(dispatcher) {
val deferred = async { accountCreationEvents.first() }
- serviceConnectionManager.accountDataSource()?.createAccount()
+ messageHandler.trySendRequest(Request.CreateAccount)
deferred.await()
}
suspend fun login(accountToken: String): LoginResult =
withContext(Dispatchers.IO) {
- val deferred = async { loginEvents.first().result }
- serviceConnectionManager.accountDataSource()?.login(accountToken)
+ val deferred = async { loginEvents.first() }
+ messageHandler.trySendRequest(Request.Login(accountToken))
deferred.await()
}
fun logout() {
clearCreatedAccountCache()
- serviceConnectionManager.accountDataSource()?.logout()
+ messageHandler.trySendRequest(Request.Logout)
}
fun fetchAccountExpiry() {
- serviceConnectionManager.accountDataSource()?.fetchAccountExpiry()
+ messageHandler.trySendRequest(Request.FetchAccountExpiry)
}
fun fetchAccountHistory() {
- serviceConnectionManager.accountDataSource()?.fetchAccountHistory()
+ messageHandler.trySendRequest(Request.FetchAccountHistory)
}
fun clearAccountHistory() {
- serviceConnectionManager.accountDataSource()?.clearAccountHistory()
+ messageHandler.trySendRequest(Request.ClearAccountHistory)
}
private fun clearCreatedAccountCache() {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index 3d30d28845..e8c8cefb81 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import net.mullvad.mullvadvpn.BuildConfig
@@ -312,6 +313,7 @@ open class MainActivity : FragmentActivity() {
private suspend fun isExpired(timeoutMillis: Long): Boolean {
return withTimeoutOrNull(timeoutMillis) {
accountRepository.accountExpiryState
+ .onSubscription { accountRepository.fetchAccountExpiry() }
.filter { it is AccountExpiry.Available }
.map { it.date()?.isBeforeNow }
.first()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/MessageHandler.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/MessageHandler.kt
new file mode 100644
index 0000000000..3bbcae9361
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/MessageHandler.kt
@@ -0,0 +1,16 @@
+package net.mullvad.mullvadvpn.ui.serviceconnection
+
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import net.mullvad.mullvadvpn.lib.ipc.Event
+import net.mullvad.mullvadvpn.lib.ipc.Request
+
+interface MessageHandler {
+ fun <R : Event> events(klass: KClass<R>): Flow<R>
+
+ fun trySendRequest(request: Request): Boolean
+}
+
+inline fun <reified R : Event> MessageHandler.events(): Flow<R> {
+ return this.events(R::class)
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionAccountDataSource.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionAccountDataSource.kt
deleted file mode 100644
index d383035102..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionAccountDataSource.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.mullvad.mullvadvpn.ui.serviceconnection
-
-import android.os.Messenger
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
-import net.mullvad.mullvadvpn.lib.ipc.Request
-
-class ServiceConnectionAccountDataSource(
- private val connection: Messenger,
- private val dispatcher: EventDispatcher
-) {
- val accountCreationResult = callbackFlow {
- val handler: (Event.AccountCreationEvent) -> Unit = { event -> trySend(event.result) }
- dispatcher.registerHandler(Event.AccountCreationEvent::class, handler)
- awaitClose {
- // The current dispatcher doesn't support unregistration of handlers.
- }
- }
-
- val accountExpiry = callbackFlow {
- val handler: (Event.AccountExpiryEvent) -> Unit = { event -> trySend(event.expiry) }
- dispatcher.registerHandler(Event.AccountExpiryEvent::class, handler)
- connection.send(Request.FetchAccountExpiry.message)
- awaitClose {
- // The current dispatcher doesn't support unregistration of handlers.
- }
- }
-
- val accountHistory = callbackFlow {
- val handler: (Event.AccountHistoryEvent) -> Unit = { event -> trySend(event.history) }
- dispatcher.registerHandler(Event.AccountHistoryEvent::class, handler)
- awaitClose {
- // The current dispatcher doesn't support unregistration of handlers.
- }
- }
-
- val loginEvents = callbackFlow {
- val handler: (Event.LoginEvent) -> Unit = { event -> trySend(event) }
- dispatcher.registerHandler(Event.LoginEvent::class, handler)
- awaitClose {
- // The current dispatcher doesn't support unregistration of handlers.
- }
- }
-
- fun createAccount() = connection.send(Request.CreateAccount.message)
-
- fun login(accountToken: String) = connection.send(Request.Login(accountToken).message)
-
- fun logout() = connection.send(Request.Logout.message)
-
- fun fetchAccountExpiry() = connection.send(Request.FetchAccountExpiry.message)
-
- fun fetchAccountHistory() = connection.send(Request.FetchAccountHistory.message)
-
- fun clearAccountHistory() = connection.send(Request.ClearAccountHistory.message)
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
index 6dbea7a517..7d34f2a96f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
@@ -4,9 +4,11 @@ import android.os.Looper
import android.os.Messenger
import android.os.RemoteException
import android.util.Log
+import kotlinx.coroutines.flow.filterIsInstance
import net.mullvad.mullvadvpn.lib.ipc.DispatchingHandler
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.Request
+import net.mullvad.mullvadvpn.lib.ipc.extensions.trySendRequest
import org.koin.core.component.KoinComponent
// Container of classes that communicate with the service through an active connection
@@ -21,7 +23,8 @@ class ServiceConnectionContainer(
private val dispatcher =
DispatchingHandler(Looper.getMainLooper()) { message -> Event.fromMessage(message) }
- val accountDataSource = ServiceConnectionAccountDataSource(connection, dispatcher)
+ val events = dispatcher.parsedMessages.filterIsInstance<Event>()
+
val authTokenCache = AuthTokenCache(connection, dispatcher)
val connectionProxy = ConnectionProxy(connection, dispatcher)
val deviceDataSource = ServiceConnectionDeviceDataSource(connection, dispatcher)
@@ -49,6 +52,10 @@ class ServiceConnectionContainer(
registerListener(connection)
}
+ fun trySendRequest(request: Request, logErrors: Boolean): Boolean {
+ return connection.trySendRequest(request, logErrors = logErrors)
+ }
+
fun onDestroy() {
unregisterListener()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt
index 0bbc4e71d4..d840b93491 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt
@@ -6,15 +6,22 @@ import android.content.Intent
import android.os.IBinder
import android.os.Messenger
import android.util.Log
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
import net.mullvad.mullvadvpn.lib.endpoint.BuildConfig
import net.mullvad.mullvadvpn.lib.endpoint.putApiEndpointConfigurationExtra
+import net.mullvad.mullvadvpn.lib.ipc.Event
+import net.mullvad.mullvadvpn.lib.ipc.Request
import net.mullvad.mullvadvpn.service.MullvadVpnService
+import net.mullvad.mullvadvpn.util.flatMapReadyConnectionOrDefault
import net.mullvad.talpid.util.EventNotifier
-class ServiceConnectionManager(private val context: Context) {
+class ServiceConnectionManager(private val context: Context) : MessageHandler {
private val _connectionState =
MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
@@ -27,6 +34,9 @@ class ServiceConnectionManager(private val context: Context) {
var isBound = false
private var vpnPermissionRequestHandler: (() -> Unit)? = null
+ private val events =
+ connectionState.flatMapReadyConnectionOrDefault(emptyFlow()) { it.container.events }
+
private val serviceConnection =
object : android.content.ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
@@ -82,6 +92,15 @@ class ServiceConnectionManager(private val context: Context) {
}
}
+ override fun <E : Event> events(klass: KClass<E>): Flow<E> {
+ return events.filterIsInstance(klass)
+ }
+
+ override fun trySendRequest(request: Request): Boolean {
+ return connectionState.value.readyContainer()?.trySendRequest(request, logErrors = false)
+ ?: false
+ }
+
fun onDestroy() {
_connectionState.value.readyContainer()?.onDestroy()
serviceNotifier.unsubscribeAll()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManagerExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManagerExtensions.kt
index f7be833792..c4b3d100bd 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManagerExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManagerExtensions.kt
@@ -1,8 +1,5 @@
package net.mullvad.mullvadvpn.ui.serviceconnection
-fun ServiceConnectionManager.accountDataSource() =
- this.connectionState.value.readyContainer()?.accountDataSource
-
fun ServiceConnectionManager.appVersionInfoCache() =
this.connectionState.value.readyContainer()?.appVersionInfoCache
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt
index a03de806b4..fb3e3d6393 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt
@@ -41,6 +41,10 @@ class AccountViewModel(
@Suppress("konsist.ensure public properties use permitted names")
val enterTransitionEndAction = _enterTransitionEndAction.asSharedFlow()
+ init {
+ accountRepository.fetchAccountExpiry()
+ }
+
fun onManageAccountClick() {
viewModelScope.launch {
_uiSideEffect.tryEmit(
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
index 7f6195c61e..dd73f3c156 100644
--- 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
@@ -7,12 +7,18 @@ import android.util.Log
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
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()
+ private val _parsedMessages = MutableSharedFlow<T>(extraBufferCapacity = 1)
+ val parsedMessages = _parsedMessages.asSharedFlow()
+
+ @Deprecated("Use parsedMessages instead.")
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) }
@@ -27,6 +33,7 @@ class DispatchingHandler<T : Any>(looper: Looper, private val extractor: (Messag
val handler = handlers.get(instance::class)
handler?.invoke(instance)
+ _parsedMessages.tryEmit(instance)
} else {
Log.e("mullvad", "Dispatching handler received an unexpected message")
}