diff options
| author | Albin <albin@mullvad.net> | 2023-10-16 17:10:31 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-10-16 17:10:31 +0200 |
| commit | d32b6f81ceb8c383588678e8fbabe613fbac4142 (patch) | |
| tree | 2a33ca74411ee5208619d763dcde2b21b49988c2 /android | |
| parent | dc70ef89771a89d1b978516e3415ddf5d50cc033 (diff) | |
| parent | 989f57e29d1f694d8bfda186d52697aaf85dcae4 (diff) | |
| download | mullvadvpn-d32b6f81ceb8c383588678e8fbabe613fbac4142.tar.xz mullvadvpn-d32b6f81ceb8c383588678e8fbabe613fbac4142.zip | |
Merge branch 'expose-flexible-ipc-message-handling'
Diffstat (limited to 'android')
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") } |
