diff options
| author | Albin <albin@mullvad.net> | 2022-03-29 14:53:02 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2022-03-30 09:55:00 +0200 |
| commit | cd954e5cfb8b1457bd5261e85fc78fc860ae5032 (patch) | |
| tree | c5766920a3800f7926c3b93ec4448bab7a49d5cf | |
| parent | 5c014d6c6ec9a5685be738eb084549cbdacd177b (diff) | |
| download | mullvadvpn-cd954e5cfb8b1457bd5261e85fc78fc860ae5032.tar.xz mullvadvpn-cd954e5cfb8b1457bd5261e85fc78fc860ae5032.zip | |
Propagate device state over service connection
Adds device state communication between the app and service with a
repository backed by a data source to add abstraction layers on the app
side.
Limitations:
* Expiration is not updated correctly.
* The login/creation flow has not been fully adapted to devices.
16 files changed, 206 insertions, 51 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt index a5854232a8..7fe971e678 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt @@ -4,6 +4,7 @@ import android.os.Message as RawMessage import android.os.Messenger import kotlinx.parcelize.Parcelize import net.mullvad.mullvadvpn.model.AppVersionInfo as AppVersionInfoData +import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.model.GeoIpLocation import net.mullvad.mullvadvpn.model.KeygenEvent import net.mullvad.mullvadvpn.model.LoginStatus as LoginStatusData @@ -29,6 +30,9 @@ sealed class Event : Message.EventMessage() { data class CurrentVersion(val version: String?) : Event() @Parcelize + data class DeviceStateEvent(val newState: DeviceState) : Event() + + @Parcelize data class ListenerReady(val connection: Messenger, val listenerId: Int) : Event() @Parcelize diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt index 27a93b0a2d..78a6baa072 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt @@ -42,6 +42,9 @@ sealed class Request : Message.RequestMessage() { data class Login(val account: String?) : Request() @Parcelize + object RefreshDeviceState : Request() + + @Parcelize object Logout : Request() @Parcelize diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt new file mode 100644 index 0000000000..04b87a87cd --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt @@ -0,0 +1,29 @@ +package net.mullvad.mullvadvpn.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +sealed class DeviceState : Parcelable { + @Parcelize + object InitialState : DeviceState() + + @Parcelize + data class DeviceRegistered(val deviceConfig: DeviceConfig) : DeviceState() + + @Parcelize + object DeviceNotRegistered : DeviceState() + + fun isInitialState(): Boolean { + return this is InitialState + } + + fun token(): String? { + return (this as? DeviceRegistered)?.deviceConfig?.token + } + + companion object { + fun fromDeviceConfig(deviceConfig: DeviceConfig?): DeviceState { + return deviceConfig?.let { DeviceRegistered(it) } ?: DeviceNotRegistered + } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt index f4b7993e38..ea66d6b308 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt @@ -1,9 +1,13 @@ package net.mullvad.mullvadvpn.service +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import net.mullvad.mullvadvpn.model.AppVersionInfo import net.mullvad.mullvadvpn.model.Device import net.mullvad.mullvadvpn.model.DeviceConfig import net.mullvad.mullvadvpn.model.DeviceEvent +import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.model.DnsOptions import net.mullvad.mullvadvpn.model.GeoIpLocation import net.mullvad.mullvadvpn.model.GetAccountDataResult @@ -22,7 +26,6 @@ import net.mullvad.talpid.util.EventNotifier class MullvadDaemon(vpnService: MullvadVpnService) { protected var daemonInterfaceAddress = 0L - var onDeviceChange = EventNotifier<DeviceConfig?>(null) var onDeviceRemoved = EventNotifier<RemoveDeviceEvent?>(null) val onSettingsChange = EventNotifier<Settings?>(null) var onTunnelStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected) @@ -32,12 +35,17 @@ class MullvadDaemon(vpnService: MullvadVpnService) { var onRelayListChange: ((RelayList) -> Unit)? = null var onDaemonStopped: (() -> Unit)? = null + private val _deviceStateUpdates = MutableSharedFlow<DeviceState>( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val deviceStateUpdates = _deviceStateUpdates.asSharedFlow() + init { System.loadLibrary("mullvad_jni") initialize(vpnService, vpnService.cacheDir.absolutePath, vpnService.filesDir.absolutePath) onSettingsChange.notify(getSettings()) - onDeviceChange.notify(getDevice()) onTunnelStateChange.notify(getState() ?: TunnelState.Disconnected) } @@ -120,8 +128,8 @@ class MullvadDaemon(vpnService: MullvadVpnService) { fun getDevice(): DeviceConfig? = getDevice(daemonInterfaceAddress) - fun removeDevice(accountToken: String?, device: Device) { - removeDevice(daemonInterfaceAddress, accountToken, device.id) + fun removeDevice(accountToken: String, deviceId: String): RemoveDeviceResult { + return removeDevice(daemonInterfaceAddress, accountToken, deviceId) } fun setAllowLan(allowLan: Boolean) { @@ -251,7 +259,7 @@ class MullvadDaemon(vpnService: MullvadVpnService) { } private fun notifyDeviceEvent(event: DeviceEvent) { - onDeviceChange.notify(event.device) + _deviceStateUpdates.tryEmit(DeviceState.fromDeviceConfig(event.device)) } private fun notifyRemoveDeviceEvent(event: RemoveDeviceEvent) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt index 40fc8d7bd1..435c6bd220 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt @@ -204,10 +204,8 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } private suspend fun doLogout() { - if (accountNumber != null) { - daemon.await().logoutAccount() - loginStatus = null - } + daemon.await().logoutAccount() + loginStatus = null } private fun fetchAccountHistory() { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/DaemonDeviceDataSource.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/DaemonDeviceDataSource.kt new file mode 100644 index 0000000000..dd0769c5e7 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/DaemonDeviceDataSource.kt @@ -0,0 +1,47 @@ +package net.mullvad.mullvadvpn.service.endpoint + +import kotlinx.coroutines.flow.collect +import net.mullvad.mullvadvpn.ipc.Event +import net.mullvad.mullvadvpn.ipc.Request +import net.mullvad.mullvadvpn.model.DeviceState +import net.mullvad.mullvadvpn.service.MullvadDaemon +import net.mullvad.mullvadvpn.util.JobTracker + +class DaemonDeviceDataSource( + val endpoint: ServiceEndpoint +) { + private val tracker = JobTracker() + + init { + endpoint.intermittentDaemon.registerListener(this) { daemon -> + if (daemon != null) { + launchDeviceEndpointJobs(daemon) + } else { + tracker.cancelAllJobs() + } + } + } + + private fun launchDeviceEndpointJobs(daemon: MullvadDaemon) { + tracker.newBackgroundJob("propagateDeviceUpdates") { + daemon.deviceStateUpdates.collect { newState -> + endpoint.sendEvent(Event.DeviceStateEvent(newState)) + } + } + + endpoint.dispatcher.registerHandler(Request.RefreshDeviceState::class) { + tracker.newBackgroundJob("refreshDeviceJob") { + daemon.getDevice() + .let { deviceConfig -> + Event.DeviceStateEvent(DeviceState.fromDeviceConfig(deviceConfig)) + } + .also { event -> endpoint.sendEvent(event) } + } + } + } + + fun onDestroy() { + tracker.cancelAllJobs() + endpoint.intermittentDaemon.unregisterListener(this) + } +} 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 0a0c41b42e..f53454b401 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 @@ -58,6 +58,8 @@ class ServiceEndpoint( val splitTunneling = SplitTunneling(SplitTunnelingPersistence(context), this) val voucherRedeemer = VoucherRedeemer(this) + private val deviceRepositoryBackend = DaemonDeviceDataSource(this) + init { dispatcher.apply { registerHandler(Request.RegisterListener::class) { request -> @@ -79,6 +81,7 @@ class ServiceEndpoint( authTokenCache.onDestroy() connectionProxy.onDestroy() customDns.onDestroy() + deviceRepositoryBackend.onDestroy() keyStatusListener.onDestroy() locationInfoCache.onDestroy() relayListListener.onDestroy() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt index 02c8bba71f..00fa67059c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt @@ -6,6 +6,8 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentManager import java.text.DateFormat +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.ui.widget.Button @@ -93,10 +95,14 @@ class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) { } override fun onSafelyStart() { - accountCache.onAccountNumberChange.subscribe(this) { accountNumber -> - jobTracker.newUiJob("updateAccountNumber") { - accountNumberView.information = accountNumber - } + jobTracker.newUiJob("updateAccountNumber") { + deviceRepository.deviceState + .onEach { state -> + if (state.isInitialState()) deviceRepository.refreshDeviceState() + } + .collect { + accountNumberView.information = it.token() + } } accountCache.onAccountExpiryChange.subscribe(this) { accountExpiry -> @@ -124,8 +130,8 @@ class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) { } override fun onSafelyStop() { - accountCache.onAccountNumberChange.unsubscribe(this) accountCache.onAccountExpiryChange.unsubscribe(this) + jobTracker.cancelAllJobs() } override fun onSafelyDestroyView() { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt index c6ce330128..5f0e417f58 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt @@ -4,18 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onSubscription import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.model.DeviceState +import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection class LaunchFragment : ServiceAwareFragment() { - private val hasAccountToken = CompletableDeferred<Boolean>() - - override fun onNewServiceConnection(serviceConnection: ServiceConnection) { - serviceConnection.settingsListener.accountNumberNotifier.subscribe(this) { accountToken -> - hasAccountToken.complete(accountToken != null) - } - } override fun onCreateView( inflater: LayoutInflater, @@ -31,25 +27,27 @@ class LaunchFragment : ServiceAwareFragment() { return view } - override fun onStart() { - super.onStart() - - jobTracker.newUiJob("advanceToNextScreen") { - advanceToNextScreen() - } - } - override fun onStop() { jobTracker.cancelJob("advanceToNextScreen") - super.onStop() } - private suspend fun advanceToNextScreen() { - if (hasAccountToken.await()) { - advanceToConnectScreen() - } else { - advanceToLoginScreen() + override fun onNewServiceConnection(serviceConnection: ServiceConnection) { + advanceToNextScreen(serviceConnection.deviceRepository) + } + + private fun advanceToNextScreen(deviceRepository: DeviceRepository) { + jobTracker.newUiJob("advanceToNextScreen") { + deviceRepository.deviceState + .onSubscription { deviceRepository.refreshDeviceState() } + .first { state -> state.isInitialState().not() } + .let { deviceState -> + when (deviceState) { + is DeviceState.DeviceRegistered -> advanceToConnectScreen() + is DeviceState.DeviceNotRegistered -> advanceToLoginScreen() + else -> Unit + } + } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt index c71c321b39..c2c1c2fc26 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt @@ -10,6 +10,7 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy import net.mullvad.mullvadvpn.ui.serviceconnection.CustomDns +import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.KeyStatusListener import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener @@ -48,6 +49,9 @@ abstract class ServiceDependentFragment(private val onNoService: OnNoService) : lateinit var customDns: CustomDns private set + lateinit var deviceRepository: DeviceRepository + private set + lateinit var keyStatusListener: KeyStatusListener private set @@ -70,6 +74,7 @@ abstract class ServiceDependentFragment(private val onNoService: OnNoService) : appVersionInfoCache = serviceConnection.appVersionInfoCache authTokenCache = serviceConnection.authTokenCache connectionProxy = serviceConnection.connectionProxy + deviceRepository = serviceConnection.deviceRepository customDns = serviceConnection.customDns keyStatusListener = serviceConnection.keyStatusListener locationInfoCache = serviceConnection.locationInfoCache diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt index 7204c1084c..9afd5bdb2c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt @@ -8,9 +8,12 @@ import android.widget.ImageButton import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.ui.serviceconnection.AccountCache import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache +import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection import net.mullvad.mullvadvpn.ui.widget.AccountCell import net.mullvad.mullvadvpn.ui.widget.AppVersionCell @@ -26,10 +29,12 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar private var active = false private var accountCache: AccountCache? = null + private var deviceRepository: DeviceRepository? = null private var versionInfoCache: AppVersionInfoCache? = null override fun onNewServiceConnection(serviceConnection: ServiceConnection) { accountCache = serviceConnection.accountCache + deviceRepository = serviceConnection.deviceRepository versionInfoCache = serviceConnection.appVersionInfoCache if (active) { @@ -39,6 +44,7 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar override fun onNoServiceConnection() { accountCache = null + deviceRepository = null versionInfoCache = null } @@ -102,10 +108,11 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar versionInfoCache?.onUpdate = null accountCache?.apply { - onAccountNumberChange.unsubscribe(this@SettingsFragment) onAccountExpiryChange.unsubscribe(this@SettingsFragment) } + jobTracker.cancelAllJobs() + super.onStop() } @@ -116,12 +123,6 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar private fun configureListeners() { accountCache?.apply { - onAccountNumberChange.subscribe(this@SettingsFragment) { account -> - jobTracker.newUiJob("updateLoggedInStatus") { - updateLoggedInStatus(account != null) - } - } - onAccountExpiryChange.subscribe(this@SettingsFragment) { expiry -> jobTracker.newUiJob("updateAccountInfo") { accountMenu.accountExpiry = expiry @@ -131,6 +132,16 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar fetchAccountExpiry() } + jobTracker.newUiJob("updateLoggedInStatus") { + deviceRepository?.let { repository -> + repository.deviceState + .onEach { state -> if (state.isInitialState()) repository.refreshDeviceState() } + .collect { device -> + updateLoggedInStatus(device is DeviceState.DeviceRegistered) + } + } + } + versionInfoCache?.onUpdate = { jobTracker.newUiJob("updateVersionInfo") { updateVersionInfo() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt index d9968a79a4..6bf4b5205e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt @@ -57,9 +57,7 @@ class WelcomeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) { } override fun onSafelyStart() { - accountCache.onAccountNumberChange.subscribe(this) { account -> - updateAccountNumber(account) - } + updateAccountNumber(deviceRepository.deviceState.value.token()) accountCache.onAccountExpiryChange.subscribe(this) { expiry -> checkExpiry(expiry) @@ -76,7 +74,6 @@ class WelcomeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) { } override fun onSafelyStop() { - accountCache.onAccountNumberChange.unsubscribe(this) accountCache.onAccountExpiryChange.unsubscribe(this) jobTracker.cancelJob("pollAccountData") } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt index 9bf5942f4c..6bec5c88b5 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt @@ -9,7 +9,6 @@ import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime class AccountCache(private val connection: Messenger, eventDispatcher: EventDispatcher) { - val onAccountNumberChange = EventNotifier<String?>(null) val onAccountExpiryChange = EventNotifier<DateTime?>(null) val onAccountHistoryChange = EventNotifier<String?>(null) val onLoginStatusChange = EventNotifier<LoginStatus?>(null) @@ -25,8 +24,6 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp registerHandler(Event.LoginStatus::class) { event -> loginStatus = event.status - - onAccountNumberChange.notifyIfChanged(loginStatus?.account) onAccountExpiryChange.notifyIfChanged(loginStatus?.expiry) } } @@ -59,7 +56,6 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp } fun onDestroy() { - onAccountNumberChange.unsubscribeAll() onAccountExpiryChange.unsubscribeAll() onAccountHistoryChange.unsubscribeAll() onLoginStatusChange.unsubscribeAll() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt new file mode 100644 index 0000000000..de796c5be3 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt @@ -0,0 +1,20 @@ +package net.mullvad.mullvadvpn.ui.serviceconnection + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily +import kotlinx.coroutines.flow.stateIn +import net.mullvad.mullvadvpn.model.DeviceState + +class DeviceRepository( + private val dataSource: ServiceConnectionDeviceDataSource, + externalScope: CoroutineScope +) { + val deviceState = dataSource.deviceStateUpdates + .stateIn( + externalScope, + Lazily, + DeviceState.InitialState + ) + + fun refreshDeviceState() = dataSource.refreshDevice() +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt index 861a54a561..bea6c34ecb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt @@ -4,6 +4,7 @@ import android.os.Looper import android.os.Messenger import android.os.RemoteException import android.util.Log +import kotlinx.coroutines.MainScope import net.mullvad.mullvadvpn.di.SERVICE_CONNECTION_SCOPE import net.mullvad.mullvadvpn.ipc.DispatchingHandler import net.mullvad.mullvadvpn.ipc.Event @@ -35,6 +36,8 @@ class ServiceConnection( val accountCache = AccountCache(connection, dispatcher) val authTokenCache = AuthTokenCache(connection, dispatcher) val connectionProxy = ConnectionProxy(connection, dispatcher) + val deviceRepository = + DeviceRepository(ServiceConnectionDeviceDataSource(connection, dispatcher), MainScope()) val keyStatusListener = KeyStatusListener(connection, dispatcher) val locationInfoCache = LocationInfoCache(dispatcher) val settingsListener = SettingsListener(connection, dispatcher) 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 new file mode 100644 index 0000000000..60a2eb68e9 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt @@ -0,0 +1,27 @@ +package net.mullvad.mullvadvpn.ui.serviceconnection + +import android.os.Messenger +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import net.mullvad.mullvadvpn.ipc.Event +import net.mullvad.mullvadvpn.ipc.EventDispatcher +import net.mullvad.mullvadvpn.ipc.Request + +class ServiceConnectionDeviceDataSource( + private val connection: Messenger, + private val dispatcher: EventDispatcher +) { + val deviceStateUpdates = callbackFlow { + dispatcher.registerHandler(Event.DeviceStateEvent::class) { event -> + trySend(event.newState) + } + awaitClose { + // The current dispatcher doesn't support unregistration of handlers. + } + } + + // Async result: Event.DeviceChanged + fun refreshDevice() { + connection.send(Request.RefreshDeviceState.message) + } +} |
